Compare commits
2 Commits
ed945abdf1
...
81833de6ef
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81833de6ef | ||
|
|
e7c301023c |
@@ -1,11 +1,11 @@
|
||||
---
|
||||
name: dds-to-skill
|
||||
description: >
|
||||
将 DDS(详细设计说明书)/ PRD / 架构文档转换为一套可落地的 Claude Code Agent Skills(Converts DDS/PRD/Architecture docs into production-ready Agent Skills)。
|
||||
包含系统级 Skill、模块级 Skills、横切 Skills 的完整生成流程,涵盖设计细节抽取、reference 分层、frontmatter 规范、质量自检。
|
||||
触发场景 Trigger: 当用户需要将 DDS 文档转为 Skills / 需要从架构设计文档生成开发指导 Skill / 需要批量创建模块级 Skill 套件。
|
||||
关键词 Keywords: DDS, PRD, 架构说明, 设计文档, skill 生成, skill 套件, agent skill, 模块拆分, reference 抽取, 契约, API, 状态机, 事件, Schema。
|
||||
argument-hint: "<dds-file-path> [--output-dir <skills-output-dir>] [--project-name <name>]"
|
||||
将单模块 DDS(详细设计说明书)/ PRD / 架构文档转换为一个 All-in-One 全栈开发指导 Skill(Converts a single-module DDS/PRD/Architecture doc into one All-in-One development guidebook Skill)。
|
||||
输出唯一的 developing-<module-name> Skill,涵盖 API、数据库、状态机、事件、安全等全栈设计细节抽取与 reference 分层索引。
|
||||
触发场景 Trigger: 当用户需要将单模块 DDS 文档转为可落地的开发指导 Skill / 需要从架构设计文档生成 All-in-One 开发向导。
|
||||
关键词 Keywords: DDS, PRD, 架构说明, 设计文档, skill 生成, all-in-one, agent skill, reference 抽取, API, 状态机, 事件, Schema。
|
||||
argument-hint: "<dds-file-path> [--output-dir <skills-output-dir>] [--module-name <name>]"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
@@ -15,11 +15,16 @@ allowed-tools:
|
||||
- Bash
|
||||
---
|
||||
|
||||
# DDS-to-Skill:从设计文档生成 Agent Skills
|
||||
# DDS-to-Skill:从设计文档生成 All-in-One 开发指导 Skill
|
||||
|
||||
本 Skill 指导你将一份 DDS(Detailed Design Specification)或 PRD / 架构说明文档,转换为一套**可落地、含设计细节**的 Claude Code Agent Skills 套件。
|
||||
本 Skill 指导你将一份单模块 DDS(Detailed Design Specification)或 PRD / 架构说明文档,转换为**唯一一个**包含全栈开发细节的 `developing-<module-name>` Skill。
|
||||
|
||||
> **核心理念**:生成的不是"空洞的工作流提示词",而是**绑定了 DDS 设计细节**、能指导真实开发/审查的 Skill 套件。
|
||||
> **核心理念**:一个 DDS 输入 → 一个 Skill 输出。生成的不是"空洞的工作流提示词",而是**绑定了 DDS 设计细节**、能指导真实全栈开发的 All-in-One 指导书。
|
||||
|
||||
> **⚠️ 强制约束**:
|
||||
> - **禁止**生成系统级 Skill(`*-system`)
|
||||
> - **禁止**生成横切/全局 Skill(`managing-*`、`designing-*`、`implementing-auth` 等)
|
||||
> - 唯一合法输出为 **1 个** `developing-<module-name>` 目录
|
||||
|
||||
---
|
||||
|
||||
@@ -66,62 +71,73 @@ allowed-tools:
|
||||
|
||||
若无法读取文件,**必须停止**,输出"继续所需的最小信息清单":
|
||||
|
||||
1. 系统模块列表(名称 + 职责 + 关键技术)
|
||||
2. 每个模块的接口/API 列表
|
||||
1. 模块名称、职责与关键技术栈
|
||||
2. 接口/API 列表
|
||||
3. 事件/Topic 定义
|
||||
4. 数据库表结构
|
||||
5. 状态机/流程定义
|
||||
6. 授权模型
|
||||
7. 模块间依赖关系
|
||||
7. 外部依赖关系
|
||||
|
||||
**禁止在缺少源文档的情况下臆造设计细节。**
|
||||
|
||||
---
|
||||
|
||||
## Phase 1:分析与规划
|
||||
## Phase 1:分析与规划(单模块全包容)
|
||||
|
||||
### 1.1 模块识别
|
||||
### 1.1 模块识别与命名
|
||||
|
||||
从 DDS 中识别所有业务模块,生成模块清单表:
|
||||
从 DDS 中识别目标模块,确定唯一 Skill 名称:
|
||||
|
||||
| 模块名 | 职责概述 | 关键技术 | Skill 类型 |
|
||||
|--------|---------|---------|-----------|
|
||||
| *从 DDS 抽取* | *从 DDS 抽取* | *从 DDS 抽取* | 系统级/模块级/横切 |
|
||||
| 项目 | 内容 |
|
||||
|------|------|
|
||||
| 模块名 | *从 DDS 抽取* |
|
||||
| 职责概述 | *从 DDS 抽取* |
|
||||
| 关键技术栈 | *从 DDS 抽取* |
|
||||
| **Skill 名称** | `developing-<module-name>` |
|
||||
|
||||
### 1.2 Skill 三层架构规划
|
||||
> **命名规则**:
|
||||
> - 动名词 `developing-` 前缀 + 模块名
|
||||
> - 小写字母 + 数字 + 连字符
|
||||
> - ≤ 64 字符
|
||||
|
||||
必须生成 3 类 Skills:
|
||||
### 1.2 单模块 All-in-One 规划
|
||||
|
||||
**A) 系统级 Skill(1 个)**
|
||||
- 跨模块一致性、依赖规则、全局变更流程
|
||||
- 命名:`developing-<system-name>-system`
|
||||
**唯一输出**为 `developing-<module-name>`,该 Skill 必须涵盖以下所有职责:
|
||||
|
||||
**B) 模块级 Skills(N 个,每模块 1 个)**
|
||||
- 高频开发指导:实现步骤 + 依赖影响检查
|
||||
- 命名:`developing-<module-name>`
|
||||
- ✅ 模块架构总览与技术栈说明
|
||||
- ✅ 数据库 Schema 与迁移指导
|
||||
- ✅ API/接口实现指导
|
||||
- ✅ 状态机/业务流程实现指导
|
||||
- ✅ 事件/消息处理指导
|
||||
- ✅ 安全/授权实现指导(如适用)
|
||||
- ✅ 外部依赖与集成点说明
|
||||
- ✅ 可观测性/监控/日志规范(如适用)
|
||||
|
||||
**C) 横切 Skills(≥ 3 个)**
|
||||
- 基于 DDS 内容选择,常见横切关注点:
|
||||
**⚠️ 以下生成物被严格禁止**:
|
||||
|
||||
| 横切主题 | 适用场景 | 参考命名 |
|
||||
| 禁止类型 | 匹配模式 | 禁止原因 |
|
||||
|---------|---------|---------|
|
||||
| API/事件/Schema 契约 | 有跨模块接口定义 | `designing-contracts` |
|
||||
| 数据库迁移 | 有 DB Schema 定义 | `managing-db-migrations` |
|
||||
| 可观测性/审计 | 有日志/监控/审计需求 | `managing-observability` |
|
||||
| 安全/认证 | 有 RBAC/JWT/授权体系 | `implementing-auth` |
|
||||
| 前端开发规范 | 有前端架构设计 | `frontend-<framework>` |
|
||||
| 后端编码规范 | 有后端技术栈规范 | `backend-<framework>` |
|
||||
| 部署/运维 | 有 K8S/Docker/CI 设计 | `deploying-<target>` |
|
||||
| 系统级 Skill | `*-system` | 单模块场景无需跨系统协调 |
|
||||
| 横切/契约 Skill | `designing-*` | 所有契约信息收入唯一 Skill 的 reference/ |
|
||||
| 横切/管理 Skill | `managing-*` | 所有运维/迁移/监控信息收入唯一 Skill |
|
||||
| 横切/实现 Skill | `implementing-*` | 所有实现指导收入唯一 Skill 的 Execute 章节 |
|
||||
|
||||
> 实际横切 Skills 必须根据 DDS 内容动态决定,不可少于 3 个。
|
||||
### 1.3 Reference 目录规划
|
||||
|
||||
### 1.3 Name 候选与确认
|
||||
规划 `reference/` 的分层目录结构,将所有设计细节(API、DB、状态机、事件等)集中在唯一 Skill 下:
|
||||
|
||||
为每个 Skill 提供 2~3 个命名候选,从中选择 1 个并说明理由。命名规则:
|
||||
- 动名词形式(如 `developing-*`、`managing-*`、`implementing-*`)
|
||||
- 小写字母 + 数字 + 连字符
|
||||
- ≤ 64 字符
|
||||
- 包含模块名或领域名
|
||||
```
|
||||
developing-<module-name>/reference/
|
||||
├── 01-<section-slug>/
|
||||
│ ├── apis.md
|
||||
│ ├── db-schema.md
|
||||
│ └── events-topics.md
|
||||
├── 02-<section-slug>/
|
||||
│ └── state-machine.md
|
||||
└── 03-<section-slug>/
|
||||
└── security-model.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -133,34 +149,22 @@ allowed-tools:
|
||||
|
||||
从 DDS 章节标题构建 `reference/` 分层目录:
|
||||
|
||||
```
|
||||
<skill-name>/reference/
|
||||
├── 01-<section-slug>/
|
||||
│ ├── apis.md
|
||||
│ ├── db-schema.md
|
||||
│ └── events-topics.md
|
||||
├── 02-<section-slug>/
|
||||
│ └── state-machine.md
|
||||
└── 03-<section-slug>/
|
||||
└── security-model.md
|
||||
```
|
||||
|
||||
**目录命名规范**:
|
||||
- 有序前缀 `01-`、`02-`... + slug
|
||||
- slug:全小写,非字母数字字符替换为 `-`,连续 `-` 合并,≤ 48 字符
|
||||
|
||||
### 2.2 六类设计要素抽取(必须覆盖)
|
||||
|
||||
每个模块级 Skill 的 reference/ 必须覆盖**至少 3 类**:
|
||||
唯一 Skill 的 reference/ 必须覆盖**至少 3 类**:
|
||||
|
||||
| 要素类型 | 抽取内容 | reference 文件名 |
|
||||
|---------|---------|-----------------|
|
||||
|---------|---------|-----------------|
|
||||
| **API/接口** | 路径、方法、请求/响应字段、错误码 | `apis.md` |
|
||||
| **事件/Topic** | 字段、版本、幂等键、重试语义 | `events-topics.md` |
|
||||
| **DB Schema** | 字段、索引、约束、迁移策略 | `db-schema.md` |
|
||||
| **状态机/流程** | 状态、转移、守卫条件、回调、补偿 | `state-machine.md` |
|
||||
| **授权模型** | JWT claims、RBAC/DAC、权限层级 | `security-model.md` |
|
||||
| **依赖关系** | 跨模块调用链路、协议、集成点 | `dependencies.md` |
|
||||
| **依赖关系** | 外部调用链路、协议、集成点 | `dependencies.md` |
|
||||
|
||||
### 2.3 reference 条目格式(强制)
|
||||
|
||||
@@ -186,17 +190,17 @@ allowed-tools:
|
||||
|
||||
---
|
||||
|
||||
## Phase 3:逐个生成 SKILL.md
|
||||
## Phase 3:生成 SKILL.md
|
||||
|
||||
### 3.1 SKILL.md 结构模板
|
||||
|
||||
> **详细模板见** `reference/skill-templates.md`
|
||||
|
||||
每个 SKILL.md 必须包含以下结构:
|
||||
唯一的 SKILL.md 必须包含以下结构:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: <skill-name>
|
||||
name: developing-<module>
|
||||
description: <单行,< 1024 字符,中英文混合,第三人称,含功能+触发场景+关键词>
|
||||
argument-hint: "<参数格式说明>"
|
||||
allowed-tools:
|
||||
@@ -208,9 +212,9 @@ allowed-tools:
|
||||
- Bash # 按需
|
||||
---
|
||||
|
||||
# <Skill 标题>
|
||||
# Developing <Module>
|
||||
|
||||
<一段话概述本 Skill 的用途和适用范围>
|
||||
<一段话描述模块职责、技术栈、适用范围>
|
||||
|
||||
## Quick Context
|
||||
<动态注入命令,至少 2 处 !`command`>
|
||||
@@ -220,13 +224,13 @@ allowed-tools:
|
||||
### 决策点
|
||||
|
||||
## Verify
|
||||
<按类别组织的 Checklist,可勾选>
|
||||
<按类别组织的全栈 Checklist:数据库→API→状态机→事件→安全>
|
||||
|
||||
## Execute
|
||||
<分步骤的可操作指令>
|
||||
<分步骤的全栈开发指令:数据库→API→状态机→事件→安全>
|
||||
|
||||
## Pitfalls
|
||||
<3~8 条与该模块/主题强相关的常见坑,至少 2 条引用 reference>
|
||||
<3~8 条与该模块强相关的常见坑,至少 2 条引用 reference>
|
||||
|
||||
## Related References
|
||||
<指向 reference/ 的链接列表,说明何时查阅>
|
||||
@@ -249,6 +253,7 @@ allowed-tools:
|
||||
3. **可执行动作**:禁止空话(如"检查 API 兼容"),必须写成具体审查动作
|
||||
4. **设计细节绑定**:Pitfalls 和 Verify 中至少 2 处引用 `reference/` 的具体内容
|
||||
5. **行数限制**:SKILL.md 主体 < 500 行
|
||||
6. **全栈覆盖**:Verify 和 Execute 必须覆盖数据库、API、状态机、事件等全栈层面
|
||||
|
||||
**示例 — 空话 vs 可执行动作**:
|
||||
|
||||
@@ -269,10 +274,10 @@ allowed-tools:
|
||||
|
||||
### 4.1 目录结构
|
||||
|
||||
每个 Skill 遵循标准目录模板:
|
||||
唯一 Skill 遵循标准目录模板:
|
||||
|
||||
```
|
||||
<skill-name>/
|
||||
developing-<module-name>/
|
||||
├── SKILL.md # 主文件(< 500 行)
|
||||
├── reference/ # 设计细节(按章节分层)
|
||||
│ ├── 01-<section>/
|
||||
@@ -289,7 +294,7 @@ allowed-tools:
|
||||
|
||||
### 4.2 verify.sh 编写要求
|
||||
|
||||
每个 Skill 必须至少包含 1 个 `verify.sh`:
|
||||
唯一 Skill 必须包含 1 个 `verify.sh`:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
@@ -341,9 +346,9 @@ echo "=== 结果: $PASS PASS / $FAIL FAIL ==="
|
||||
|
||||
### 5.1 输出顺序(必须遵守)
|
||||
|
||||
1. **Skills 清单表**:系统级 / 模块级 / 横切,含最终 name 与理由
|
||||
1. **Skill 信息表**:唯一 `developing-<module-name>` 的 name、职责概述、覆盖的设计要素类型
|
||||
2. **总目录树**:Unix 路径风格
|
||||
3. **每个 SKILL.md**:完整内容
|
||||
3. **SKILL.md**:完整内容
|
||||
4. **Supporting files**:按 `文件路径 → 文件内容` 逐个输出
|
||||
5. **全局自检结果**:逐条 PASS/FAIL + 修复建议
|
||||
|
||||
@@ -351,11 +356,11 @@ echo "=== 结果: $PASS PASS / $FAIL FAIL ==="
|
||||
|
||||
按以下维度逐条检查:
|
||||
|
||||
**结构完整性**
|
||||
- [ ] 系统级 Skill 存在(1 个)
|
||||
- [ ] 模块级 Skills 数量 = 模块数
|
||||
- [ ] 横切 Skills ≥ 3 个
|
||||
- [ ] 每个 Skill 都有 SKILL.md + reference/ + scripts/verify.sh
|
||||
**唯一收敛性(最高优先级)**
|
||||
- [ ] 仅存在 1 个 `developing-<module-name>` 目录
|
||||
- [ ] 不存在任何 `*-system` 系统级 Skill
|
||||
- [ ] 不存在任何 `managing-*` / `designing-*` / `implementing-*` 横切 Skill
|
||||
- [ ] 唯一 Skill 有 SKILL.md + reference/ + scripts/verify.sh
|
||||
|
||||
**Frontmatter 规范**
|
||||
- [ ] description 为单行
|
||||
@@ -370,9 +375,10 @@ echo "=== 结果: $PASS PASS / $FAIL FAIL ==="
|
||||
- [ ] ≥ 2 处 `!command` 动态注入
|
||||
- [ ] Pitfalls ≥ 2 条引用 reference
|
||||
- [ ] 无空话("检查 XX 一致性"这类无具体动作的描述)
|
||||
- [ ] Verify 和 Execute 覆盖全栈层面(数据库→API→状态机→事件)
|
||||
|
||||
**Reference 质量**
|
||||
- [ ] 每个模块 Skill 覆盖 ≥ 3 类设计要素
|
||||
- [ ] 覆盖 ≥ 3 类设计要素
|
||||
- [ ] reference 有章节分层目录(非扁平)
|
||||
- [ ] 每条 reference 含 DDS-Section + DDS-Lines 溯源
|
||||
- [ ] DDS 缺失内容标注 [TBD]
|
||||
@@ -385,7 +391,7 @@ echo "=== 结果: $PASS PASS / $FAIL FAIL ==="
|
||||
| 需要了解... | 查阅... |
|
||||
|------------|--------|
|
||||
| DDS 抽取的详细方法 | `reference/dds-extraction-guide.md` |
|
||||
| SKILL.md 模板(系统/模块/横切) | `reference/skill-templates.md` |
|
||||
| SKILL.md 模板(模块全栈开发指导书) | `reference/skill-templates.md` |
|
||||
| Frontmatter 详细规范 | `reference/frontmatter-spec.md` |
|
||||
| 质量自检的完整清单 | `reference/quality-checklist.md` |
|
||||
| 成功案例的目录结构 | `examples/` |
|
||||
|
||||
@@ -8,13 +8,13 @@ DDS-to-Skill 转换完成后,必须按以下清单逐条检查。每条标记
|
||||
|
||||
| # | 检查项 | PASS 条件 |
|
||||
|---|-------|----------|
|
||||
| S1 | 系统级 Skill 存在 | 恰好 1 个 `developing-*-system` Skill |
|
||||
| S2 | 模块级 Skills 数量 | = DDS 中识别的模块数 |
|
||||
| S3 | 横切 Skills 数量 | ≥ 3 个 |
|
||||
| S4 | 每个 Skill 有 SKILL.md | 所有 Skill 目录下存在 SKILL.md |
|
||||
| S5 | 每个 Skill 有 reference/ | 所有 Skill 目录下存在 reference/ |
|
||||
| S6 | 每个 Skill 有 verify.sh | 所有 Skill 的 scripts/ 下存在 verify.sh |
|
||||
| S7 | 目录命名规范 | 全小写、连字符、动名词形式 |
|
||||
| S1 | 唯一收敛 | 仅存在 1 个 `developing-<module-name>` 目录 |
|
||||
| S2 | 无越界系统级 Skill | 不存在任何 `*-system` 目录 |
|
||||
| S3 | 无越界横切 Skill | 不存在 `managing-*` / `designing-*` / `implementing-*` 等横切目录 |
|
||||
| S4 | Skill 有 SKILL.md | `developing-<module-name>` 目录下存在 SKILL.md |
|
||||
| S5 | Skill 有 reference/ | `developing-<module-name>` 目录下存在 reference/ |
|
||||
| S6 | Skill 有 verify.sh | `developing-<module-name>` 的 scripts/ 下存在 verify.sh |
|
||||
| S7 | 目录命名规范 | 全小写、连字符、`developing-` 前缀 |
|
||||
|
||||
---
|
||||
|
||||
@@ -27,9 +27,9 @@ DDS-to-Skill 转换完成后,必须按以下清单逐条检查。每条标记
|
||||
| F3 | description 中英文 | 同时包含中文和英文描述 |
|
||||
| F4 | description 含触发场景 | 包含 "触发场景" 或 "Trigger" 关键词 |
|
||||
| F5 | description 含关键词 | 包含 "关键词" 或 "Keywords" |
|
||||
| F6 | name 格式 | 小写字母 + 数字 + 连字符,动名词开头 |
|
||||
| F6 | name 格式 | 小写字母 + 数字 + 连字符,`developing-` 开头 |
|
||||
| F7 | argument-hint 存在 | frontmatter 中包含 argument-hint 字段 |
|
||||
| F8 | allowed-tools 最小授权 | 只读 Skill 不包含 Write/Edit |
|
||||
| F8 | allowed-tools 最小授权 | 工具列表按需最小化 |
|
||||
|
||||
---
|
||||
|
||||
@@ -45,8 +45,8 @@ DDS-to-Skill 转换完成后,必须按以下清单逐条检查。每条标记
|
||||
| C6 | 动态注入 | ≥ 2 处 `!` + 反引号命令 |
|
||||
| C7 | Pitfalls 引用 reference | ≥ 2 条 Pitfall 中出现 `reference/` 路径 |
|
||||
| C8 | 无空话 | 不含"检查 XX 一致性"这类无具体动作的描述 |
|
||||
| C9 | 无常识内容 | 不含 Claude 已知的通用知识(如 HTTP 状态码定义) |
|
||||
| C10 | 术语一致 | 同一概念在所有 Skill 中使用相同术语 |
|
||||
| C9 | 无常识内容 | 不含 AI 已知的通用知识(如 HTTP 状态码定义) |
|
||||
| C10 | 全栈覆盖 | Verify 和 Execute 至少覆盖数据库、API 两个层面 |
|
||||
|
||||
---
|
||||
|
||||
@@ -54,7 +54,7 @@ DDS-to-Skill 转换完成后,必须按以下清单逐条检查。每条标记
|
||||
|
||||
| # | 检查项 | PASS 条件 |
|
||||
|---|-------|----------|
|
||||
| R1 | 设计要素覆盖率 | 每个模块 Skill 覆盖 ≥ 3 类(API/事件/DB/状态机/权限/依赖) |
|
||||
| R1 | 设计要素覆盖率 | 覆盖 ≥ 3 类(API/事件/DB/状态机/权限/依赖) |
|
||||
| R2 | 章节分层 | reference/ 下存在 `01-*` 等编号目录(或使用扁平+说明) |
|
||||
| R3 | DDS 溯源 | 每条 reference 含 `DDS-Section:` 字段 |
|
||||
| R4 | DDS 行号 | 每条 reference 含 `DDS-Lines:` 字段 |
|
||||
@@ -64,51 +64,50 @@ DDS-to-Skill 转换完成后,必须按以下清单逐条检查。每条标记
|
||||
|
||||
---
|
||||
|
||||
## 5. 跨 Skill 一致性
|
||||
|
||||
| # | 检查项 | PASS 条件 |
|
||||
|---|-------|----------|
|
||||
| X1 | 模块名一致 | 所有 Skill 中模块名拼写相同 |
|
||||
| X2 | 错误码不冲突 | 相同错误码在不同 Skill 中含义相同 |
|
||||
| X3 | API 路径不冲突 | 不同模块的 API 路径无重叠 |
|
||||
| X4 | 事件/Topic 定义一致 | 同一 Topic 在发布方和订阅方 Skill 中定义相同 |
|
||||
| X5 | 授权模型一致 | JWT Claims、角色定义在所有 Skill 中一致 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 自检输出格式
|
||||
## 5. 自检输出格式
|
||||
|
||||
```markdown
|
||||
# 全局自检结果
|
||||
|
||||
## 结构完整性
|
||||
- ✅ S1 PASS: 系统级 Skill `developing-xxx-system` 存在
|
||||
- ✅ S2 PASS: 模块级 Skills 数量 = 5(匹配 DDS 中的 5 个模块)
|
||||
- ❌ S3 FAIL: 横切 Skills 仅 2 个,少于要求的 3 个
|
||||
- **修复**: 从 DDS 中识别出缓存策略章节,建议增加 `managing-cache` Skill
|
||||
- ✅ S4 PASS: 所有 Skill 目录下存在 SKILL.md
|
||||
## 结构完整性(唯一收敛性)
|
||||
- ✅ S1 PASS: 仅存在 1 个模块 Skill `developing-rmdc-feishu-operator`
|
||||
- ✅ S2 PASS: 不存在任何 `*-system` 系统级 Skill
|
||||
- ✅ S3 PASS: 不存在任何 `managing-*` / `designing-*` / `implementing-*` 横切 Skill
|
||||
- ✅ S4 PASS: SKILL.md 存在
|
||||
- ✅ S5 PASS: reference/ 目录存在
|
||||
- ✅ S6 PASS: scripts/verify.sh 存在
|
||||
- ✅ S7 PASS: 目录命名 `developing-rmdc-feishu-operator` 符合规范
|
||||
|
||||
## Frontmatter 规范
|
||||
- ✅ F1 PASS: 所有 description 为单行
|
||||
- ❌ F2 FAIL: `developing-core` 的 description 超过 1024 字符(1156 字符)
|
||||
- **修复**: 精简触发场景描述,移除重复关键词
|
||||
- ✅ F1 PASS: description 为单行
|
||||
- ✅ F2 PASS: description 长度 = 856 字符 (< 1024)
|
||||
- ✅ F3 PASS: description 同时包含中文和英文
|
||||
- ✅ F4 PASS: description 包含触发场景
|
||||
- ✅ F5 PASS: description 包含关键词
|
||||
|
||||
## 内容质量
|
||||
- ✅ C1 PASS: 所有 SKILL.md < 500 行
|
||||
- ❌ C8 FAIL: `developing-gateway` 中 Verify 包含"检查 API 一致性"
|
||||
- **修复**: 改为"对照 reference/02-api-design/apis.md 中的接口清单,grep 仓库中的 handler 注册点,确认路径和方法一致"
|
||||
- ✅ C1 PASS: SKILL.md = 380 行 (< 500)
|
||||
- ✅ C10 PASS: Verify 覆盖数据库、API、状态机、事件 4 个层面;Execute 覆盖 6 个步骤
|
||||
- ❌ C8 FAIL: Verify 中包含"检查通知一致性"
|
||||
- **修复**: 改为"对照 reference/03-notification/card-templates.md 中的模板列表,grep 仓库中的 SendCard 调用点,确认 template_id 一致"
|
||||
|
||||
## 总计: XX PASS / YY FAIL
|
||||
## Reference 质量
|
||||
- ✅ R1 PASS: 覆盖 5 类设计要素(API、事件、DB、状态机、依赖)
|
||||
- ✅ R3 PASS: 所有 reference 条目含 DDS-Section 溯源
|
||||
|
||||
## 总计: 14 PASS / 1 FAIL
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 常见 FAIL 及修复方案
|
||||
## 6. 常见 FAIL 及修复方案
|
||||
|
||||
| FAIL 类型 | 常见原因 | 修复方案 |
|
||||
|----------|---------|---------|
|
||||
|----------|---------|---------|
|
||||
| 越界生成多个 Skill | 习惯性拆分横切关注点 | 将横切内容收入唯一 Skill 的 reference/ 和 Execute 步骤 |
|
||||
| 生成了 *-system Skill | 沿用旧的三层架构模式 | 删除系统级 Skill,将跨模块依赖收入 Plan 决策点或 Pitfalls |
|
||||
| description 多行 | 使用了 `\|` 语法 | 改用 `>` 或单行字符串 |
|
||||
| reference 不足 | DDS 内容被遗漏 | 重新扫描 DDS,补充缺失要素 |
|
||||
| 空话 | 直接复制 DDS 原文 | 转化为可执行的审查动作 |
|
||||
| 脑补 | DDS 未提及的细节 | 标注 [TBD] 并列出补充清单 |
|
||||
| 横切不足 | 未充分分析 DDS | 从 DDS 中识别更多跨模块关注点 |
|
||||
| 全栈覆盖不足 | Verify/Execute 仅覆盖部分层面 | 根据 DDS 内容补充数据库/API/状态机/事件等层面 |
|
||||
|
||||
@@ -1,107 +1,18 @@
|
||||
# SKILL.md 模板库
|
||||
|
||||
本文档包含系统级 Skill、模块级 Skill、横切 Skill 的 SKILL.md 模板,供 DDS-to-Skill 转换时参照。
|
||||
本文档包含单模块全栈开发指导书的 SKILL.md 模板,供 DDS-to-Skill 转换时参照。
|
||||
|
||||
> **注意**:本框架仅生成唯一的 `developing-<module-name>` Skill,不生成系统级或横切级 Skill。
|
||||
|
||||
---
|
||||
|
||||
## 1. 系统级 Skill 模板
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: developing-<system>-system
|
||||
description: >
|
||||
指导 <系统名> 系统级开发决策与跨模块一致性(Guides system-level development for <system>)。
|
||||
包含:架构总览、模块注册、依赖规则、全局变更流程、版本兼容策略、技术栈规范。
|
||||
触发场景 Trigger: 新增模块 / 跨模块变更 / 全局架构决策 / 技术栈选型。
|
||||
关键词 Keywords: <system>, system, architecture, 架构, 模块, 依赖, 兼容, cross-module。
|
||||
argument-hint: "<module-name|change-type> - 指定涉及的模块名或变更类型"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Glob
|
||||
- Grep
|
||||
- Bash
|
||||
---
|
||||
|
||||
# Developing <System> System
|
||||
|
||||
<一段话描述系统整体架构、技术栈、模块组成>
|
||||
|
||||
## Quick Context
|
||||
|
||||
```bash
|
||||
# 动态注入:查看系统模块结构
|
||||
!`ls -la <project-root>/`
|
||||
|
||||
# 动态注入:搜索模块间依赖
|
||||
!`grep -rnE "import|module|service" <project-root>/ | head -30`
|
||||
```
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
<ASCII 架构图或层次说明>
|
||||
|
||||
## Module Registry
|
||||
|
||||
| 模块 | 职责 | 技术 | Skill |
|
||||
|------|------|------|-------|
|
||||
| ... | ... | ... | `developing-<module>` |
|
||||
|
||||
## Plan
|
||||
|
||||
### 产物清单
|
||||
- [ ] 确定变更涉及的模块列表
|
||||
- [ ] 确认是否涉及跨模块通信
|
||||
- [ ] 确认是否涉及契约变更
|
||||
- [ ] 确认是否需要数据库迁移
|
||||
|
||||
### 决策点
|
||||
1. 变更是否影响多个模块?
|
||||
2. 是否需要版本兼容处理?
|
||||
3. 是否需要全局配置变更?
|
||||
|
||||
## Verify
|
||||
|
||||
- [ ] 模块间依赖无循环
|
||||
- [ ] 共享契约版本一致
|
||||
- [ ] 全局配置项完整
|
||||
- [ ] 技术栈版本对齐
|
||||
|
||||
## Execute
|
||||
|
||||
### 添加新模块
|
||||
1. 在项目根目录创建模块目录...
|
||||
2. 注册到路由/网关...
|
||||
3. 更新模块依赖图...
|
||||
|
||||
### 跨模块变更
|
||||
1. 列出所有受影响模块...
|
||||
2. 按依赖顺序逐个修改...
|
||||
3. 运行集成测试...
|
||||
|
||||
## Pitfalls
|
||||
|
||||
1. **循环依赖**: 模块间禁止直接 import,必须通过共享接口定义
|
||||
2. **版本不一致**: 修改共享结构需同步更新所有消费方
|
||||
3. ...
|
||||
|
||||
## Related References
|
||||
|
||||
- [模块依赖关系](reference/dependencies.md)
|
||||
- [技术栈规范](reference/tech-stack.md)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 模块级 Skill 模板
|
||||
## 1. 模块全栈开发指导书模板
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: developing-<module>
|
||||
description: >
|
||||
指导 <module> 模块的开发(Guides development of <module> module)。
|
||||
包含:<模块职责概述>、API 实现、数据库操作、状态管理、安全校验。
|
||||
触发场景 Trigger: 开发/修改 <module> 相关功能 / <模块特定场景>。
|
||||
关键词 Keywords: <module>, <技术关键词>, <业务关键词>。
|
||||
<module> 系统全栈开发指导 Skill(Full-stack development guide for <module>)。涵盖 <技术栈概述>、<核心数据模型概述>、<核心业务流程概述>、<关键集成点概述>。触发场景 Trigger: 开发/修改/调试 <module> 任意模块时、审查代码变更时、排查问题时。关键词 Keywords: <模块名>, <技术关键词>, <业务关键词>。
|
||||
argument-hint: "<action> <target> - e.g., 'create handler', 'add api', 'update schema'"
|
||||
allowed-tools:
|
||||
- Read
|
||||
@@ -114,7 +25,7 @@ allowed-tools:
|
||||
|
||||
# Developing <Module>
|
||||
|
||||
<一段话描述模块职责、技术栈、在系统中的位置>
|
||||
<一段话描述模块的整体职责、核心技术栈、适用范围。强调这是该模块的唯一全栈开发向导,涵盖数据库、API、状态流转、事件处理等所有开发层面。>
|
||||
|
||||
## Quick Context
|
||||
|
||||
@@ -124,132 +35,137 @@ allowed-tools:
|
||||
|
||||
# 动态注入:查看现有接口
|
||||
!`grep -rn "func.*Handler\|func.*Service" ./<module>/ | head -20`
|
||||
|
||||
# 动态注入:查看数据库相关文件
|
||||
!`find . -name "*.sql" -o -name "*migration*" -o -name "*model*" | head -20`
|
||||
```
|
||||
|
||||
## Plan
|
||||
|
||||
### 产物清单
|
||||
- [ ] <根据 DDS 列出具体产物>
|
||||
- [ ] <根据 DDS 列出具体产物:数据库模型、API handler、状态机实现等>
|
||||
- [ ] <每一项都应对应 reference/ 中的设计细节>
|
||||
|
||||
### 决策点
|
||||
1. <从 DDS 抽取的关键决策>
|
||||
1. <从 DDS 抽取的关键决策,如技术选型、架构权衡>
|
||||
2. ...
|
||||
|
||||
## Verify
|
||||
|
||||
### <验证类别 1>
|
||||
- [ ] <具体检查项,引用 reference>
|
||||
> 全栈验证按以下顺序依次进行,确保底层就绪后再构建上层。
|
||||
|
||||
### <验证类别 2>
|
||||
- [ ] <具体检查项>
|
||||
### 数据层验证
|
||||
- [ ] 数据库表结构与 `reference/<section>/db-schema.md` 一致
|
||||
- [ ] 索引、约束、外键按设计实现
|
||||
- [ ] 迁移脚本可回滚(包含 down 语句或回滚段落)
|
||||
|
||||
### API 层验证
|
||||
- [ ] API 路径、方法与 `reference/<section>/apis.md` 定义一致
|
||||
- [ ] 请求/响应字段与设计文档吻合
|
||||
- [ ] 错误码覆盖文档中定义的所有异常场景
|
||||
|
||||
### 状态机/业务流程验证
|
||||
- [ ] 状态枚举与 `reference/<section>/state-machine.md` 中的状态集一致
|
||||
- [ ] 状态转移条件(守卫)按设计实现,无非法跳转
|
||||
- [ ] 回调/补偿逻辑在异常路径上正确触发
|
||||
|
||||
### 事件/消息验证
|
||||
- [ ] Topic 命名与 `reference/<section>/events-topics.md` 一致
|
||||
- [ ] Payload 字段完整且版本正确
|
||||
- [ ] 幂等键生成逻辑正确,消费端实现去重
|
||||
|
||||
### 安全/授权验证(如适用)
|
||||
- [ ] 授权模型与 `reference/<section>/security-model.md` 一致
|
||||
- [ ] 敏感操作有权限校验
|
||||
|
||||
## Execute
|
||||
|
||||
### 1. <步骤标题>
|
||||
```bash
|
||||
# 具体操作命令
|
||||
> 全栈开发按以下顺序推进,先夯实基础再构建业务。
|
||||
|
||||
### Step 1:数据库 Schema 实现
|
||||
1. 根据 `reference/<section>/db-schema.md` 创建数据库模型/实体
|
||||
2. 编写迁移脚本(必须包含 up 和 down)
|
||||
3. 创建索引和约束
|
||||
```go
|
||||
// 关键模型骨架(从 reference 中抽取)
|
||||
```
|
||||
|
||||
### 2. <步骤标题>
|
||||
### Step 2:核心 API / Handler 实现
|
||||
1. 根据 `reference/<section>/apis.md` 注册路由
|
||||
2. 实现请求校验与响应序列化
|
||||
3. 实现错误码映射
|
||||
```go
|
||||
// 关键代码骨架
|
||||
// 关键 handler 骨架
|
||||
```
|
||||
|
||||
### Step 3:状态机 / 业务流程实现
|
||||
1. 根据 `reference/<section>/state-machine.md` 定义状态枚举与转移表
|
||||
2. 实现守卫条件与副作用
|
||||
3. 实现异常路径的回调/补偿
|
||||
```go
|
||||
// 关键状态转移骨架
|
||||
```
|
||||
|
||||
### Step 4:事件/消息处理实现
|
||||
1. 根据 `reference/<section>/events-topics.md` 注册事件处理器
|
||||
2. 实现发布端 payload 构建
|
||||
3. 实现消费端幂等处理
|
||||
```go
|
||||
// 关键事件处理骨架
|
||||
```
|
||||
|
||||
### Step 5:安全/授权实现(如适用)
|
||||
1. 根据 `reference/<section>/security-model.md` 实现授权中间件
|
||||
2. 配置权限检查
|
||||
|
||||
### Step 6:可观测性与监控(如适用)
|
||||
1. 接入日志、指标、追踪
|
||||
2. 配置关键业务指标告警
|
||||
|
||||
## Pitfalls
|
||||
|
||||
1. **<坑名>**: <描述>(参考 `reference/<file>.md`)
|
||||
2. ...(至少 3 条,至少 2 条引用 reference)
|
||||
2. **<坑名>**: <描述>(参考 `reference/<file>.md`)
|
||||
3. ...(至少 3 条,至少 2 条引用 reference)
|
||||
|
||||
## Related References
|
||||
|
||||
- [API 定义](reference/01-<section>/apis.md)
|
||||
- [数据库 Schema](reference/02-<section>/db-schema.md)
|
||||
- [数据模型定义](reference/01-<section>/db-schema.md) — 修改表结构时查阅
|
||||
- [API 接口定义](reference/01-<section>/apis.md) — 新增/修改接口时查阅
|
||||
- [状态机定义](reference/02-<section>/state-machine.md) — 修改业务流程时查阅
|
||||
- [事件/Topic 定义](reference/02-<section>/events-topics.md) — 修改事件处理时查阅
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 横切 Skill 模板
|
||||
## 2. 模板使用注意事项
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: <crosscut-skill-name>
|
||||
description: >
|
||||
<横切关注点>的统一规范与实现指导(Guides <crosscut concern> across all modules)。
|
||||
包含:<具体内容列表>。
|
||||
触发场景 Trigger: <触发场景列表>。
|
||||
关键词 Keywords: <关键词列表>。
|
||||
argument-hint: "<module-name|file-path> - 指定要应用规范的模块或文件"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Glob
|
||||
- Grep
|
||||
- Bash
|
||||
---
|
||||
|
||||
# <横切 Skill 标题>
|
||||
|
||||
<描述这个横切关注点在系统中的重要性和适用范围>
|
||||
|
||||
## Quick Context
|
||||
|
||||
```bash
|
||||
# 动态注入
|
||||
!`<扫描所有模块中与该横切主题相关的文件>`
|
||||
```
|
||||
|
||||
## Plan
|
||||
|
||||
### 产物清单
|
||||
- [ ] <横切维度的产物>
|
||||
|
||||
### 决策点
|
||||
1. <跨模块的统一决策>
|
||||
2. ...
|
||||
|
||||
## Verify
|
||||
|
||||
- [ ] <跨模块一致性检查>
|
||||
- [ ] <规范合规检查>
|
||||
- [ ] ...
|
||||
|
||||
## Execute
|
||||
|
||||
### 全局规范
|
||||
<适用于所有模块的规则>
|
||||
|
||||
### 模块适配
|
||||
<各模块的特殊处理>
|
||||
|
||||
## Pitfalls
|
||||
|
||||
1. **<跨模块一致性问题>**: <描述>
|
||||
2. ...
|
||||
|
||||
## Related References
|
||||
|
||||
- [全局规范定义](reference/<global-spec>.md)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 模板使用注意事项
|
||||
|
||||
### 4.1 必须自定义的部分
|
||||
### 2.1 必须自定义的部分
|
||||
|
||||
- `<尖括号>` 中的所有占位符
|
||||
- Plan 的产物清单和决策点必须来自 DDS
|
||||
- Verify 的检查项必须与模块设计细节对应
|
||||
- Pitfalls 必须与模块/主题强相关,不可用通用建议填充
|
||||
- Execute 的步骤必须绑定 reference/ 中的具体设计文档
|
||||
- Pitfalls 必须与模块强相关,不可用通用建议填充
|
||||
|
||||
### 4.2 禁止照搬模板
|
||||
### 2.2 禁止照搬模板
|
||||
|
||||
模板是结构参考,不是内容来源。以下行为将导致自检 FAIL:
|
||||
- 产物清单中出现模板占位符
|
||||
- Pitfalls 与模块无关(如:在前端 Skill 中出现数据库 Pitfall)
|
||||
- Verify 中没有引用任何 reference
|
||||
- Pitfalls 与模块无关
|
||||
- Verify/Execute 中没有引用任何 reference
|
||||
- Execute 步骤缺失具体的代码骨架或命令
|
||||
|
||||
### 4.3 按 DDS 内容增减
|
||||
### 2.3 按 DDS 内容增减
|
||||
|
||||
- 如果 DDS 中没有状态机,模块 Skill 可以不包含状态机相关 Verify
|
||||
- 如果 DDS 中有额外的关注点(如性能优化、缓存策略),应增加对应章节
|
||||
- 横切 Skill 的数量和主题必须由 DDS 内容决定
|
||||
- 如果 DDS 中没有状态机,可以省略 Step 3 和状态机验证
|
||||
- 如果 DDS 中没有事件系统,可以省略 Step 4 和事件验证
|
||||
- 如果 DDS 中有额外的关注点(如缓存策略、性能优化),应在 Verify 和 Execute 中增加对应章节
|
||||
- Verify 和 Execute 的分层顺序可根据 DDS 中的技术架构调整,但必须保持"先基础后上层"的原则
|
||||
|
||||
### 2.4 全栈覆盖要求
|
||||
|
||||
作为唯一的开发指导书,必须确保:
|
||||
- 所有原本属于"横切 Skill"(如数据库迁移、安全认证、可观测性)的知识都内嵌到 Verify/Execute 的对应步骤中
|
||||
- 原本属于"系统级 Skill"(如跨模块依赖、全局配置)的知识收入 Plan 的决策点或 Pitfalls 中
|
||||
- 所有设计细节的完整数据存放在 `reference/` 下,SKILL.md 中仅以引用方式指向
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
#!/bin/bash
|
||||
# verify-skill-output.sh
|
||||
# 验证 DDS-to-Skill 转换输出的完整性和质量
|
||||
# 验证 DDS-to-Skill 转换输出的完整性和质量(单模块 All-in-One 模式)
|
||||
#
|
||||
# 用法:./verify-skill-output.sh <skills-output-dir>
|
||||
# 示例:./verify-skill-output.sh /path/to/1-AgentSkills
|
||||
#
|
||||
# 校验规则:
|
||||
# - 仅允许存在 1 个 developing-* 目录
|
||||
# - 不允许存在 *-system、managing-*、designing-*、implementing-* 等越界 Skill
|
||||
#
|
||||
# 依赖:bash, grep, sed, find, wc
|
||||
|
||||
set -e
|
||||
@@ -37,26 +41,56 @@ warn() {
|
||||
}
|
||||
|
||||
echo "============================================"
|
||||
echo " DDS-to-Skill 输出质量验证"
|
||||
echo " DDS-to-Skill 输出质量验证(单模块模式)"
|
||||
echo " 目标目录: $SKILLS_DIR"
|
||||
echo "============================================"
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 1. 结构完整性检查
|
||||
# 1. 唯一收敛性检查(最高优先级)
|
||||
# ============================================
|
||||
echo "--- 1. 结构完整性 ---"
|
||||
echo "--- 1. 唯一收敛性 ---"
|
||||
|
||||
# S1: 检查是否有系统级 Skill
|
||||
SYSTEM_SKILLS=$(find "$SKILLS_DIR" -maxdepth 1 -type d -name "*-system*" 2>/dev/null | wc -l)
|
||||
if [ "$SYSTEM_SKILLS" -ge 1 ]; then
|
||||
pass "S1: 存在系统级 Skill ($SYSTEM_SKILLS 个)"
|
||||
# S1: 确认 developing-* 目录数量恰好为 1
|
||||
DEV_SKILLS=$(find "$SKILLS_DIR" -maxdepth 1 -type d -name "developing-*" 2>/dev/null | wc -l)
|
||||
if [ "$DEV_SKILLS" -eq 1 ]; then
|
||||
DEV_SKILL_NAME=$(find "$SKILLS_DIR" -maxdepth 1 -type d -name "developing-*" -exec basename {} \;)
|
||||
pass "S1: 恰好存在 1 个模块 Skill: $DEV_SKILL_NAME"
|
||||
elif [ "$DEV_SKILLS" -eq 0 ]; then
|
||||
fail "S1: 未找到任何 developing-* 目录" "确认输出目录正确,并确保生成了 developing-<module-name> Skill"
|
||||
else
|
||||
warn "S1: 未找到系统级 Skill(名称包含 '-system')"
|
||||
fail "S1: 发现 $DEV_SKILLS 个 developing-* 目录(应为 1 个)" "合并或删除多余的 developing-* 目录,仅保留 1 个"
|
||||
fi
|
||||
|
||||
# S4: 每个 Skill 都有 SKILL.md
|
||||
SKILL_DIRS=$(find "$SKILLS_DIR" -maxdepth 1 -type d ! -name "$(basename "$SKILLS_DIR")" 2>/dev/null)
|
||||
# S2: 反向校验 — 不允许存在系统级 Skill
|
||||
SYSTEM_SKILLS=$(find "$SKILLS_DIR" -maxdepth 1 -type d -name "*-system*" 2>/dev/null | wc -l)
|
||||
if [ "$SYSTEM_SKILLS" -eq 0 ]; then
|
||||
pass "S2: 不存在越界的系统级 Skill (*-system)"
|
||||
else
|
||||
SYSTEM_NAMES=$(find "$SKILLS_DIR" -maxdepth 1 -type d -name "*-system*" -exec basename {} \; | tr '\n' ', ')
|
||||
fail "S2: 发现 $SYSTEM_SKILLS 个越界的系统级 Skill: $SYSTEM_NAMES" "删除系统级 Skill,将跨模块内容收入唯一的 developing-* Skill"
|
||||
fi
|
||||
|
||||
# S3: 反向校验 — 不允许存在横切 Skill (managing-*, designing-*, implementing-*)
|
||||
CROSSCUT_SKILLS=$(find "$SKILLS_DIR" -maxdepth 1 -type d \( -name "managing-*" -o -name "designing-*" -o -name "implementing-*" \) 2>/dev/null | wc -l)
|
||||
if [ "$CROSSCUT_SKILLS" -eq 0 ]; then
|
||||
pass "S3: 不存在越界的横切 Skill (managing-*/designing-*/implementing-*)"
|
||||
else
|
||||
CROSSCUT_NAMES=$(find "$SKILLS_DIR" -maxdepth 1 -type d \( -name "managing-*" -o -name "designing-*" -o -name "implementing-*" \) -exec basename {} \; | tr '\n' ', ')
|
||||
fail "S3: 发现 $CROSSCUT_SKILLS 个越界的横切 Skill: $CROSSCUT_NAMES" "删除横切 Skill,将其内容收入唯一的 developing-* Skill 的 reference/ 和 Execute 中"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 2. 结构完整性检查
|
||||
# ============================================
|
||||
echo "--- 2. 结构完整性 ---"
|
||||
|
||||
# 获取唯一的 developing-* 目录
|
||||
SKILL_DIRS=$(find "$SKILLS_DIR" -maxdepth 1 -type d -name "developing-*" 2>/dev/null)
|
||||
|
||||
# S4: Skill 有 SKILL.md
|
||||
MISSING_SKILLMD=0
|
||||
for dir in $SKILL_DIRS; do
|
||||
if [ ! -f "$dir/SKILL.md" ]; then
|
||||
@@ -64,28 +98,40 @@ for dir in $SKILL_DIRS; do
|
||||
((MISSING_SKILLMD++))
|
||||
fi
|
||||
done
|
||||
if [ "$MISSING_SKILLMD" -eq 0 ]; then
|
||||
pass "S4: 所有 Skill 目录都有 SKILL.md"
|
||||
if [ "$MISSING_SKILLMD" -eq 0 ] && [ -n "$SKILL_DIRS" ]; then
|
||||
pass "S4: Skill 目录有 SKILL.md"
|
||||
fi
|
||||
|
||||
# S5: 每个 Skill 都有 reference/
|
||||
# S5: Skill 有 reference/
|
||||
MISSING_REF=0
|
||||
for dir in $SKILL_DIRS; do
|
||||
if [ ! -d "$dir/reference" ]; then
|
||||
warn "S5: $dir 缺少 reference/ 目录"
|
||||
fail "S5: $dir 缺少 reference/ 目录" "创建 reference/ 并按 DDS 章节填充设计细节"
|
||||
((MISSING_REF++))
|
||||
fi
|
||||
done
|
||||
if [ "$MISSING_REF" -eq 0 ]; then
|
||||
pass "S5: 所有 Skill 目录都有 reference/"
|
||||
if [ "$MISSING_REF" -eq 0 ] && [ -n "$SKILL_DIRS" ]; then
|
||||
pass "S5: Skill 目录有 reference/"
|
||||
fi
|
||||
|
||||
# S6: Skill 有 scripts/verify.sh
|
||||
MISSING_VERIFY=0
|
||||
for dir in $SKILL_DIRS; do
|
||||
if [ ! -f "$dir/scripts/verify.sh" ]; then
|
||||
fail "S6: $dir 缺少 scripts/verify.sh" "创建 scripts/verify.sh 验证脚本"
|
||||
((MISSING_VERIFY++))
|
||||
fi
|
||||
done
|
||||
if [ "$MISSING_VERIFY" -eq 0 ] && [ -n "$SKILL_DIRS" ]; then
|
||||
pass "S6: Skill 目录有 scripts/verify.sh"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 2. Frontmatter 规范检查
|
||||
# 3. Frontmatter 规范检查
|
||||
# ============================================
|
||||
echo "--- 2. Frontmatter 规范 ---"
|
||||
echo "--- 3. Frontmatter 规范 ---"
|
||||
|
||||
for dir in $SKILL_DIRS; do
|
||||
SKILL_FILE="$dir/SKILL.md"
|
||||
@@ -106,6 +152,14 @@ for dir in $SKILL_DIRS; do
|
||||
fail "F2 [$SKILL_NAME]: 缺少 description 字段" "在 frontmatter 中添加 description 字段"
|
||||
fi
|
||||
|
||||
# F6: name 以 developing- 开头
|
||||
SKILL_NAME_VALUE=$(head -20 "$SKILL_FILE" | grep '^name:' | sed 's/^name:[[:space:]]*//')
|
||||
if echo "$SKILL_NAME_VALUE" | grep -q '^developing-'; then
|
||||
pass "F6 [$SKILL_NAME]: name 以 developing- 开头"
|
||||
else
|
||||
fail "F6 [$SKILL_NAME]: name 不以 developing- 开头 (当前: $SKILL_NAME_VALUE)" "修改 name 为 developing-<module-name> 格式"
|
||||
fi
|
||||
|
||||
# C1: 行数 < 500
|
||||
LINE_COUNT=$(wc -l < "$SKILL_FILE")
|
||||
if [ "$LINE_COUNT" -lt 500 ]; then
|
||||
@@ -118,9 +172,9 @@ done
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 3. 内容质量检查
|
||||
# 4. 内容质量检查
|
||||
# ============================================
|
||||
echo "--- 3. 内容质量 ---"
|
||||
echo "--- 4. 内容质量 ---"
|
||||
|
||||
for dir in $SKILL_DIRS; do
|
||||
SKILL_FILE="$dir/SKILL.md"
|
||||
@@ -156,9 +210,9 @@ done
|
||||
echo ""
|
||||
|
||||
# ============================================
|
||||
# 4. Reference 质量检查
|
||||
# 5. Reference 质量检查
|
||||
# ============================================
|
||||
echo "--- 4. Reference 质量 ---"
|
||||
echo "--- 5. Reference 质量 ---"
|
||||
|
||||
for dir in $SKILL_DIRS; do
|
||||
[ ! -d "$dir/reference" ] && continue
|
||||
@@ -201,7 +255,7 @@ echo ""
|
||||
# 总结
|
||||
# ============================================
|
||||
echo "============================================"
|
||||
echo " 验证完成"
|
||||
echo " 验证完成(单模块 All-in-One 模式)"
|
||||
echo " ✅ PASS: $PASS"
|
||||
echo " ❌ FAIL: $FAIL"
|
||||
echo " ⚠️ WARN: $WARN"
|
||||
|
||||
16
.idea/csv-editor.xml
generated
Normal file
16
.idea/csv-editor.xml
generated
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CsvFileAttributes">
|
||||
<option name="attributeMap">
|
||||
<map>
|
||||
<entry key="\18-基础架构及交付部署特战队\10-飞书多维表格\offline-docs-v2\manifest.csv">
|
||||
<value>
|
||||
<Attribute>
|
||||
<option name="separator" value="," />
|
||||
</Attribute>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
15
.idea/git_toolbox_prj.xml
generated
Normal file
15
.idea/git_toolbox_prj.xml
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GitToolBoxProjectSettings">
|
||||
<option name="commitMessageIssueKeyValidationOverride">
|
||||
<BoolValueOverride>
|
||||
<option name="enabled" value="true" />
|
||||
</BoolValueOverride>
|
||||
</option>
|
||||
<option name="commitMessageValidationEnabledOverride">
|
||||
<BoolValueOverride>
|
||||
<option name="enabled" value="true" />
|
||||
</BoolValueOverride>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
10
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
10
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="ImplicitTypeConversion" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="BITS" value="1720" />
|
||||
<option name="FLAG_EXPLICIT_CONVERSION" value="true" />
|
||||
<option name="IGNORE_NODESET_TO_BOOLEAN_VIA_STRING" value="true" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
||||
2
.idea/vcs.xml
generated
2
.idea/vcs.xml
generated
@@ -2,5 +2,7 @@
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/18-基础架构及交付部署特战队/10-飞书多维表格/offline-docs-v2/go-sdk-examples/oapi-sdk-go" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/18-基础架构及交付部署特战队/10-飞书多维表格/offline-docs-v2/go-sdk-examples/oapi-sdk-go-demo" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
67
1-AgentSkills/andrej-karpathy-skills/SKILL.md
Normal file
67
1-AgentSkills/andrej-karpathy-skills/SKILL.md
Normal file
@@ -0,0 +1,67 @@
|
||||
---
|
||||
name: karpathy-guidelines
|
||||
description: Behavioral guidelines to reduce common LLM coding mistakes. Use when writing, reviewing, or refactoring code to avoid overcomplication, make surgical changes, surface assumptions, and define verifiable success criteria.
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# Karpathy Guidelines
|
||||
|
||||
Behavioral guidelines to reduce common LLM coding mistakes, derived from [Andrej Karpathy's observations](https://x.com/karpathy/status/2015883857489522876) on LLM coding pitfalls.
|
||||
|
||||
**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.
|
||||
|
||||
## 1. Think Before Coding
|
||||
|
||||
**Don't assume. Don't hide confusion. Surface tradeoffs.**
|
||||
|
||||
Before implementing:
|
||||
- State your assumptions explicitly. If uncertain, ask.
|
||||
- If multiple interpretations exist, present them - don't pick silently.
|
||||
- If a simpler approach exists, say so. Push back when warranted.
|
||||
- If something is unclear, stop. Name what's confusing. Ask.
|
||||
|
||||
## 2. Simplicity First
|
||||
|
||||
**Minimum code that solves the problem. Nothing speculative.**
|
||||
|
||||
- No features beyond what was asked.
|
||||
- No abstractions for single-use code.
|
||||
- No "flexibility" or "configurability" that wasn't requested.
|
||||
- No error handling for impossible scenarios.
|
||||
- If you write 200 lines and it could be 50, rewrite it.
|
||||
|
||||
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
|
||||
|
||||
## 3. Surgical Changes
|
||||
|
||||
**Touch only what you must. Clean up only your own mess.**
|
||||
|
||||
When editing existing code:
|
||||
- Don't "improve" adjacent code, comments, or formatting.
|
||||
- Don't refactor things that aren't broken.
|
||||
- Match existing style, even if you'd do it differently.
|
||||
- If you notice unrelated dead code, mention it - don't delete it.
|
||||
|
||||
When your changes create orphans:
|
||||
- Remove imports/variables/functions that YOUR changes made unused.
|
||||
- Don't remove pre-existing dead code unless asked.
|
||||
|
||||
The test: Every changed line should trace directly to the user's request.
|
||||
|
||||
## 4. Goal-Driven Execution
|
||||
|
||||
**Define success criteria. Loop until verified.**
|
||||
|
||||
Transform tasks into verifiable goals:
|
||||
- "Add validation" → "Write tests for invalid inputs, then make them pass"
|
||||
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
|
||||
- "Refactor X" → "Ensure tests pass before and after"
|
||||
|
||||
For multi-step tasks, state a brief plan:
|
||||
```
|
||||
1. [Step] → verify: [check]
|
||||
2. [Step] → verify: [check]
|
||||
3. [Step] → verify: [check]
|
||||
```
|
||||
|
||||
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
|
||||
250
1-AgentSkills/doc-sync-skill/SKILL.md
Normal file
250
1-AgentSkills/doc-sync-skill/SKILL.md
Normal file
@@ -0,0 +1,250 @@
|
||||
---
|
||||
name: doc-sync-skill
|
||||
description: >
|
||||
当 PRD 文档版本更新时,自动分析两版 PRD 差异并生成 Change Intent,驱动 DDS 与 AgentSkill 的增量同步更新,最终归档版本矩阵。
|
||||
Automatically analyzes PRD version diffs, generates structured Change Intent, drives incremental DDS and AgentSkill updates, and archives version matrix.
|
||||
触发场景 Trigger: 当用户提供了新旧两版 PRD 文档并需要同步更新 DDS 和 Skill / 需要执行文档级联变更 / 需要生成版本归档矩阵。
|
||||
关键词 Keywords: PRD, DDS, diff, 增量更新, incremental update, change intent, version matrix, skill sync, 文档同步, doc sync, 版本管理。
|
||||
version: 1.0.0
|
||||
author: wdd
|
||||
argument-hint: "--old <old-prd-path> --new <new-prd-path> [--dds <current-dds-path>] [--skills-dir <skills-directory>]"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
- Edit
|
||||
- Glob
|
||||
- Grep
|
||||
- Bash
|
||||
---
|
||||
|
||||
# Doc-Sync-Skill:PRD 增量变更驱动的文档级联同步
|
||||
|
||||
本 Skill 用于在软件开发流程中,当 PRD(产品需求文档)从旧版本更新到新版本时,自动完成以下四个阶段的级联同步:
|
||||
|
||||
1. **Phase 1 — PRD Diff 分析**:对比新旧 PRD,生成结构化 Change Intent
|
||||
2. **Phase 2 — DDS 增量更新**:基于 Change Intent 驱动 DDS 只改受影响章节
|
||||
3. **Phase 3 — AgentSkill 增量更新**:基于 DDS 变更同步更新相关 SKILL.md
|
||||
4. **Phase 4 — 版本归档**:生成 VERSION_MATRIX.md 追踪所有文档版本关系
|
||||
|
||||
> **核心原则**:
|
||||
> - **最小变更原则** — 只修改受 PRD 变更影响的章节,未提及部分绝对不动
|
||||
> - **溯源可追踪** — 每一处变更都能追溯到具体的 PRD 变更条目
|
||||
> - **幂等安全** — 相同输入多次执行应产生相同结果
|
||||
|
||||
---
|
||||
|
||||
## 前置条件
|
||||
|
||||
执行前必须确认以下文件存在:
|
||||
|
||||
| 文件 | 必需 | 用途 |
|
||||
|------|------|------|
|
||||
| 旧版 PRD(`--old`) | ✅ | PRD diff 基准 |
|
||||
| 新版 PRD(`--new`) | ✅ | PRD diff 目标 |
|
||||
| 当前 DDS(`--dds`) | ⚠️ Phase 2 必需 | DDS 增量更新的基础 |
|
||||
| Skills 目录(`--skills-dir`) | ⚠️ Phase 3 必需 | Skill 增量更新的目标目录 |
|
||||
|
||||
### 文件不存在时的降级策略
|
||||
|
||||
- **旧版 PRD 不存在**:终止执行,提示用户提供旧版 PRD 路径
|
||||
- **新版 PRD 不存在**:终止执行,提示用户提供新版 PRD 路径
|
||||
- **DDS 不存在**:跳过 Phase 2,在 Phase 4 记录"DDS 待创建"
|
||||
- **Skills 目录不存在**:跳过 Phase 3,在 Phase 4 记录"Skills 待创建"
|
||||
|
||||
---
|
||||
|
||||
## Phase 1:PRD Diff 分析 → 生成 Change Intent
|
||||
|
||||
### 1.1 执行 PRD 差异对比脚本
|
||||
|
||||
使用 `scripts/diff_prd.py` 对新旧两版 PRD 进行章节级对比:
|
||||
|
||||
```bash
|
||||
# 运行 PRD 差异对比
|
||||
python3 "$(dirname "$0")/scripts/diff_prd.py" --old "$OLD_PRD_PATH" --new "$NEW_PRD_PATH"
|
||||
```
|
||||
|
||||
该脚本输出结构化 JSON,包含:
|
||||
- `added_sections`:新增章节列表
|
||||
- `modified_sections`:修改章节列表(含 old/new 对比)
|
||||
- `removed_sections`:删除章节列表
|
||||
- `summary`:变更摘要
|
||||
|
||||
### 1.2 生成 Change Intent 文档
|
||||
|
||||
基于脚本输出的 JSON,填充 `references/CHANGE_INTENT_TEMPLATE.md` 模板:
|
||||
|
||||
1. 读取模板:`references/CHANGE_INTENT_TEMPLATE.md`
|
||||
2. 根据 JSON 结果填充所有字段
|
||||
3. 输出完成的 Change Intent 文档到工作目录
|
||||
|
||||
**填充规则**:
|
||||
- `变更版本`:从新版 PRD 的文档头提取版本号
|
||||
- `变更日期`:使用当前日期
|
||||
- `新增需求列表`:对应 `added_sections`
|
||||
- `修改需求列表`:对应 `modified_sections`
|
||||
- `废弃需求列表`:对应 `removed_sections`
|
||||
- `影响模块列表`:从变更章节中提取涉及的模块/组件名称
|
||||
- `变更摘要`:使用 JSON 中的 `summary` 字段,必要时补充人工判断
|
||||
|
||||
### 1.3 Change Intent 质量门禁
|
||||
|
||||
生成的 Change Intent 必须通过以下检查:
|
||||
- [ ] 所有字段非空(或显式标注"无")
|
||||
- [ ] 影响模块列表至少 1 项(纯文案修改除外)
|
||||
- [ ] 变更摘要不超过 500 字
|
||||
- [ ] 新增/修改/废弃列表与 diff 脚本输出一致
|
||||
|
||||
---
|
||||
|
||||
## Phase 2:DDS 增量更新
|
||||
|
||||
### 2.1 准备 DDS 更新 Prompt
|
||||
|
||||
将 Change Intent 和当前 DDS 内容注入 `references/DDS_UPDATE_PROMPT.md`:
|
||||
|
||||
1. 读取当前 DDS 全文
|
||||
2. 读取 Phase 1 生成的 Change Intent
|
||||
3. 将两者填入 `references/DDS_UPDATE_PROMPT.md` 的对应占位符
|
||||
|
||||
**变量替换**:
|
||||
- `{{CHANGE_INTENT}}` → 完整的 Change Intent 文档内容
|
||||
- `{{CURRENT_DDS}}` → 当前 DDS 全文
|
||||
|
||||
### 2.2 执行 DDS 更新
|
||||
|
||||
将准备好的 Prompt 发送给大模型,获得 DDS 增量更新内容。
|
||||
|
||||
**输出格式要求**(在 Prompt 中已约束):
|
||||
- `[NEW]` 标注新增章节
|
||||
- `[MODIFIED]` 标注修改章节,含 diff 对比
|
||||
- `[DEPRECATED]` 标注废弃章节
|
||||
- 未提及的章节**绝对不修改**
|
||||
|
||||
### 2.3 应用 DDS 变更
|
||||
|
||||
将大模型返回的增量更新逐章节应用到 DDS 文件:
|
||||
|
||||
1. 对 `[NEW]` 章节:插入到 DDS 合适位置
|
||||
2. 对 `[MODIFIED]` 章节:替换对应章节内容
|
||||
3. 对 `[DEPRECATED]` 章节:标注废弃标记(保留原文,添加删除线或注释)
|
||||
4. 更新 DDS 文档头的版本号(按 Prompt 建议)
|
||||
|
||||
### 2.4 DDS 变更验证
|
||||
|
||||
- [ ] 只有 Change Intent 中涉及的章节被修改
|
||||
- [ ] 未涉及章节内容与原文完全一致
|
||||
- [ ] 文档版本号已更新
|
||||
- [ ] 无语法错误(Markdown lint 通过)
|
||||
|
||||
---
|
||||
|
||||
## Phase 3:AgentSkill 增量更新
|
||||
|
||||
### 3.1 一致性检查
|
||||
|
||||
运行 `scripts/sync_check.py` 检查 DDS 与 Skills 的对应关系:
|
||||
|
||||
```bash
|
||||
python3 "$(dirname "$0")/scripts/sync_check.py" --dds "$DDS_PATH" --skills-dir "$SKILLS_DIR"
|
||||
```
|
||||
|
||||
该脚本输出不一致报告:
|
||||
- DDS 中有定义但无对应 Skill 的模块
|
||||
- Skills 目录中有 Skill 但无对应 DDS 章节的模块
|
||||
|
||||
### 3.2 确定需要更新的 Skill 列表
|
||||
|
||||
基于 Phase 2 的 DDS 变更内容,确定受影响的 Skill 列表:
|
||||
|
||||
1. 提取 DDS 中 `[NEW]`/`[MODIFIED]`/`[DEPRECATED]` 章节涉及的模块名
|
||||
2. 在 Skills 目录中查找对应的 SKILL.md 文件
|
||||
3. 对于 DDS 新增模块但无 Skill 的情况,标记为"需新建 Skill"
|
||||
|
||||
### 3.3 执行 Skill 增量更新
|
||||
|
||||
对每个受影响的 Skill,使用 `references/SKILL_UPDATE_PROMPT.md` 驱动更新:
|
||||
|
||||
**变量替换**:
|
||||
- `{{DDS_DIFF}}` → 该模块涉及的 DDS 变更内容(`[NEW]`/`[MODIFIED]`/`[DEPRECATED]` 片段)
|
||||
- `{{SKILL_NAME}}` → 当前 Skill 的 name 值
|
||||
- `{{CURRENT_SKILL_MD}}` → 当前 SKILL.md 全文
|
||||
|
||||
**更新规则**:
|
||||
- 只更新受 DDS 变更影响的部分
|
||||
- version 字段 patch 版本 +1
|
||||
- 输出完整的新 SKILL.md 内容(直接覆盖写入)
|
||||
|
||||
### 3.4 Skill 更新验证
|
||||
|
||||
对每个更新后的 Skill 执行以下检查:
|
||||
- [ ] SKILL.md 的 YAML frontmatter 格式正确
|
||||
- [ ] version 字段已递增
|
||||
- [ ] name 字段未被修改
|
||||
- [ ] description 保持单行且 < 1024 字符
|
||||
- [ ] 未受影响的章节内容未被修改
|
||||
|
||||
---
|
||||
|
||||
## Phase 4:版本归档
|
||||
|
||||
### 4.1 生成 VERSION_MATRIX.md
|
||||
|
||||
基于 `assets/VERSION_MATRIX_TEMPLATE.md` 模板,追加一行新的版本记录:
|
||||
|
||||
| 字段 | 取值来源 |
|
||||
|------|---------|
|
||||
| 迭代日期 | 当前执行日期 |
|
||||
| PRD版本 | 新版 PRD 中提取的版本号 |
|
||||
| DDS版本 | 更新后 DDS 的版本号 |
|
||||
| 受影响Skill | Phase 3 中更新的 Skill 名称列表 |
|
||||
| 变更摘要 | Change Intent 中的变更摘要 |
|
||||
| Git Commit | 预留占位(手动填写或自动获取) |
|
||||
|
||||
### 4.2 归档文件存放位置
|
||||
|
||||
VERSION_MATRIX.md 存放在项目根目录或用户指定目录,采用**追加模式**:
|
||||
- 如果文件已存在,在表格末尾追加新行
|
||||
- 如果文件不存在,从模板创建并填入第一条记录
|
||||
|
||||
---
|
||||
|
||||
## 异常处理规则
|
||||
|
||||
| 异常场景 | 处理策略 |
|
||||
|---------|---------|
|
||||
| 旧版/新版 PRD 文件不存在 | 终止执行,输出错误信息 |
|
||||
| diff_prd.py 脚本执行失败 | 输出错误日志,提示用户手动对比 |
|
||||
| DDS 文件不存在 | 跳过 Phase 2/3,仅执行 Phase 1/4 |
|
||||
| Skills 目录不存在 | 跳过 Phase 3,仅执行 Phase 1/2/4 |
|
||||
| DDS 更新 Prompt 返回格式异常 | 要求重新生成,最多重试 2 次 |
|
||||
| Skill 更新后 frontmatter 格式错误 | 回滚该 Skill 更新,标记为需人工修复 |
|
||||
| sync_check.py 发现不一致模块 | 在 VERSION_MATRIX 中记录,不阻断流程 |
|
||||
|
||||
---
|
||||
|
||||
## 输出清单
|
||||
|
||||
每次执行完成后,必须输出以下产物清单:
|
||||
|
||||
```
|
||||
📋 Doc-Sync 执行报告
|
||||
├── [Phase 1] CHANGE_INTENT.md — PRD 变更意图文档
|
||||
├── [Phase 2] DDS 更新文件 — 增量更新后的 DDS(如适用)
|
||||
├── [Phase 3] 更新的 SKILL.md 列表 — 增量更新后的 Skills(如适用)
|
||||
├── [Phase 4] VERSION_MATRIX.md — 追加后的版本矩阵
|
||||
└── [报告] 执行摘要 — 各 Phase 执行状态汇总
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| 需要了解... | 查阅... |
|
||||
|------------|--------|
|
||||
| Change Intent 的结构模板 | `references/CHANGE_INTENT_TEMPLATE.md` |
|
||||
| DDS 增量更新 Prompt | `references/DDS_UPDATE_PROMPT.md` |
|
||||
| Skill 增量更新 Prompt | `references/SKILL_UPDATE_PROMPT.md` |
|
||||
| PRD 差异对比脚本 | `scripts/diff_prd.py` |
|
||||
| DDS-Skill 一致性检查脚本 | `scripts/sync_check.py` |
|
||||
| 版本矩阵归档模板 | `assets/VERSION_MATRIX_TEMPLATE.md` |
|
||||
@@ -0,0 +1,27 @@
|
||||
# VERSION MATRIX — 文档版本追踪矩阵
|
||||
|
||||
> 本表记录每次 PRD → DDS → Skill 级联同步的版本变更历史。
|
||||
> 由 doc-sync-skill Phase 4 自动追加,请勿手动删除已有记录。
|
||||
|
||||
---
|
||||
|
||||
| 迭代日期 | PRD版本 | DDS版本 | 受影响Skill | 变更摘要 | Git Commit |
|
||||
|---------|--------|--------|------------|---------|-----------|
|
||||
| `<YYYY-MM-DD>` | `<vX.Y.Z>` | `<vX.Y.Z>` | `<skill-1, skill-2, ...>` | `<简要描述本次变更>` | `<commit-hash>` |
|
||||
|
||||
---
|
||||
|
||||
## 使用说明
|
||||
|
||||
1. **自动追加**:每次执行 doc-sync-skill 的 Phase 4 时,会在表格末尾追加一行新记录
|
||||
2. **手动补充**:Git Commit 列可在提交后手动填写,或通过 CI 自动获取
|
||||
3. **版本格式**:所有版本号遵循语义化版本规范(SemVer)
|
||||
4. **历史保留**:请勿删除已有行,保持完整的变更历史
|
||||
|
||||
## 版本号约定
|
||||
|
||||
| 变更类型 | 版本递增规则 | 示例 |
|
||||
|---------|------------|------|
|
||||
| 文案/细节修正 | patch +1 | v1.0.0 → v1.0.1 |
|
||||
| 新增功能模块 | minor +1 | v1.0.0 → v1.1.0 |
|
||||
| 架构级重构 | major +1 | v1.0.0 → v2.0.0 |
|
||||
@@ -0,0 +1,93 @@
|
||||
# Change Intent — PRD 变更意图文档
|
||||
|
||||
> 本文档由 doc-sync-skill Phase 1 自动生成,描述 PRD 两个版本之间的结构化变更。
|
||||
|
||||
---
|
||||
|
||||
## 元信息
|
||||
|
||||
| 字段 | 值 |
|
||||
|------|------|
|
||||
| **变更版本** | `<旧版本号>` → `<新版本号>` |
|
||||
| **变更日期** | `<YYYY-MM-DD>` |
|
||||
| **变更范围** | `<全局 / 局部模块名列表>` |
|
||||
| **变更发起人** | `<姓名或角色>` |
|
||||
|
||||
---
|
||||
|
||||
## 新增需求列表
|
||||
|
||||
> 在新版 PRD 中新增、旧版中不存在的章节或需求。
|
||||
|
||||
| 序号 | 章节标题 | 需求概述 | 优先级 |
|
||||
|------|---------|---------|-------|
|
||||
| 1 | `<章节标题>` | `<一句话描述该新增需求>` | `<P0/P1/P2>` |
|
||||
| 2 | `<章节标题>` | `<一句话描述该新增需求>` | `<P0/P1/P2>` |
|
||||
|
||||
---
|
||||
|
||||
## 修改需求列表
|
||||
|
||||
> 在新版 PRD 中内容发生变化的章节(标题可能相同,但内容不同)。
|
||||
|
||||
| 序号 | 章节标题 | 变更类型 | 变更描述 |
|
||||
|------|---------|---------|---------|
|
||||
| 1 | `<章节标题>` | `<内容修改 / 重构 / 增强>` | `<具体变更描述,对比旧版说明改了什么>` |
|
||||
| 2 | `<章节标题>` | `<内容修改 / 重构 / 增强>` | `<具体变更描述>` |
|
||||
|
||||
### 修改详情
|
||||
|
||||
对于每个修改章节,记录关键 diff:
|
||||
|
||||
#### M1. `<章节标题>`
|
||||
|
||||
**旧版内容摘要**:
|
||||
```
|
||||
<旧版关键内容摘要>
|
||||
```
|
||||
|
||||
**新版内容摘要**:
|
||||
```
|
||||
<新版关键内容摘要>
|
||||
```
|
||||
|
||||
**变更原因**:`<为什么修改>`
|
||||
|
||||
---
|
||||
|
||||
## 废弃需求列表
|
||||
|
||||
> 在旧版 PRD 中存在、但在新版中被移除或标记废弃的章节。
|
||||
|
||||
| 序号 | 章节标题 | 废弃原因 | 替代方案 |
|
||||
|------|---------|---------|---------|
|
||||
| 1 | `<章节标题>` | `<废弃原因>` | `<替代方案或"无">` |
|
||||
|
||||
---
|
||||
|
||||
## 影响模块列表
|
||||
|
||||
> 基于上述变更分析,推断受影响的系统模块/组件。
|
||||
|
||||
| 模块名称 | 影响类型 | 影响说明 |
|
||||
|---------|---------|---------|
|
||||
| `<模块名>` | `<新增 / 修改 / 废弃>` | `<该模块如何受影响>` |
|
||||
|
||||
---
|
||||
|
||||
## 变更摘要
|
||||
|
||||
> 用不超过 500 字概括本次 PRD 变更的整体目的和核心内容。
|
||||
|
||||
```
|
||||
<变更摘要正文>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 后续动作建议
|
||||
|
||||
- [ ] 基于本 Change Intent 更新 DDS(Phase 2)
|
||||
- [ ] 更新受影响的 AgentSkill(Phase 3)
|
||||
- [ ] 归档版本矩阵(Phase 4)
|
||||
- [ ] 通知相关开发人员
|
||||
125
1-AgentSkills/doc-sync-skill/references/DDS_UPDATE_PROMPT.md
Normal file
125
1-AgentSkills/doc-sync-skill/references/DDS_UPDATE_PROMPT.md
Normal file
@@ -0,0 +1,125 @@
|
||||
# DDS 增量更新 Prompt
|
||||
|
||||
> 本文档是一份完整的 Prompt 模板,用于驱动大模型对 DDS(详细设计文档)进行增量更新。
|
||||
> 使用时将 `{{CHANGE_INTENT}}` 和 `{{CURRENT_DDS}}` 替换为实际内容后,整体发送给大模型。
|
||||
|
||||
---
|
||||
|
||||
## Prompt 正文
|
||||
|
||||
```
|
||||
你是一名资深软件架构师,拥有 15 年以上的系统设计经验。你当前的任务是根据提供的 PRD 变更意图(Change Intent),对现有的详细设计文档(DDS)进行精确的增量更新。
|
||||
|
||||
## 你的角色与职责
|
||||
|
||||
- 你是一名严谨的架构师,只做必要的最小变更
|
||||
- 你必须保持 DDS 整体架构的一致性和连贯性
|
||||
- 你绝不会修改未被 Change Intent 提及的章节
|
||||
- 你会为每一处变更提供清晰的变更标注和理由
|
||||
|
||||
## 输入
|
||||
|
||||
### Change Intent(PRD 变更意图)
|
||||
|
||||
{{CHANGE_INTENT}}
|
||||
|
||||
### 当前 DDS 全文
|
||||
|
||||
{{CURRENT_DDS}}
|
||||
|
||||
## 输出要求
|
||||
|
||||
请按照以下规则输出 DDS 增量更新内容:
|
||||
|
||||
### 标注规则
|
||||
|
||||
对于每个需要变更的章节,使用以下标注格式:
|
||||
|
||||
#### 1. 新增章节 — `[NEW]`
|
||||
|
||||
```markdown
|
||||
## [NEW] <章节标题>
|
||||
|
||||
<!-- 新增原因:<Change Intent 中对应的变更条目> -->
|
||||
|
||||
<新章节完整内容>
|
||||
```
|
||||
|
||||
#### 2. 修改章节 — `[MODIFIED]`
|
||||
|
||||
```markdown
|
||||
## [MODIFIED] <章节标题>
|
||||
|
||||
<!-- 修改原因:<Change Intent 中对应的变更条目> -->
|
||||
|
||||
### 变更对比
|
||||
|
||||
#### 原内容:
|
||||
```
|
||||
<被修改部分的原文>
|
||||
```
|
||||
|
||||
#### 新内容:
|
||||
```
|
||||
<修改后的完整内容>
|
||||
```
|
||||
|
||||
### 完整更新后章节
|
||||
|
||||
<该章节更新后的完整内容,可直接替换原章节>
|
||||
```
|
||||
|
||||
#### 3. 废弃章节 — `[DEPRECATED]`
|
||||
|
||||
```markdown
|
||||
## [DEPRECATED] <章节标题>
|
||||
|
||||
<!-- 废弃原因:<Change Intent 中对应的变更条目> -->
|
||||
<!-- 替代方案:<如有替代章节,指出章节名> -->
|
||||
|
||||
> ⚠️ 本章节已废弃,原因:<简述废弃原因>
|
||||
|
||||
~~<原章节内容保留,加删除线>~~
|
||||
```
|
||||
|
||||
### 严格约束
|
||||
|
||||
1. **最小变更原则**:只输出 Change Intent 中提及的变更所影响的章节。如果 Change Intent 没有提到某个章节,**绝对不要输出该章节的任何内容**。
|
||||
2. **溯源标注**:每个 `[NEW]`/`[MODIFIED]`/`[DEPRECATED]` 标注必须在注释中说明对应的 Change Intent 条目。
|
||||
3. **结构保持**:新增或修改的章节必须与现有 DDS 的格式风格保持一致(标题层级、表格格式、代码块风格等)。
|
||||
4. **接口兼容**:修改涉及 API 接口时,必须标注是否存在 breaking change,如有则需提供迁移建议。
|
||||
5. **数据库兼容**:修改涉及数据库 Schema 时,必须提供迁移 SQL 或迁移策略说明。
|
||||
6. **状态机变更**:修改涉及状态机时,必须更新状态转移图并检查新旧状态的兼容性。
|
||||
|
||||
### 输出末尾追加
|
||||
|
||||
在所有章节变更输出完毕后,请追加以下版本信息:
|
||||
|
||||
```markdown
|
||||
---
|
||||
|
||||
## 版本号建议
|
||||
|
||||
- **当前版本**:<从 DDS 文档头提取的当前版本号>
|
||||
- **建议新版本**:<根据变更范围建议的新版本号>
|
||||
- 若仅修改细节/修复:patch +1(如 v1.0.0 → v1.0.1)
|
||||
- 若新增功能模块:minor +1(如 v1.0.0 → v1.1.0)
|
||||
- 若架构级重构或破坏性变更:major +1(如 v1.0.0 → v2.0.0)
|
||||
- **版本号变更理由**:<一句话说明>
|
||||
```
|
||||
|
||||
### 输出格式总结
|
||||
|
||||
你的完整输出结构应为:
|
||||
|
||||
1. 所有 `[NEW]` 章节(如有)
|
||||
2. 所有 `[MODIFIED]` 章节(如有)
|
||||
3. 所有 `[DEPRECATED]` 章节(如有)
|
||||
4. 版本号建议
|
||||
|
||||
如果 Change Intent 中没有任何实质性变更(例如仅文案修正),请输出:
|
||||
|
||||
```
|
||||
无需更新 DDS。Change Intent 中的变更不影响系统设计层面。
|
||||
```
|
||||
```
|
||||
@@ -0,0 +1,83 @@
|
||||
# AgentSkill 增量更新 Prompt
|
||||
|
||||
> 本文档是一份完整的 Prompt 模板,用于驱动大模型对单个 AgentSkill 的 SKILL.md 进行增量更新。
|
||||
> 使用时将 `{{DDS_DIFF}}`、`{{SKILL_NAME}}` 和 `{{CURRENT_SKILL_MD}}` 替换为实际内容后,整体发送给大模型。
|
||||
|
||||
---
|
||||
|
||||
## Prompt 正文
|
||||
|
||||
```
|
||||
你是一名 AgentSkill 维护专家,精通 Anthropic Agent Skill 规范。你当前的任务是根据 DDS 的增量变更内容,对指定的 AgentSkill SKILL.md 文件进行精确的增量更新。
|
||||
|
||||
## 你的角色与职责
|
||||
|
||||
- 你是一名严谨的 Skill 维护者,只修改受 DDS 变更影响的部分
|
||||
- 你必须保持 SKILL.md 格式规范的完整性
|
||||
- 你绝不会修改未受影响的章节内容
|
||||
- 你会确保每次更新后 SKILL.md 仍然是一份完整可用的文档
|
||||
|
||||
## 输入
|
||||
|
||||
### DDS 变更内容(Diff)
|
||||
|
||||
以下是与该 Skill 相关的 DDS 变更片段,使用 [NEW]/[MODIFIED]/[DEPRECATED] 标注:
|
||||
|
||||
{{DDS_DIFF}}
|
||||
|
||||
### 当前 Skill 信息
|
||||
|
||||
- **Skill 名称**:{{SKILL_NAME}}
|
||||
|
||||
### 当前 SKILL.md 全文
|
||||
|
||||
{{CURRENT_SKILL_MD}}
|
||||
|
||||
## 输出要求
|
||||
|
||||
### 更新规则
|
||||
|
||||
1. **只更新受影响部分**:根据 DDS 变更内容,判断 SKILL.md 中哪些章节需要更新,只修改这些章节。
|
||||
2. **version 字段递增**:在 YAML frontmatter 中,将 version 的 patch 版本号 +1(例如 `1.0.0` → `1.0.1`,`1.2.3` → `1.2.4`)。
|
||||
3. **保持 name 不变**:YAML frontmatter 中的 name 字段绝对不能修改。
|
||||
4. **description 保持单行**:更新 description 时必须保持单行格式,且长度 < 1024 字符。
|
||||
5. **格式一致性**:更新后的内容必须与现有 SKILL.md 的格式风格保持一致。
|
||||
|
||||
### 变更类型与处理
|
||||
|
||||
根据 DDS 变更的标注类型,采取以下处理策略:
|
||||
|
||||
| DDS 变更标注 | SKILL.md 处理策略 |
|
||||
|-------------|-----------------|
|
||||
| `[NEW]` 新增章节 | 在 SKILL.md 的 Execute/Verify/Plan 中添加相关步骤或检查项 |
|
||||
| `[MODIFIED]` 修改章节 | 更新 SKILL.md 中对应的步骤描述、checklist 或 reference 引用 |
|
||||
| `[DEPRECATED]` 废弃章节 | 从 SKILL.md 中移除或标注废弃相关步骤,更新 Pitfalls 提醒 |
|
||||
|
||||
### 输出格式
|
||||
|
||||
请直接输出**完整的、更新后的 SKILL.md 文件内容**,从 YAML frontmatter 的 `---` 开始,到文件末尾结束。
|
||||
|
||||
输出的 SKILL.md 必须满足以下格式检查:
|
||||
|
||||
- [ ] YAML frontmatter 格式正确(以 `---` 开头和结尾)
|
||||
- [ ] name 字段只含小写字母、数字和连字符,≤ 64 字符
|
||||
- [ ] description 为单行,< 1024 字符
|
||||
- [ ] version 字段已递增 patch 版本
|
||||
- [ ] 包含必要的标准章节(Plan / Verify / Execute / Pitfalls 等,如原文有)
|
||||
- [ ] 文件总行数 < 500 行
|
||||
|
||||
### 严格约束
|
||||
|
||||
1. **禁止创造性发挥**:只基于提供的 DDS 变更内容进行更新,不要添加 DDS 中没有的内容。
|
||||
2. **禁止删除无关内容**:不受 DDS 变更影响的章节必须原封不动保留。
|
||||
3. **禁止修改 name**:SKILL 的 name 字段是标识符,绝对不能修改。
|
||||
4. **reference 更新**:如果 SKILL.md 引用了 reference/ 中的文件,且这些文件内容因 DDS 变更而需要更新,在 SKILL.md 中标注 `<!-- TODO: 同步更新 reference/xxx.md -->`。
|
||||
|
||||
### 无需更新的情况
|
||||
|
||||
如果 DDS 变更内容与该 Skill 完全无关(例如变更的是其他模块的内容),请输出:
|
||||
|
||||
```
|
||||
该 Skill ({{SKILL_NAME}}) 不受本次 DDS 变更影响,无需更新。
|
||||
```
|
||||
```
|
||||
300
1-AgentSkills/doc-sync-skill/scripts/diff_prd.py
Normal file
300
1-AgentSkills/doc-sync-skill/scripts/diff_prd.py
Normal file
@@ -0,0 +1,300 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
diff_prd.py - PRD 文档章节级差异对比工具
|
||||
|
||||
按章节(## 标题)级别对比两个 Markdown 格式的 PRD 文档,
|
||||
输出结构化 JSON 包含新增、修改和删除的章节信息。
|
||||
|
||||
Usage:
|
||||
python diff_prd.py --old <old_prd_path> --new <new_prd_path>
|
||||
|
||||
Output:
|
||||
JSON 格式的差异报告,写入 stdout
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import os
|
||||
from typing import Dict, List, Tuple
|
||||
from difflib import unified_diff
|
||||
|
||||
|
||||
def parse_sections(content: str) -> Dict[str, str]:
|
||||
"""
|
||||
将 Markdown 文档按 ## 标题级别拆分为章节字典。
|
||||
|
||||
Args:
|
||||
content: Markdown 文档全文
|
||||
|
||||
Returns:
|
||||
有序字典,key 为章节标题(去除 ## 前缀),value 为章节内容
|
||||
"""
|
||||
sections: Dict[str, str] = {}
|
||||
current_title = "__HEADER__" # 文档头部(## 之前的内容)
|
||||
current_lines: List[str] = []
|
||||
|
||||
for line in content.splitlines():
|
||||
# 匹配 ## 级别标题(不匹配 # 和 ### 及以下)
|
||||
match = re.match(r'^##\s+(.+)$', line)
|
||||
if match:
|
||||
# 保存上一个章节
|
||||
section_content = '\n'.join(current_lines).strip()
|
||||
if section_content or current_title != "__HEADER__":
|
||||
sections[current_title] = section_content
|
||||
|
||||
current_title = match.group(1).strip()
|
||||
current_lines = []
|
||||
else:
|
||||
current_lines.append(line)
|
||||
|
||||
# 保存最后一个章节
|
||||
section_content = '\n'.join(current_lines).strip()
|
||||
if section_content or current_title != "__HEADER__":
|
||||
sections[current_title] = section_content
|
||||
|
||||
return sections
|
||||
|
||||
|
||||
def compute_diff(old_text: str, new_text: str) -> str:
|
||||
"""
|
||||
计算两段文本的 unified diff。
|
||||
|
||||
Args:
|
||||
old_text: 旧版文本
|
||||
new_text: 新版文本
|
||||
|
||||
Returns:
|
||||
unified diff 字符串
|
||||
"""
|
||||
old_lines = old_text.splitlines(keepends=True)
|
||||
new_lines = new_text.splitlines(keepends=True)
|
||||
|
||||
diff = unified_diff(
|
||||
old_lines,
|
||||
new_lines,
|
||||
fromfile='old',
|
||||
tofile='new',
|
||||
lineterm=''
|
||||
)
|
||||
return ''.join(diff)
|
||||
|
||||
|
||||
def compare_sections(
|
||||
old_sections: Dict[str, str],
|
||||
new_sections: Dict[str, str]
|
||||
) -> Tuple[List[str], List[Dict[str, str]], List[str]]:
|
||||
"""
|
||||
对比两个版本的章节字典,找出新增、修改和删除的章节。
|
||||
|
||||
Args:
|
||||
old_sections: 旧版章节字典
|
||||
new_sections: 新版章节字典
|
||||
|
||||
Returns:
|
||||
(added, modified, removed) 三元组
|
||||
"""
|
||||
old_titles = set(old_sections.keys())
|
||||
new_titles = set(new_sections.keys())
|
||||
|
||||
# 新增章节:在新版中存在,旧版中不存在
|
||||
added = sorted(list(new_titles - old_titles))
|
||||
|
||||
# 删除章节:在旧版中存在,新版中不存在
|
||||
removed = sorted(list(old_titles - new_titles))
|
||||
|
||||
# 修改章节:两版都存在但内容不同
|
||||
common_titles = old_titles & new_titles
|
||||
modified = []
|
||||
for title in sorted(common_titles):
|
||||
old_content = old_sections[title]
|
||||
new_content = new_sections[title]
|
||||
if old_content != new_content:
|
||||
modified.append({
|
||||
"title": title,
|
||||
"old": old_content,
|
||||
"new": new_content,
|
||||
"diff": compute_diff(old_content, new_content)
|
||||
})
|
||||
|
||||
return added, modified, removed
|
||||
|
||||
|
||||
def generate_summary(
|
||||
added: List[str],
|
||||
modified: List[Dict[str, str]],
|
||||
removed: List[str]
|
||||
) -> str:
|
||||
"""
|
||||
生成变更摘要。
|
||||
|
||||
Args:
|
||||
added: 新增章节标题列表
|
||||
modified: 修改章节详情列表
|
||||
removed: 删除章节标题列表
|
||||
|
||||
Returns:
|
||||
变更摘要字符串
|
||||
"""
|
||||
parts = []
|
||||
|
||||
total_changes = len(added) + len(modified) + len(removed)
|
||||
if total_changes == 0:
|
||||
return "无变更:两个版本的 PRD 内容完全一致。"
|
||||
|
||||
parts.append(f"共检测到 {total_changes} 处章节级变更。")
|
||||
|
||||
if added:
|
||||
parts.append(f"新增 {len(added)} 个章节:{', '.join(added)}。")
|
||||
|
||||
if modified:
|
||||
mod_titles = [m['title'] for m in modified]
|
||||
parts.append(f"修改 {len(modified)} 个章节:{', '.join(mod_titles)}。")
|
||||
|
||||
if removed:
|
||||
parts.append(f"删除 {len(removed)} 个章节:{', '.join(removed)}。")
|
||||
|
||||
return ' '.join(parts)
|
||||
|
||||
|
||||
def read_file(filepath: str) -> str:
|
||||
"""
|
||||
读取文件内容,支持 UTF-8 编码。
|
||||
|
||||
Args:
|
||||
filepath: 文件路径
|
||||
|
||||
Returns:
|
||||
文件内容字符串
|
||||
|
||||
Raises:
|
||||
FileNotFoundError: 文件不存在
|
||||
UnicodeDecodeError: 编码错误
|
||||
"""
|
||||
abs_path = os.path.abspath(filepath)
|
||||
if not os.path.exists(abs_path):
|
||||
raise FileNotFoundError(f"文件不存在: {abs_path}")
|
||||
|
||||
if not os.path.isfile(abs_path):
|
||||
raise ValueError(f"路径不是文件: {abs_path}")
|
||||
|
||||
# 尝试多种编码
|
||||
encodings = ['utf-8', 'utf-8-sig', 'gbk', 'gb2312', 'latin-1']
|
||||
for encoding in encodings:
|
||||
try:
|
||||
with open(abs_path, 'r', encoding=encoding) as f:
|
||||
return f.read()
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
|
||||
raise UnicodeDecodeError(
|
||||
'utf-8', b'', 0, 1,
|
||||
f"无法以任何支持的编码读取文件: {abs_path}"
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='PRD 文档章节级差异对比工具',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
示例:
|
||||
python diff_prd.py --old prd_v1.md --new prd_v2.md
|
||||
python diff_prd.py --old prd_v1.md --new prd_v2.md 2>/dev/null
|
||||
|
||||
输出格式:
|
||||
JSON 对象,包含以下字段:
|
||||
- added_sections: 新增章节标题列表
|
||||
- modified_sections: 修改章节详情列表
|
||||
- removed_sections: 删除章节标题列表
|
||||
- summary: 变更摘要
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--old',
|
||||
required=True,
|
||||
help='旧版 PRD 文件路径(Markdown 格式)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--new',
|
||||
required=True,
|
||||
help='新版 PRD 文件路径(Markdown 格式)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--output',
|
||||
default=None,
|
||||
help='输出文件路径(默认输出到 stdout)'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
# 读取文件
|
||||
old_content = read_file(args.old)
|
||||
new_content = read_file(args.new)
|
||||
|
||||
# 解析章节
|
||||
old_sections = parse_sections(old_content)
|
||||
new_sections = parse_sections(new_content)
|
||||
|
||||
# 对比差异
|
||||
added, modified, removed = compare_sections(old_sections, new_sections)
|
||||
|
||||
# 生成摘要
|
||||
summary = generate_summary(added, modified, removed)
|
||||
|
||||
# 构建结果(修改章节中去除 diff 字段的冗余信息,只保留标题和新旧内容)
|
||||
modified_output = []
|
||||
for m in modified:
|
||||
modified_output.append({
|
||||
"title": m["title"],
|
||||
"old": m["old"],
|
||||
"new": m["new"]
|
||||
})
|
||||
|
||||
result = {
|
||||
"added_sections": added,
|
||||
"modified_sections": modified_output,
|
||||
"removed_sections": removed,
|
||||
"summary": summary
|
||||
}
|
||||
|
||||
# 输出结果
|
||||
output_json = json.dumps(result, ensure_ascii=False, indent=2)
|
||||
|
||||
if args.output:
|
||||
output_path = os.path.abspath(args.output)
|
||||
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
f.write(output_json)
|
||||
print(f"差异报告已写入: {output_path}", file=sys.stderr)
|
||||
else:
|
||||
print(output_json)
|
||||
|
||||
except FileNotFoundError as e:
|
||||
print(json.dumps({
|
||||
"error": str(e),
|
||||
"type": "FileNotFoundError"
|
||||
}, ensure_ascii=False), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
except UnicodeDecodeError as e:
|
||||
print(json.dumps({
|
||||
"error": f"文件编码错误: {str(e)}",
|
||||
"type": "UnicodeDecodeError"
|
||||
}, ensure_ascii=False), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
except Exception as e:
|
||||
print(json.dumps({
|
||||
"error": f"未预期的错误: {str(e)}",
|
||||
"type": type(e).__name__
|
||||
}, ensure_ascii=False), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
454
1-AgentSkills/doc-sync-skill/scripts/sync_check.py
Normal file
454
1-AgentSkills/doc-sync-skill/scripts/sync_check.py
Normal file
@@ -0,0 +1,454 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
sync_check.py - DDS 与 AgentSkill 一致性检查工具
|
||||
|
||||
扫描 DDS 文档中定义的所有接口/模块名称,检查在 Skills 目录下是否存在对应的 SKILL.md。
|
||||
输出不一致报告:
|
||||
- DDS 中定义但没有对应 Skill 的模块
|
||||
- Skills 目录中存在但无对应 DDS 章节的 Skill
|
||||
|
||||
Usage:
|
||||
python sync_check.py --dds <dds_path> --skills-dir <skills_directory>
|
||||
|
||||
Output:
|
||||
JSON 格式的一致性报告,写入 stdout
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from typing import Dict, List, Set, Tuple
|
||||
|
||||
|
||||
def read_file(filepath: str) -> str:
|
||||
"""
|
||||
读取文件内容,支持 UTF-8 编码。
|
||||
|
||||
Args:
|
||||
filepath: 文件路径
|
||||
|
||||
Returns:
|
||||
文件内容字符串
|
||||
|
||||
Raises:
|
||||
FileNotFoundError: 文件不存在
|
||||
"""
|
||||
abs_path = os.path.abspath(filepath)
|
||||
if not os.path.exists(abs_path):
|
||||
raise FileNotFoundError(f"文件不存在: {abs_path}")
|
||||
|
||||
if not os.path.isfile(abs_path):
|
||||
raise ValueError(f"路径不是文件: {abs_path}")
|
||||
|
||||
encodings = ['utf-8', 'utf-8-sig', 'gbk', 'gb2312', 'latin-1']
|
||||
for encoding in encodings:
|
||||
try:
|
||||
with open(abs_path, 'r', encoding=encoding) as f:
|
||||
return f.read()
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
|
||||
raise UnicodeDecodeError(
|
||||
'utf-8', b'', 0, 1,
|
||||
f"无法以任何支持的编码读取文件: {abs_path}"
|
||||
)
|
||||
|
||||
|
||||
def extract_dds_modules(content: str) -> List[Dict[str, str]]:
|
||||
"""
|
||||
从 DDS 文档中提取模块/接口名称。
|
||||
|
||||
提取策略(按优先级):
|
||||
1. 匹配 ## 级别标题中包含"模块"、"接口"、"服务"等关键词的章节
|
||||
2. 匹配代码块中的模块/包/服务定义
|
||||
3. 匹配表格中定义的模块列表
|
||||
|
||||
Args:
|
||||
content: DDS 文档全文
|
||||
|
||||
Returns:
|
||||
模块信息列表,每项包含 name 和 source(来源标记)
|
||||
"""
|
||||
modules: List[Dict[str, str]] = []
|
||||
seen_names: Set[str] = set()
|
||||
|
||||
# 策略 1:从 ## 标题提取模块名
|
||||
# 匹配模式:## xxx模块 / ## xxx服务 / ## xxx接口 / ## xxx-xxx
|
||||
section_pattern = re.compile(
|
||||
r'^##\s+(?:\d+[\.\s]*)?' # ## 和可选的编号
|
||||
r'(.+?)' # 标题内容
|
||||
r'\s*$',
|
||||
re.MULTILINE
|
||||
)
|
||||
|
||||
for match in section_pattern.finditer(content):
|
||||
title = match.group(1).strip()
|
||||
|
||||
# 尝试从标题中提取模块名
|
||||
# 模式:xxx模块、xxx服务、xxx接口、xxx组件
|
||||
module_match = re.match(
|
||||
r'(.+?)\s*(?:模块|服务|接口|组件|子系统|处理器|引擎|管理器)',
|
||||
title
|
||||
)
|
||||
if module_match:
|
||||
name = module_match.group(1).strip()
|
||||
normalized = normalize_module_name(name)
|
||||
if normalized and normalized not in seen_names:
|
||||
seen_names.add(normalized)
|
||||
modules.append({
|
||||
"name": normalized,
|
||||
"display_name": name,
|
||||
"source": f"章节标题: {title}"
|
||||
})
|
||||
|
||||
# 策略 2:从表格中提取模块名
|
||||
# 匹配 | 模块名 | 或 | 名称 | 格式的表格行
|
||||
table_pattern = re.compile(
|
||||
r'^\|\s*(.+?)\s*\|.*(?:模块|服务|接口|职责|说明)',
|
||||
re.MULTILINE
|
||||
)
|
||||
|
||||
# 查找表格头行之后的数据行
|
||||
lines = content.splitlines()
|
||||
in_module_table = False
|
||||
for i, line in enumerate(lines):
|
||||
# 检测表格头
|
||||
if re.match(r'^\|.*(?:模块|名称|组件|服务).*\|', line):
|
||||
in_module_table = True
|
||||
continue
|
||||
|
||||
# 跳过分隔行
|
||||
if in_module_table and re.match(r'^\|[\s\-:]+\|', line):
|
||||
continue
|
||||
|
||||
# 提取表格数据行
|
||||
if in_module_table and line.startswith('|'):
|
||||
cells = [c.strip() for c in line.split('|') if c.strip()]
|
||||
if cells:
|
||||
name = cells[0].strip('`').strip()
|
||||
normalized = normalize_module_name(name)
|
||||
if normalized and normalized not in seen_names:
|
||||
seen_names.add(normalized)
|
||||
modules.append({
|
||||
"name": normalized,
|
||||
"display_name": name,
|
||||
"source": f"表格第 {i + 1} 行"
|
||||
})
|
||||
elif in_module_table and not line.startswith('|'):
|
||||
in_module_table = False
|
||||
|
||||
# 策略 3:从代码块中提取 package/module 定义
|
||||
code_pattern = re.compile(
|
||||
r'(?:package|module|service)\s+(\w[\w\-]*)',
|
||||
re.MULTILINE
|
||||
)
|
||||
for match in code_pattern.finditer(content):
|
||||
name = match.group(1).strip()
|
||||
normalized = normalize_module_name(name)
|
||||
if normalized and normalized not in seen_names and len(normalized) > 2:
|
||||
seen_names.add(normalized)
|
||||
modules.append({
|
||||
"name": normalized,
|
||||
"display_name": name,
|
||||
"source": "代码块定义"
|
||||
})
|
||||
|
||||
return modules
|
||||
|
||||
|
||||
def normalize_module_name(name: str) -> str:
|
||||
"""
|
||||
标准化模块名称为 Skill 命名格式(小写、连字符分隔)。
|
||||
|
||||
Args:
|
||||
name: 原始模块名称
|
||||
|
||||
Returns:
|
||||
标准化后的名称
|
||||
"""
|
||||
if not name:
|
||||
return ""
|
||||
|
||||
# 移除中文括号和英文括号中的内容
|
||||
name = re.sub(r'[((].+?[))]', '', name)
|
||||
|
||||
# 转小写
|
||||
name = name.lower().strip()
|
||||
|
||||
# 中文转拼音或直接使用(这里简单处理,保留英文部分)
|
||||
# 如果全是中文,暂时保留原名
|
||||
has_english = bool(re.search(r'[a-z]', name))
|
||||
|
||||
if has_english:
|
||||
# 提取英文部分
|
||||
english_parts = re.findall(r'[a-z][a-z0-9]*', name)
|
||||
name = '-'.join(english_parts)
|
||||
else:
|
||||
# 纯中文名称,保留原文
|
||||
name = name.replace(' ', '-')
|
||||
|
||||
# 清理连续的连字符
|
||||
name = re.sub(r'-+', '-', name).strip('-')
|
||||
|
||||
return name
|
||||
|
||||
|
||||
def scan_skills_directory(skills_dir: str) -> List[Dict[str, str]]:
|
||||
"""
|
||||
扫描 Skills 目录,获取所有现有 Skill 信息。
|
||||
|
||||
Args:
|
||||
skills_dir: Skills 目录路径
|
||||
|
||||
Returns:
|
||||
Skill 信息列表,每项包含 name、path 和 has_skill_md
|
||||
"""
|
||||
abs_dir = os.path.abspath(skills_dir)
|
||||
if not os.path.exists(abs_dir):
|
||||
raise FileNotFoundError(f"Skills 目录不存在: {abs_dir}")
|
||||
|
||||
if not os.path.isdir(abs_dir):
|
||||
raise ValueError(f"路径不是目录: {abs_dir}")
|
||||
|
||||
skills: List[Dict[str, str]] = []
|
||||
|
||||
for entry in sorted(os.listdir(abs_dir)):
|
||||
entry_path = os.path.join(abs_dir, entry)
|
||||
if not os.path.isdir(entry_path):
|
||||
continue
|
||||
|
||||
skill_md_path = os.path.join(entry_path, 'SKILL.md')
|
||||
has_skill_md = os.path.isfile(skill_md_path)
|
||||
|
||||
skill_info: Dict[str, str] = {
|
||||
"name": entry,
|
||||
"path": entry_path,
|
||||
"has_skill_md": has_skill_md
|
||||
}
|
||||
|
||||
# 如果有 SKILL.md,尝试提取 frontmatter 中的 name
|
||||
if has_skill_md:
|
||||
try:
|
||||
skill_content = read_file(skill_md_path)
|
||||
fm_match = re.search(
|
||||
r'^---\s*\n(.*?)\n---',
|
||||
skill_content,
|
||||
re.DOTALL
|
||||
)
|
||||
if fm_match:
|
||||
fm_content = fm_match.group(1)
|
||||
name_match = re.search(r'^name:\s*(.+)$', fm_content, re.MULTILINE)
|
||||
if name_match:
|
||||
skill_info["frontmatter_name"] = name_match.group(1).strip()
|
||||
|
||||
version_match = re.search(r'^version:\s*(.+)$', fm_content, re.MULTILINE)
|
||||
if version_match:
|
||||
skill_info["version"] = version_match.group(1).strip()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
skills.append(skill_info)
|
||||
|
||||
return skills
|
||||
|
||||
|
||||
def check_consistency(
|
||||
dds_modules: List[Dict[str, str]],
|
||||
skills: List[Dict[str, str]]
|
||||
) -> Dict:
|
||||
"""
|
||||
检查 DDS 模块与 Skills 的一致性。
|
||||
|
||||
Args:
|
||||
dds_modules: DDS 中提取的模块列表
|
||||
skills: Skills 目录中扫描到的 Skill 列表
|
||||
|
||||
Returns:
|
||||
一致性报告字典
|
||||
"""
|
||||
dds_names = {m["name"] for m in dds_modules}
|
||||
skill_names = {s["name"] for s in skills}
|
||||
|
||||
# 也收集 frontmatter 中的 name
|
||||
skill_fm_names = {
|
||||
s.get("frontmatter_name", s["name"])
|
||||
for s in skills
|
||||
}
|
||||
|
||||
# DDS 中有但 Skills 中没有的模块
|
||||
missing_skills: List[Dict[str, str]] = []
|
||||
for module in dds_modules:
|
||||
name = module["name"]
|
||||
# 检查是否有匹配的 Skill(目录名或 frontmatter name 匹配)
|
||||
# 常见 Skill 命名模式:developing-<module-name>
|
||||
possible_skill_names = {
|
||||
name,
|
||||
f"developing-{name}",
|
||||
f"implementing-{name}",
|
||||
f"managing-{name}"
|
||||
}
|
||||
|
||||
if not (possible_skill_names & skill_names) and \
|
||||
not (possible_skill_names & skill_fm_names):
|
||||
missing_skills.append({
|
||||
"module_name": module["name"],
|
||||
"display_name": module["display_name"],
|
||||
"source": module["source"],
|
||||
"suggested_skill_name": f"developing-{name}"
|
||||
})
|
||||
|
||||
# Skills 中有但 DDS 中没有的 Skill
|
||||
orphan_skills: List[Dict[str, str]] = []
|
||||
for skill in skills:
|
||||
skill_name = skill["name"]
|
||||
# 从 Skill 名称中提取模块名(去除 developing-/implementing-/managing- 前缀)
|
||||
module_name = re.sub(
|
||||
r'^(?:developing|implementing|managing|designing)-',
|
||||
'',
|
||||
skill_name
|
||||
)
|
||||
|
||||
if module_name not in dds_names and skill_name not in dds_names:
|
||||
orphan_skills.append({
|
||||
"skill_name": skill_name,
|
||||
"skill_path": skill["path"],
|
||||
"has_skill_md": skill["has_skill_md"],
|
||||
"expected_dds_module": module_name
|
||||
})
|
||||
|
||||
# 匹配成功的映射
|
||||
matched: List[Dict[str, str]] = []
|
||||
for module in dds_modules:
|
||||
name = module["name"]
|
||||
possible_skill_names = {
|
||||
name,
|
||||
f"developing-{name}",
|
||||
f"implementing-{name}",
|
||||
f"managing-{name}"
|
||||
}
|
||||
|
||||
for skill in skills:
|
||||
if skill["name"] in possible_skill_names or \
|
||||
skill.get("frontmatter_name", "") in possible_skill_names:
|
||||
matched.append({
|
||||
"dds_module": module["name"],
|
||||
"skill_name": skill["name"],
|
||||
"has_skill_md": skill["has_skill_md"]
|
||||
})
|
||||
break
|
||||
|
||||
return {
|
||||
"summary": {
|
||||
"total_dds_modules": len(dds_modules),
|
||||
"total_skills": len(skills),
|
||||
"matched": len(matched),
|
||||
"missing_skills": len(missing_skills),
|
||||
"orphan_skills": len(orphan_skills)
|
||||
},
|
||||
"missing_skills": missing_skills,
|
||||
"orphan_skills": orphan_skills,
|
||||
"matched": matched
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='DDS 与 AgentSkill 一致性检查工具',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
示例:
|
||||
python sync_check.py --dds design.md --skills-dir ./skills
|
||||
python sync_check.py --dds dds_v2.md --skills-dir ../1-AgentSkills
|
||||
|
||||
输出格式:
|
||||
JSON 对象,包含以下字段:
|
||||
- summary: 统计摘要
|
||||
- missing_skills: DDS 中有但无对应 Skill 的模块列表
|
||||
- orphan_skills: 有 Skill 但无对应 DDS 章节的列表
|
||||
- matched: 已匹配的 DDS-Skill 映射列表
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--dds',
|
||||
required=True,
|
||||
help='DDS 文件路径(Markdown 格式)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--skills-dir',
|
||||
required=True,
|
||||
help='AgentSkills 目录路径'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--output',
|
||||
default=None,
|
||||
help='输出文件路径(默认输出到 stdout)'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
# 读取 DDS 并提取模块
|
||||
dds_content = read_file(args.dds)
|
||||
dds_modules = extract_dds_modules(dds_content)
|
||||
|
||||
if not dds_modules:
|
||||
print(
|
||||
"⚠️ 警告:未能从 DDS 文档中提取到任何模块定义。"
|
||||
"请检查 DDS 文档是否使用了 ## 标题格式定义模块。",
|
||||
file=sys.stderr
|
||||
)
|
||||
|
||||
# 扫描 Skills 目录
|
||||
skills = scan_skills_directory(args.skills_dir)
|
||||
|
||||
# 检查一致性
|
||||
report = check_consistency(dds_modules, skills)
|
||||
|
||||
# 添加元信息
|
||||
report["meta"] = {
|
||||
"dds_path": os.path.abspath(args.dds),
|
||||
"skills_dir": os.path.abspath(args.skills_dir),
|
||||
"dds_modules_extracted": [
|
||||
{"name": m["name"], "display_name": m["display_name"]}
|
||||
for m in dds_modules
|
||||
]
|
||||
}
|
||||
|
||||
# 输出结果
|
||||
output_json = json.dumps(report, ensure_ascii=False, indent=2)
|
||||
|
||||
if args.output:
|
||||
output_path = os.path.abspath(args.output)
|
||||
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
f.write(output_json)
|
||||
print(f"一致性报告已写入: {output_path}", file=sys.stderr)
|
||||
else:
|
||||
print(output_json)
|
||||
|
||||
# 如果有不一致项,返回非零退出码
|
||||
if report["summary"]["missing_skills"] > 0 or \
|
||||
report["summary"]["orphan_skills"] > 0:
|
||||
sys.exit(2) # Exit code 2 表示有不一致但非致命错误
|
||||
|
||||
except FileNotFoundError as e:
|
||||
print(json.dumps({
|
||||
"error": str(e),
|
||||
"type": "FileNotFoundError"
|
||||
}, ensure_ascii=False), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
except Exception as e:
|
||||
print(json.dumps({
|
||||
"error": f"未预期的错误: {str(e)}",
|
||||
"type": type(e).__name__
|
||||
}, ensure_ascii=False), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -6,14 +6,14 @@
|
||||
4. 支持maven并发构建
|
||||
5. 构建缓存支持较好
|
||||
6. 支持多分支构建
|
||||
6. 至此gravvl vm构建
|
||||
7. 至此gravvl vm构建
|
||||
2. 灵活的构建参数
|
||||
3. 灵活的服务器调度
|
||||
4. 需要又灵活的集成策略
|
||||
1. 构建信息需要向外传递
|
||||
5. 主流的脚本语法
|
||||
6. 良好的文档、生态支持
|
||||
7. 有接口暴露API,能够进行二次开发
|
||||
7. 有良好的接口暴露API,能够进行二次开发
|
||||
8. 支持私有化部署
|
||||
|
||||
最好是开源免费的CI CD工具,也可以对比付费的构建工具 如TeamCity等, 我们现在使用的是Jenkins工具。
|
||||
|
||||
139
15-CICD工具选型/4-CICD选型-260525.md
Normal file
139
15-CICD工具选型/4-CICD选型-260525.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# **企业级 CI/CD 现代化演进:GitLab CI、Tekton 与 TeamCity 架构深度评估与落地指南**
|
||||
|
||||
随着企业业务规模的指数级增长,软件交付生命周期对持续集成与持续交付(CI/CD)基础设施的性能、可扩展性以及弹性提出了极为严苛的要求。在每年构建数量达到十万次量级的企业环境中,传统的自动化服务器(如 Jenkins)往往会遭遇严重的架构瓶颈。Jenkins 高度依赖于有状态的 Master-Agent 架构、极其庞杂且容易产生冲突的插件生态,以及缺乏类型安全的 Groovy 脚本范式(Jenkinsfile)。这些历史包袱在极高并发的构建场景下,不可避免地会导致配置漂移、内存泄漏、单点故障以及高昂的维护成本。
|
||||
为了突破这些交付瓶颈,基础设施的现代化演进必须向分布式、容器原生以及声明式的 CI/CD 平台进行范式转移。新的架构需要能够支撑高度异构的多语言构建、原生解决构建并发冲突、支持动态参数注入,并且必须完美适配企业级的私有化部署(On-Premises)安全规范。
|
||||
本报告围绕八个核心技术维度,对当前业界最具代表性的 CI/CD 平台进行深度技术剖析与横向对比。在开源免费工具阵营中,本报告甄选了深度整合的 GitLab CI 与完全基于 Kubernetes 理念构建的 Tekton;同时,引入 JetBrains 旗下的 TeamCity 作为商业闭源的行业标杆进行对标参考。报告不仅提供了详尽的维度评估矩阵,还针对“缓存优化”、“Maven 并发”与“外围系统回调”这三大核心痛点提供了深度的架构级解决方案,并最终输出了一套从 Jenkins 平滑迁移的工程化蓝图。
|
||||
|
||||
## **1\. 候选平台架构拓扑解析**
|
||||
|
||||
在进行多维度技术对比之前,必须深刻理解各候选平台底层架构拓扑的本质差异。CI/CD 工具的架构基因直接决定了其在应对十万级并发构建时的伸缩模型与稳定性边界。
|
||||
GitLab CI 代表了一种高度集成的 GitOps 架构哲学。它将源代码管理(SCM)与持续集成引擎无缝绑定,采用声明式的 YAML 语法进行流水线编排。其底层的执行引擎 GitLab Runner 采用了高度分布式的架构,特别是在配合 Kubernetes Executor 时,能够将每一个构建作业(Job)映射为 Kubernetes 集群中转瞬即逝的 Pod。这种架构最大程度地消除了构建节点的环境污染问题,是目前开源界普适性极强的现代化方案。
|
||||
Tekton 则代表了极致的云原生与 Serverless CI/CD 流派。作为 Continuous Delivery Foundation (CDF) 孵化的核心项目,Tekton 并不提供传统意义上的“中央调度服务器”。相反,它通过引入一系列自定义资源定义(CRD)——例如 Task、Pipeline、TaskRun 和 PipelineRun,直接对 Kubernetes API 进行了功能扩展。在 Tekton 中,CI/CD 的每一个步骤都被抽象为 Kubernetes 的原生对象,由 Tekton Controller 监听这些对象的状态变化并交由 Kubernetes 原生调度器执行。这种无服务器的解耦架构赋予了 Tekton 无与伦比的横向扩展能力。
|
||||
TeamCity 则是商业化集中式编排平台的性能巅峰。与 Jenkins 类似,它采用了中央服务器与分布式构建代理(Agent)的模型,但在架构的健壮性与智能化层面进行了重构。TeamCity 的核心优势在于其引入了 Kotlin 领域特定语言(DSL)作为 Pipeline as Code 的基础,提供了具有编译期类型检查的流水线编写体验。此外,TeamCity 内置了极为复杂的构建链拓扑算法、开箱即用的高级缓存启发式机制以及深度的测试智能分析引擎。
|
||||
|
||||
## **2\. 核心技术维度深度评估**
|
||||
|
||||
在每年十万次构建的压测下,任何微小的架构缺陷都会被无限放大。以下将围绕企业提出的八个核心诉求,对三款候选平台进行颗粒度极细的交叉评估。
|
||||
|
||||
### **2.1 极致的构建能力与多环境隔离**
|
||||
|
||||
企业级应用通常是由多种技术栈组合而成的庞然大物,要求 CI/CD 平台能够在一个流水线中灵活穿插不同版本的语言环境,并能应对极其消耗资源的编译任务。
|
||||
在多语言支持与环境切换方面,容器化是替代 Jenkins 笨重节点管理的首选方案。GitLab CI 允许在 YAML 文件的每个 job 级别通过 image: 关键字动态指定特定的 Docker 镜像。这意味着流水线的第一个阶段可以在搭载 Node.js的容器中执行前端构建,而第二个阶段可以瞬间切换到 OpenJDK的环境中进行后端编译,两者互不干扰。Tekton 将这种容器隔离推向了极致,其架构要求每一个 Task 中的 Step 都必须显式声明一个独立的容器镜像,这些容器在同一个 Pod 中顺序执行,通过共享的数据卷(Workspace)传递产物。TeamCity 则通过原生支持的 Docker Wrapper 技术,允许其代理节点在执行特定构建步骤(如专门的 Maven 或 Node.js 步骤)时,自动拉取并运行在指定的容器环境中,从而彻底避免了单台物理机上环境变量污染的顽疾。
|
||||
针对当下流行的 GraalVM 编译构建,由于其原生镜像(native-image)的生成过程涉及大量的静态代码分析与提前编译(AOT),极度消耗 CPU 与内存资源。在传统的 Jenkins 中,这极易导致节点内存耗尽从而引发 JVM 崩溃。在 Kubernetes 体系下的 GitLab CI 与 Tekton 中,系统架构师必须为处理 GraalVM 的作业显式配置 Kubernetes 的资源请求与限制(requests.memory 与 limits.memory),以防止过度消耗资源被调度器(OOMKilled)强制驱逐。业界最佳实践通常采用多阶段构建:使用庞大的 JDK/GraalVM 基础镜像进行编译,随后将二进制文件复制到极小的运行时镜像(如 distroless 或 Alpine)中以降低安全攻击面。在此维度上,TeamCity 提供了与 JetBrains IDE 的深度集成,不仅支持 GraalVM 构建,还通过预配置的 Docker 容器极大地简化了带有 Docker 的 GraalVM 原生镜像调试体验,甚至支持在 CI 环境中进行汇编级别的原生代码调试。
|
||||
在复杂多分支构建策略方面,GitLab CI 提供了卓越的原生支持,通过 rules 和 workflow:rules 语法,能够基于正则表达式、变量匹配以及分支命名规范,动态决定流水线的执行路径与跳过逻辑。TeamCity 通过其强大的 VCS 根配置与构建触发器系统,能够自动捕获代码库中的新分支并应用配置好的模板,同时通过“并行测试(Parallel Tests)”特性,能够基于历史构建统计数据,智能地将一个庞大的测试套件分割成多个批次,在多个 Agent 上针对不同分支进行并发测试,极大地缩短了反馈周期。Tekton 则通过 EventListeners 和 TriggerBindings 接收来自代码托管平台的 Webhook,利用强大的 CEL(Common Expression Language)拦截器对 payload 进行解析与分支条件判断,进而动态实例化对应的 PipelineRun。
|
||||
|
||||
### **2.2 极其优秀且易于配置的构建缓存机制**
|
||||
|
||||
在十万次构建的体量下,依赖包(如 Node.js 的 node\_modules 或 Java 的 .m2 仓库)的重复下载会造成惊人的网络带宽浪费与时间损耗。优秀的缓存机制是提升流水线效能的关键命题。
|
||||
TeamCity 在这一维度展现出了商业软件极其细腻的工程考量。其原生的“构建缓存(Build Cache)”功能抛弃了对外部对象存储的强依赖,转而采用一种高效的本地发布-订阅(Publisher-Consumer)模型。在 TeamCity 的构建步骤中,可以配置特定的目录(如 node\_modules/)作为缓存目标,并赋予其一个唯一的 Cache Name。当发布者(Publisher)配置成功运行完毕后,TeamCity 会将这些文件打包并作为隐藏构建工件(存储在 .teamcity.build\_cache 目录下)保存在代理服务器的本地磁盘中。在后续的构建或依赖该缓存的消费者(Consumer)构建触发时,TeamCity 的执行引擎会严格按照“解析依赖 \-\> 拉取源码 \-\> 下载缓存 \-\> 执行构建”的顺序,在代码编译前将缓存解压到工作目录。这种设计最大程度地利用了 Agent 的本地 I/O 性能,并通过限制缓存发布条件(例如仅当依赖树哈希值改变时发布)来防止磁盘空间的过度膨胀。
|
||||
相比之下,Tekton 的无服务器架构使得缓存的处理更加复杂。由于执行 Task 的 Pod 在运行结束后即刻被销毁,Tekton 必须依赖 Kubernetes 的持久化卷(Persistent Volume, PV)来实现缓存的跨构建保留。开发者需要定义 PersistentVolumeClaim(PVC)并在 PipelineRun 中将其作为 Workspace 挂载入容器。例如,在执行 Maven 构建时,需通过参数 \-Dmaven.repo.local=$(workspaces.maven-repo-cache.path) 将缓存目录硬编码指向该挂载卷。然而,由于缺乏原生的锁机制,当多个并行的 Task 试图同时读写同一个 PV 上的缓存卷时,会引发严重的数据竞争问题(此问题将在后续“痛点解决方案”章节详述)。
|
||||
GitLab CI 主要依赖于集中式的分布式缓存模型。其架构通过与 Amazon S3、MinIO 或 Google Cloud Storage 的深度集成,在每个 Job 启动时从远端拉取压缩的缓存包,在执行结束后计算差异并推送回对象存储。虽然这种设计完美契合了 Runner 的无状态与横向扩展需求,但在面对数百兆甚至数 GB 的 node\_modules 缓存时,网络传输的延迟往往会抵消缓存本身带来的时间收益。因此,在 GitLab CI 中,必须配合精准的 cache:key 策略(例如基于 package-lock.json 的 SHA 值)来避免不必要的网络开销。
|
||||
|
||||
### **2.3 灵活的构建参数注入与动态交互**
|
||||
|
||||
不可变的基础设施原则要求流水线必须支持在运行时通过外部参数进行动态调整,从而实现“一次构建,多环境部署”。
|
||||
GitLab CI 的“动态子流水线(Dynamic Child Pipelines)”机制在此领域具有统治地位。它允许父流水线通过执行 Python 或 Bash 脚本,基于代码库的实时状态(例如分析哪些微服务的代码发生了变更)动态生成一个 JSON 或 YAML 格式的 CI 配置文件(如 generated-config.yml)22。随后,使用 trigger 关键字和 include: artifact 指令,GitLab 会即时拉起一个完全由上述脚本当场构建的子流水线。这种设计彻底打破了静态 YAML 文件的限制,赋予了流水线极强的适应性,特别适合管理结构复杂的 Monorepo。
|
||||
Tekton 通过其 Custom Resource 的强类型约束实现了严谨的参数传递。每一个 Pipeline 和 Task 都可以定义 params,这些参数在触发时由 PipelineRun 注入。此外,Tekton 引入了 Results 的概念,允许一个 Task 在执行完毕后将特定数据(如镜像哈希值或动态生成的环境标签)作为结果输出,并隐式地传递给流水线拓扑图中的下游 Task。这种机制确保了流水线作为有向无环图(DAG)的数据流向清晰可见,但由于其声明式的特性,要在运行中途生成全新的 Task 节点在技术上是难以实现的。
|
||||
TeamCity 利用其 Kotlin DSL 的图灵完备特性,将动态参数化推向了编程语言的范畴。开发者可以在流水线代码中直接读取环境变量、使用循环结构(for loop)或条件判断,从而在流水线初始化阶段动态生成成百上千个同构的构建配置(BuildType)9。同时,TeamCity 的用户界面支持复杂的自定义参数类型,能够与 HashiCorp Vault 等外部凭据管理系统无缝对接,支持参数的继承与运行时覆写,极大地降低了安全配置的复杂度。
|
||||
|
||||
### **2.4 高并发下的弹性伸缩与智能调度**
|
||||
|
||||
对于万次/年的构建负载,静态的服务器资源分配注定会导致低谷期的资源闲置与高峰期的队列堵塞。
|
||||
GitLab CI 在 Kubernetes Executor 下的调度能力极强,但其真正的弹性威力需要结合 KEDA(Kubernetes Event-driven Autoscaling)来释放。在生产环境的最佳实践中,GitLab Runner 会在一个暴露的端口(如)上提供队列深度等指标,Prometheus 抓取这些指标后,通过 Prometheus Adapter 转化为自定义指标(Custom Metrics),最后由 KEDA 驱动 Horizontal Pod Autoscaler (HPA) 动态增加或减少负责执行具体构建作业的 Pod 数量。此外,GitLab 通过 tags 实现了精准的标签化路由,确保要求特殊硬件(如 GPU 用于机器学习模型训练,或特定网络区域的机器)的作业被准确路由至对应的节点池。
|
||||
Tekton 自身并不负责资源调度,这种“克制”正是其云原生哲学的体现。当 PipelineRun 被提交后,Tekton 控制器仅仅是负责创建对应的 Pod 规范,其余所有的亲和性(Affinity)、容忍度(Tolerations)、节点选择器(NodeSelectors)以及弹性伸缩机制,全部透明地委托给 Kubernetes 原生的调度组件(如 kube-scheduler)与集群自动伸缩器(Cluster Autoscaler 或 Karpenter)来处理。这种机制使得 Tekton 的并发上限直接等于底层 Kubernetes 集群的物理上限。
|
||||
TeamCity 提供了企业级的云配置文件(Cloud Profiles)集成,支持主动与 AWS、Azure、VMware 等公有云及虚拟化平台交互。其智能调度算法会实时监控构建队列,一旦发现队列等待时间超过预设阈值,且现有的 Agent 均被占用,TeamCity 会调用云端 API 自动分配新的虚拟机,部署 Agent 并将其注册到系统中;任务执行完毕后,系统会在设定的空闲时间后自动销毁虚拟机,实现精确的成本控制。其内置的资源耗尽检测机制更能有效预防构建死锁。
|
||||
|
||||
### **2.5 强大的集成与解耦策略(事件回调)**
|
||||
|
||||
大型企业拥有自建的研发效能平台、质量度量看板与统一通知中心。CI/CD 平台必须具备将极其丰富的状态数据向外投递的能力,且不应阻塞主流水线的运行。
|
||||
Tekton 拥有三者中最标准化的事件驱动架构。通过修改 TektonConfig 中的参数,可以全局启用 CloudEvents 投递。这使得任何一个 TaskRun 或 PipelineRun 在经历 Started、Running、Failed 或 Succeeded 状态转变时,Tekton 都会向预设的 Sink(如 Knative Eventing 代理或企业自建的消息队列总线)发送符合 CNCF CloudEvents 标准的 JSON 载荷。外围系统只需要解析这些标准化的载荷,便可精确重构整个集群的交付拓扑图,甚至触发诸如 Argo Workflows 之类的自动响应引擎(例如当构建失败扫描出高危漏洞时自动阻断网络配置)33。
|
||||
GitLab CI 的解耦主要依赖于项目级别的 Webhook。它可以在各种事件(如流水线事件、作业事件、部署事件等)发生时,向外部 URL 推送详细的上下文信息。此外,GitLab 允许在 API 调用触发流水线时传入任意的 CI/CD 变量,并且在流水线内部通过读取预定义的 TRIGGER\_PAYLOAD 变量文件来解析触发源的附加信息,从而实现双向的闭环数据交互。
|
||||
TeamCity 提供了名为“服务消息(Service Messages)”的独特底层机制,它要求在构建脚本的输出中打印特定格式的字符串(例如 \#\#teamcity\[notification...\]),TeamCity 服务器通过持续解析标准输出流来捕获这些指令,从而在不需要硬编码外部 API 的情况下,直接通过系统内置的功能发送 Slack 消息或更新外部状态。对于更复杂的系统对接,TeamCity 拥有高度可定制的 tcWebHooks 插件机制,不仅能监听多种系统级事件,还能利用内嵌的模板引擎组装出极其复杂且符合目标系统要求(如飞书、企业微信、Jira)的 HTTP POST Payload。
|
||||
|
||||
### **2.6 现代化的脚本语法(Pipeline as Code)**
|
||||
|
||||
从 Jenkins 迁移的核心驱动力之一,是摆脱那些冗长、难以调试且极易产生隐蔽报错的 Groovy 代码片段。
|
||||
GitLab CI 将 YAML 作为唯一配置语言,通过严谨的层次结构和预定义的关键词(如 stages、script、artifacts),极大降低了 DevOps 工程师的编写门槛。利用 include 关键字,企业可以建立包含安全扫描、合规检查等核心步骤的公共模板库,供上百个微服务仓库直接引用。
|
||||
Tekton 同样使用 YAML,但其语法的学习曲线极其陡峭。编写一段简单的流水线,通常需要定义多个层级的 Kubernetes Custom Resource,导致配置文件极度冗长。然而,这种“繁琐”换来的是与云原生架构的绝对一致性,它使得基础设施即代码(IaC)工具可以像管理微服务实例一样无缝地管理构建流水线。
|
||||
TeamCity 引入了改变游戏规则的 Kotlin DSL。与基于解释型文本的 YAML 或 Groovy 不同,Kotlin 是强类型的编译型语言。当开发人员在 IntelliJ IDEA 等集成开发环境中编写 TeamCity 流水线时,可以享受到完整的自动补全、语法高亮以及最关键的**编译期静态检查**。任何拼写错误或逻辑悖论在推送到代码库前就会被编译器拦截。此外,TeamCity 支持双向操作:用户可以在友好的图形界面中通过拖拽配置好一条复杂的流水线,然后点击“View as code”,系统会自动生成极其优雅、结构分明的 Kotlin 代码供用户直接提交入库,完美解决了可视化与代码化之间的隔阂。
|
||||
|
||||
### **2.7 开放的 API 与二次开发生态**
|
||||
|
||||
GitLab 提供了完备的 RESTful 与 GraphQL API,从创建仓库、触发流水线到获取具体某一阶段的构建产物,皆可通过接口全自动化完成。
|
||||
Tekton 的 API 即 Kubernetes API。它的生态优势在于依托于 CDF 基金会建立的 Tekton Hub,这里集中了由社区维护的各种高频复用的 Task 定义。通过编写 Kubernetes Operator 或使用 Go 客户端代码库,企业内部的效能平台可以直接将 Tekton 作为底层构建引擎进行深度定制。
|
||||
TeamCity 作为商业产品,除了提供丰富的 REST API 用于触发构建、管理队列负载和获取测试统计数据外,其自身的插件机制也允许开发者使用 Java 构建深度嵌入系统内部生命周期的功能扩展。
|
||||
|
||||
### **2.8 部署架构支持**
|
||||
|
||||
对于大型企业而言,数据主权与代码安全性不可妥协,这要求 CI/CD 工具必须支持完全的私有化(On-Premises)乃至物理隔离的物理气隙(Air-gapped)环境部署。
|
||||
三款工具均完美契合这一要求。GitLab CI 提供成熟的 Omnibus 包或 Helm Chart 进行自托管部署。Tekton 基于 Kubernetes,只需安装官方提供的 Tekton Operator 即可实现在任何本地 Kubernetes 集群(包括 OpenShift)上的私有化落户。TeamCity 则提供了跨平台的二进制安装包与 Docker 镜像,只需配合后端的外部关系型数据库(如 PostgreSQL),即可在物理机或私有云中稳定支撑数以千计的代理节点协同运作。
|
||||
|
||||
## **3\. 痛点深潜:解决企业级构建的核心瓶颈**
|
||||
|
||||
当每年构建频次达到十万次时,普通中小型团队极少遇到的极端边界问题将成为吞噬计算资源的黑洞。在架构设计中,必须针对缓存复用、并发控制与系统集成提供针对性的工程解决方案。
|
||||
|
||||
### **3.1 痛点一:缓存优化极致提效方案**
|
||||
|
||||
无论是 Node.js 庞大的 node\_modules 还是 Java 深不见底的 .m2 依赖树,在多阶段、分布式的容器环境中重建这些依赖将耗费% 到% 的总流水线时长。
|
||||
**解决方案:**
|
||||
|
||||
* **TeamCity (本地 Publisher-Consumer 体系):** 为了避免依赖云存储造成的网络 IO 瓶颈,TeamCity 的缓存策略是在代理本地构建闭环。通过设定一个前置的 Build Cache 发布者步骤,收集特定路径的文件,作为隐藏制品 .teamcity.build\_cache 打包存储在同一 Agent 的安全空间中。后续需要该缓存的构建步骤被调度到该代理时,系统会在源码 Checkout 之前自动将其解压回挂载目录,实现毫秒级的依赖命中。
|
||||
* **Tekton (PVC 挂载复制):** 在 Tekton 的云原生世界中,Task 的 Pod 用完即毁。因此,最佳实践是分配持久卷 PVC 作为 Workspace 传递给 Task。在执行 npm ci 的容器启动前,编写一个前置脚本:首先检查挂载卷中是否存在历史 node\_modules,若有则将其原封不动地 cp(复制)到当前工作目录,再执行安装更新,最后检查改动并将增量变更覆盖回持久卷。这种方法利用了底层存储后端的读写性能,规避了反复的网络下载。
|
||||
|
||||
### **3.2 痛点二:解决 Maven 本地仓库的高并发冲突**
|
||||
|
||||
当业务线大量采用微服务架构时,为了追求极致速度,常常会将多个互相没有依赖关系的 Maven 模块在一个管道的并行阶段中同时触发。然而,原生的 Maven 缺乏对本地仓库 .m2/repository 的跨进程并发写入控制。当多个 JVM 进程同时尝试拉取并向相同位置写入同一个基础依赖 Jar 包或更新 maven-metadata-local.xml 时,极易导致写入竞争,引发经典的 ZipException: zip file is empty(Zip 文件为空)异常,最终导致整个流水线雪崩。
|
||||
**解决方案:**
|
||||
|
||||
* **引入 Redisson 分布式命名锁:** 从 Maven Artifact Resolver.7+ 及 Maven.9.0 开始,架构上通过 maven-resolver-named-locks-redisson 模块彻底解决了这一难题。企业架构师可以在 CI 集群(如 Kubernetes 或专用物理机组)中单独拉起一个常驻的 Redis 实例。在 CI 容器启动时注入特定的环境变量配置,使得所有的并发 Maven 进程在尝试写入底层磁盘前,必须向该 Redis 实例申请对应的依赖包全局写锁。一旦检测到某个基础包正在被另一个构建线程下载,当前线程将进入安全等待状态,从根本上消灭了并发破坏文件完整性的可能。
|
||||
* **使用现代化的 Maven Daemon (mvnd):** 为了兼顾并发安全与性能极速,更前沿的做法是弃用传统的 mvn 命令,转而使用 Apache maven-mvnd。mvnd 创新性地包含了一个基于 GraalVM 构建的极速本地 CLI 客户端与一个常驻后台的 Maven 守护进程池。由于守护进程是一个生命周期跨越多个连续构建任务的后台程序,它不仅能复用 JIT 编译器的热点代码(HotSpot)大幅度降低 JVM 的冷启动惩罚,更重要的是,它在单节点内部隐式地维护了一个同步队列,以极其高效且安全的方式统筹多个子模块的并发下载与写入操作。
|
||||
|
||||
### **3.3 痛点三:外围系统状态回调与全链路可观测性集成**
|
||||
|
||||
随着内部系统的演进,安全扫描告警、部署核准平台、资产管理 CMDB 以及自研的研发效能看板,都需要实时获悉 CI 流水线的精细状态。
|
||||
**解决方案:**
|
||||
|
||||
* **Tekton CloudEvents 推送引擎:** Tekton 利用 CNCF 的标准协议,在其核心配置参数(TektonConfig)中绑定一个远程 Sink URL,开启基于 CloudEvents 格式的高级回调。这并不需要修改流水线内业务代码。任何一个 PipelineRun 或 TaskRun 在生命周期状态变迁时,控制器会自动把诸如“开始时间”、“执行节点”、“阶段性结果输出”等元数据统一封装进标准 JSON 并进行广播。下游平台利用 Serverless 中间件(如 Knative)进行事件的监听与消费,实现了真正意义上的解耦与微服务级的可观测性链路打通。
|
||||
* **TeamCity Service Message 及自定义 Webhook:** 对于 TeamCity,除了传统的利用 tcWebHooks 插件通过直观的 WebUI 配置包含大量 TeamCity 内部变量(如 ${buildStatusUrl})并组装为符合下游格式(例如飞书卡片或 MatterMost 消息)的 payload 以外;它还具备一种原生解耦方案。在其构建脚本中,任何脚本只需要通过 echo "\#\#teamcity\[notification...\]" 将信息抛出至标准输出流。TeamCity 的运行时代理解析器会截获这一特殊字符序列并自动发起向外部 API 的调用。这种方式巧妙地避免了在构建业务代码中硬编码繁杂的 curl 网络交互逻辑与鉴权 Token,大幅提升了脚本的纯粹性与安全性。
|
||||
|
||||
## **4\. 深度横向对比矩阵**
|
||||
|
||||
通过对候选系统各维度的深度穿透,以下汇总提供适用于基础设施选型决策的横向优劣势对比雷达图(以表格呈现):
|
||||
|
||||
| 核心评估维度 | Jenkins (现有基线现状) | GitLab CI (全栈开源方案) | Tekton (云原生 K8s 方案) | TeamCity (商业化对标方案) |
|
||||
| :---- | :---- | :---- | :---- | :---- |
|
||||
| **极致构建支持** | 跨版本工具链常因节点环境变量污染造成构建混乱,插件脆弱。 | **优**;支持基于 Job 级别的独立 image 动态分配隔离环境。 | **优**;颗粒度达到 Step 级别的容器化设计,彻底杜绝环境污染。 | **优**;深度 Docker Wrapper 以及对 GraalVM 本地调试的完美支持。 |
|
||||
| **参数与多分支** | 重度依赖 Parameterized 插件及 Groovy 动态赋值,多分支维护复杂。 | **极优**;支持动态生成 YAML 的子流水线机制与强大的规则引流。 | **良**;支持强类型参数传递,但动态生成拓扑较为困难,高度结构化。 | **极优**;Kotlin DSL 可实现完全通过编程控制的分支遍历及动态实例生成。 |
|
||||
| **弹性服务器调度** | 节点增减依赖静态配置,易形成闲时浪费或忙时死锁。 | **极优**;配合 KEDA 与 Prometheus 监控队列实现秒级的 HPA 弹性伸缩。 | **极优**;依托 Kubernetes 原生调度器及自动伸缩机制处理高并发。 | **优**;通过云配置文件实现按历史运行时长统计与节点模板智能调度伸缩。 |
|
||||
| **现代脚本语法** | 命令式的 Groovy DSL,极其容易发生代码漂移与单点依赖。 | **优**;声明式 YAML 结合强大的 include 模板导入降低维护门槛。 | **良**;YAML 语法(CRD 配置极其繁琐且存在一定学习陡坡)13。 | **极优**;编译型强类型 Kotlin DSL,带来原生 IDE 检查校验与代码安全。 |
|
||||
| **构建缓存机制** | 缺乏统一抽象,通常需自己编写清理与预取脚本,导致磁盘溢出。 | **良**;分布式对象存储模式,依赖网速并可能抵消时间收益。 | **良**;依赖挂载 PVC 卷传递,需配合第三方并发锁工具防损坏。 | **极优**;内置基于本地高性能 I/O 的发布/消费者隐藏工件分发机制。 |
|
||||
| **事件通知回调** | 通常通过插件封装,难以针对复杂的 JSON Payload 进行高度定制。 | **优**;原生的项目级 Webhook,自带大量内置状态变量注入。 | **极优**;基于标准的 CNCF CloudEvents 与外部平台架构打通解耦。 | **极优**;服务消息劫持以及极高模板定制能力的 tcWebHooks 扩展。 |
|
||||
| **生态及私有化** | 插件众多但质量参差不齐,运维黑盒;支持物理机部署。 | 全栈级支持私有化部署;社区覆盖面广但容器要求高。 | 构建于 Tekton Hub 之上;强绑定 Kubernetes 环境方可私有化运行。 | 包含丰富的 JetBrains 集成体系,完备的商业文档;兼容气隙隔离环境。 |
|
||||
| **Maven 并发安全** | 原生构建容易因依赖冲突使 .m2 内的 metadata.xml 发生乱码损坏。 | 基于隔离容器运行,但并行写入同一共享网络卷仍存在隐患。 | 面临同一挂载 Workspace 下的严重竞争,需使用 Redisson 分布式锁。 | Agent 级的缓存同步解压隔离了直接的目录写入冲突,稳定性较好。 |
|
||||
|
||||
## **5\. 迁移成本评估与架构平滑过渡建议**
|
||||
|
||||
在每年十万次的巨大吞吐量下进行 CI/CD 引擎的核心替换,等同于在高速飞行的客机上更换发动机。总体拥有成本(TCO)不仅包括新软件的基础设施支出,更在于对已有庞大流水线资产的重构代价与工程师团队的心智转换成本。
|
||||
|
||||
### **5.1 架构改造成本评估**
|
||||
|
||||
从传统的有状态节点迁移至现代的容器原生平台:
|
||||
|
||||
* **迁移至 Tekton:** 基础设施成本将大幅转移至 Kubernetes 集群的管理与存储供应上。由于所有作业产生临时 Pod,不仅要求极速的网络拉取能力,还需要为其调配极高读写 IOPs 的存储类(StorageClass)来支撑 Workspace 的创建。架构师必须拥有深厚的 Kubernetes 知识储备方能掌控其排障逻辑。
|
||||
* **迁移至 GitLab CI:** 需投入较高成本规划并运维 GitLab 高可用实例。实施 KEDA 等组件实现真正弹性的同时,也要建设大型的 MinIO / S3 等对象存储集群以承载缓存与产物的网络吞吐。
|
||||
* **迁移至 TeamCity:** 其物理环境拓扑与 Jenkins 最为相似。改造成本主要体现在合理规划各类云代理节点(Agent Pool)并设计基于本地代理磁盘特性的缓存清理策略。商业授权许可费用(License)将成为长期的运营支出。
|
||||
|
||||
### **5.2 脚本重写成本及学习曲线**
|
||||
|
||||
* **向 YAML 声明式配置转换(GitLab CI / Tekton):** DevOps 团队必须摒弃通过 Groovy 撰写长篇大论 if-else 逻辑的习惯。这意味着流水线底层执行脚本需大量前置打包至容器的 Entrypoint 中,或是剥离出单纯的 Bash 脚本库。面对 Tekton,团队还必须经历一场艰难的学习,克服 Kubernetes 大量缩进和属性嵌套带来的排错地狱。
|
||||
* **向 Kotlin DSL 演进(TeamCity):** 对于主要以 Java 栈开发为主的企业,Kotlin 语法的亲和力极强。重写过程不仅不会增加负担,反而将流水线管理真正提升至与业务代码同等级别的软件工程标准。系统在 IDEA 中提供的实时编译、方法提示能够极大幅度压低排障所耗费的时间成本。此外,UI 配置与 DSL 代码间的双向转换引擎,让初学者可以通过“所见即所得”拖拽出雏形再转为代码的方式,完成极其平滑的能力进阶。
|
||||
|
||||
### **5.3 绞杀者无缝平滑过渡策略 (The Strangler Fig Pattern)**
|
||||
|
||||
在执行最终迁移时,切忌执行“休克疗法(Big Bang)”。建议采用绞杀者模式实现渐进式升级:
|
||||
|
||||
1. **基础设施剥离与容器化重构:** 优先将现有的 Jenkins 流水线任务内聚化。原本在 Jenkins 内散乱分布的构建指令,全部封装转换为标准的 Dockerfile 及独立的入口执行脚本。让 Jenkins 退化为仅执行 docker run 命令的空壳系统。
|
||||
2. **并行双跑期:** 选择少数影响面小、无外部系统强依赖的边缘微服务项目作为破局点,在 Jenkins 与新选型系统(如 TeamCity)上同时挂载相同的 VCS 触发器。验证新平台的资源并发分配能力,在相同的代码提交下对构建时长、测试报告采集率进行定量对比,确保 TeamCity 甚至展现出更高的并行处理吞吐。
|
||||
3. **异构桥接与兼容层(针对 GitLab 候选):** 若核心复杂管线短期内难以完全从 Groovy 抽离,可通过采用 Jenkinsfile Wrapper 类工具。它能够在 GitLab CI 等现代化工作流的一个 Job 内,拉起一个包含所有必选插件环境的轻量级 Jenkins 容器实例来运行旧版代码。通过这种“容器套娃”手段,能够为团队重构最为复杂的遗留系统争取宝贵的重写时间窗口。
|
||||
|
||||
综合来看,若企业正在执行激进的云原生架构转型并在微服务治理上深度依赖 Kubernetes 体系,基于标准化 CloudEvents 且无服务器的 **Tekton** 是构建未来研发底座的长远方案;若追求开源免许可费且偏重极其直观灵巧的 YAML 维护体验,具备原生弹性扩展机制的 **GitLab CI** 是最稳妥的高分选项;而在追求极致开发体验、意图彻底消灭脚本维护黑洞,并确保从传统架构平稳过渡的稳健型企业环境中,兼具智能缓存隔离与 Kotlin 强类型编排支持的 **TeamCity**,无疑能够成为托举其十万级甚至百万级构建并发的最可靠商业基石。
|
||||
30670
18-基础架构及交付部署特战队/1-项目部署-管理/docs/airscript-official-doc.md
Normal file
30670
18-基础架构及交付部署特战队/1-项目部署-管理/docs/airscript-official-doc.md
Normal file
File diff suppressed because it is too large
Load Diff
1013
18-基础架构及交付部署特战队/1-项目部署-管理/docs/airscript-spec-doc.md
Normal file
1013
18-基础架构及交付部署特战队/1-项目部署-管理/docs/airscript-spec-doc.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,646 @@
|
||||
/**
|
||||
* ============================================================
|
||||
* 金山多维表格 AirScript - 完整字段定义导出 + 阶段日志落表
|
||||
* ============================================================
|
||||
*
|
||||
* 目标:
|
||||
* 1) 导出指定 7 张表的字段定义(含完整 JSON)
|
||||
* 2) 解决控制台日志分阶段现象: 同步写入日志表
|
||||
*
|
||||
* 参考(来自本项目离线文档):
|
||||
* - Sheet.GetFields()
|
||||
* - FieldDescriptors / FieldDescriptors.Item()
|
||||
* - Application.Sheet.CreateSheet()
|
||||
* - View.RecordRange().Value
|
||||
*/
|
||||
|
||||
// ----------------------------
|
||||
// 配置区
|
||||
// ----------------------------
|
||||
var TARGET_TABLES = [
|
||||
'项目基本信息表',
|
||||
'项目本地化部署升级信息表',
|
||||
'项目本地化部署状态表',
|
||||
'项目部署环境信息表',
|
||||
'项目部署网络信息表',
|
||||
'项目部署中间件信息表',
|
||||
'项目部署业务信息表',
|
||||
'持续交付部署表',
|
||||
'持续交付部署状态表',
|
||||
];
|
||||
|
||||
var EXPORT_SCHEMA_SHEET_NAME = '数据导出-字段定义';
|
||||
var EXPORT_LOG_SHEET_NAME = '数据导出-执行日志';
|
||||
var MAX_JSON_CELL_LENGTH = 30000;
|
||||
|
||||
// ----------------------------
|
||||
// 运行态数据
|
||||
// ----------------------------
|
||||
var logRows = [];
|
||||
var schemaRows = [];
|
||||
var runId = buildRunId();
|
||||
|
||||
// ----------------------------
|
||||
// 工具函数
|
||||
// ----------------------------
|
||||
function buildRunId() {
|
||||
var d = new Date();
|
||||
function pad(n) { return n < 10 ? '0' + n : '' + n; }
|
||||
return (
|
||||
d.getFullYear() +
|
||||
pad(d.getMonth() + 1) +
|
||||
pad(d.getDate()) + '_' +
|
||||
pad(d.getHours()) +
|
||||
pad(d.getMinutes()) +
|
||||
pad(d.getSeconds())
|
||||
);
|
||||
}
|
||||
|
||||
function nowText() {
|
||||
var d = new Date();
|
||||
function pad(n) { return n < 10 ? '0' + n : '' + n; }
|
||||
return (
|
||||
d.getFullYear() + '-' +
|
||||
pad(d.getMonth() + 1) + '-' +
|
||||
pad(d.getDate()) + ' ' +
|
||||
pad(d.getHours()) + ':' +
|
||||
pad(d.getMinutes()) + ':' +
|
||||
pad(d.getSeconds())
|
||||
);
|
||||
}
|
||||
|
||||
function addLog(stage, level, message, detail) {
|
||||
var row = {
|
||||
runId: runId,
|
||||
time: nowText(),
|
||||
stage: stage,
|
||||
level: level,
|
||||
message: message,
|
||||
detail: detail || ''
|
||||
};
|
||||
logRows.push(row);
|
||||
|
||||
var line = '[' + row.time + '][' + row.stage + '][' + row.level + '] ' + row.message;
|
||||
if (row.detail) line += ' | ' + row.detail;
|
||||
console.log(line);
|
||||
}
|
||||
|
||||
function safeGet(obj, key) {
|
||||
try {
|
||||
return obj[key];
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function pickFirst(obj, keys, fallback) {
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var v = safeGet(obj, keys[i]);
|
||||
if (v !== undefined && v !== null && v !== '') {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function safeStringify(value) {
|
||||
try {
|
||||
return JSON.stringify(value);
|
||||
} catch (e) {
|
||||
try {
|
||||
return String(value);
|
||||
} catch (e2) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeCellText(value) {
|
||||
if (value === undefined || value === null) return '';
|
||||
|
||||
var text = '';
|
||||
if (typeof value === 'string') {
|
||||
text = value;
|
||||
} else if (typeof value === 'number' || typeof value === 'boolean') {
|
||||
text = String(value);
|
||||
} else {
|
||||
text = safeStringify(value);
|
||||
}
|
||||
|
||||
if (text.length > MAX_JSON_CELL_LENGTH) {
|
||||
return text.substring(0, MAX_JSON_CELL_LENGTH) + ' ...(truncated)';
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
function findSheetByName(sheetName) {
|
||||
var sheets = Application.Sheets;
|
||||
var count = sheets.Count;
|
||||
for (var i = 1; i <= count; i++) {
|
||||
var s = sheets.Item(i);
|
||||
if (s.Name === sheetName) return s;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function extractFieldArrayFromGetFields(rawResult) {
|
||||
if (!rawResult) return [];
|
||||
if (Array.isArray(rawResult)) return rawResult;
|
||||
if (rawResult.fields && Array.isArray(rawResult.fields)) return rawResult.fields;
|
||||
if (rawResult.Fields && Array.isArray(rawResult.Fields)) return rawResult.Fields;
|
||||
return [];
|
||||
}
|
||||
|
||||
function snapshotFieldDescriptor(fd) {
|
||||
return {
|
||||
Id: pickFirst(fd, ['Id', 'id'], ''),
|
||||
Name: pickFirst(fd, ['Name', 'name'], ''),
|
||||
Type: pickFirst(fd, ['Type', 'type'], ''),
|
||||
Description: pickFirst(fd, ['Description', 'description'], ''),
|
||||
DefaultVal: pickFirst(fd, ['DefaultVal', 'defaultVal'], ''),
|
||||
DefaultValType: pickFirst(fd, ['DefaultValType', 'defaultValType'], ''),
|
||||
NumberFormat: pickFirst(fd, ['NumberFormat', 'numberFormat'], ''),
|
||||
ValueUnique: pickFirst(fd, ['ValueUnique', 'IsValueUnique'], ''),
|
||||
SyncField: pickFirst(fd, ['SyncField', 'IsSyncField'], ''),
|
||||
Width: pickFirst(fd, ['Width', 'width'], ''),
|
||||
Button: pickFirst(fd, ['Button'], null),
|
||||
Address: pickFirst(fd, ['Address'], null),
|
||||
Cascade: pickFirst(fd, ['Cascade'], null),
|
||||
Contact: pickFirst(fd, ['Contact'], null),
|
||||
Date: pickFirst(fd, ['Date'], null),
|
||||
Watch: pickFirst(fd, ['Watch'], null),
|
||||
Formula: pickFirst(fd, ['Formula'], null),
|
||||
Lookup: pickFirst(fd, ['Lookup'], null),
|
||||
Link: pickFirst(fd, ['Link'], null),
|
||||
Automation: pickFirst(fd, ['Automation'], null),
|
||||
Attachment: pickFirst(fd, ['Attachment'], null),
|
||||
Url: pickFirst(fd, ['Url'], null),
|
||||
Number: pickFirst(fd, ['Number'], null),
|
||||
Select: pickFirst(fd, ['Select'], null),
|
||||
Rating: pickFirst(fd, ['Rating'], null)
|
||||
};
|
||||
}
|
||||
|
||||
function chooseRawField(rawFields, descriptorSnap, index) {
|
||||
if (!rawFields || rawFields.length === 0) return null;
|
||||
|
||||
var id = descriptorSnap.Id;
|
||||
var name = descriptorSnap.Name;
|
||||
|
||||
for (var i = 0; i < rawFields.length; i++) {
|
||||
var rf = rawFields[i];
|
||||
var rid = pickFirst(rf, ['id', 'Id', 'fieldId', 'FieldId'], '');
|
||||
if (id && rid && String(rid) === String(id)) return rf;
|
||||
}
|
||||
for (var j = 0; j < rawFields.length; j++) {
|
||||
var rf2 = rawFields[j];
|
||||
var rname = pickFirst(rf2, ['name', 'Name', 'fieldName', 'FieldName'], '');
|
||||
if (name && rname && String(rname) === String(name)) return rf2;
|
||||
}
|
||||
if (index >= 0 && index < rawFields.length) return rawFields[index];
|
||||
return null;
|
||||
}
|
||||
|
||||
function ensureOutputSheet(sheetName, fields) {
|
||||
var old = findSheetByName(sheetName);
|
||||
if (old) {
|
||||
addLog('准备输出表', 'INFO', '删除旧输出表', sheetName);
|
||||
try {
|
||||
old.Delete();
|
||||
} catch (eDel) {
|
||||
addLog('准备输出表', 'WARN', '删除旧输出表失败', sheetName + ' | ' + eDel.message);
|
||||
}
|
||||
}
|
||||
|
||||
var createResult = Application.Sheet.CreateSheet({
|
||||
Name: sheetName,
|
||||
Views: [{ name: '默认视图', type: 'Grid' }],
|
||||
Fields: fields
|
||||
});
|
||||
|
||||
var newId = createResult ? createResult.id : '';
|
||||
addLog('准备输出表', 'INFO', '已创建输出表', sheetName + ' | SheetId=' + newId);
|
||||
|
||||
try { Time.sleep(300); } catch (eSleep) { }
|
||||
|
||||
var created = findSheetByName(sheetName);
|
||||
if (!created) {
|
||||
throw new Error('未找到新创建的输出表: ' + sheetName);
|
||||
}
|
||||
return created;
|
||||
}
|
||||
|
||||
function buildFieldSelectors(fields) {
|
||||
var selectors = [];
|
||||
for (var i = 0; i < fields.length; i++) {
|
||||
selectors.push('@' + fields[i].name);
|
||||
}
|
||||
return selectors;
|
||||
}
|
||||
|
||||
function writeRowsByCellFallback(view, rows, progressStep) {
|
||||
var total = rows.length;
|
||||
var step = progressStep || 20;
|
||||
|
||||
for (var i = 0; i < total; i++) {
|
||||
var row = rows[i];
|
||||
var rowNo = i + 1;
|
||||
try {
|
||||
for (var col = 0; col < row.length; col++) {
|
||||
view.RecordRange(rowNo, col + 1).Value = row[col];
|
||||
}
|
||||
} catch (eWrite) {
|
||||
addLog('写入输出', 'ERROR', '写入行失败', 'row=' + rowNo + ' | ' + eWrite.message);
|
||||
}
|
||||
|
||||
if ((i + 1) % step === 0 || i === total - 1) {
|
||||
addLog('写入输出', 'INFO', '写入进度', (i + 1) + '/' + total);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getViewRecordCount(view) {
|
||||
try {
|
||||
var c = view.RecordRange.Count;
|
||||
if (c === undefined || c === null || c === '') return 0;
|
||||
return Number(c) || 0;
|
||||
} catch (e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function verifyWriteResult(sheet, expectedRows) {
|
||||
try {
|
||||
var view = sheet.Views(1);
|
||||
try { Time.sleep(300); } catch (eSleep1) { }
|
||||
var count = getViewRecordCount(view);
|
||||
if (count < expectedRows) {
|
||||
try { Time.sleep(800); } catch (eSleep2) { }
|
||||
count = getViewRecordCount(view);
|
||||
}
|
||||
return {
|
||||
ok: count >= expectedRows,
|
||||
count: count
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
ok: false,
|
||||
count: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function writeRowsToSheet(sheet, fields, rows, progressStep) {
|
||||
var total = rows.length;
|
||||
if (total === 0) {
|
||||
addLog('写入输出', 'INFO', '无数据可写入', sheet.Name);
|
||||
return;
|
||||
}
|
||||
|
||||
var view = sheet.Views(1);
|
||||
var selectors = buildFieldSelectors(fields);
|
||||
var step = progressStep || 20;
|
||||
var batchSize = 50;
|
||||
|
||||
try {
|
||||
// 某些环境下先激活再写入更稳定
|
||||
sheet.Activate();
|
||||
view.Activate();
|
||||
} catch (eActive) { }
|
||||
|
||||
// 方案A: 优先使用 CreateRecords 创建记录(与你现有可用脚本保持一致)
|
||||
try {
|
||||
addLog('写入输出', 'INFO', '使用 CreateRecords 批量写入', sheet.Name + ' | rows=' + total);
|
||||
|
||||
for (var crStart = 0; crStart < total; crStart += batchSize) {
|
||||
var crEnd = crStart + batchSize;
|
||||
if (crEnd > total) crEnd = total;
|
||||
|
||||
var recordBatch = [];
|
||||
for (var r = crStart; r < crEnd; r++) {
|
||||
var oneRow = rows[r];
|
||||
var fieldsObj = {};
|
||||
for (var c = 0; c < fields.length; c++) {
|
||||
fieldsObj[fields[c].name] = normalizeCellText(oneRow[c]);
|
||||
}
|
||||
recordBatch.push({ fields: fieldsObj });
|
||||
}
|
||||
|
||||
var createRes = Application.Record.CreateRecords({
|
||||
SheetId: sheet.Id,
|
||||
Records: recordBatch
|
||||
});
|
||||
|
||||
if (createRes && createRes.code !== undefined && createRes.code !== 0) {
|
||||
var createMsg = pickFirst(createRes, ['Message', 'message'], 'CreateRecords失败');
|
||||
throw new Error('code=' + createRes.code + ', message=' + createMsg + ', start=' + (crStart + 1));
|
||||
}
|
||||
|
||||
if (crEnd % step === 0 || crEnd === total) {
|
||||
addLog('写入输出', 'INFO', '写入进度', crEnd + '/' + total);
|
||||
}
|
||||
}
|
||||
|
||||
var verifyA = verifyWriteResult(sheet, total);
|
||||
if (verifyA.ok) {
|
||||
addLog('写入输出', 'INFO', 'CreateRecords 写入校验通过', sheet.Name + ' | count=' + verifyA.count);
|
||||
return;
|
||||
}
|
||||
addLog('写入输出', 'WARN', 'CreateRecords 写入后记录数不足,回退 SetValues', sheet.Name + ' | count=' + verifyA.count + ', expected=' + total);
|
||||
} catch (eCreateRecords) {
|
||||
addLog('写入输出', 'WARN', 'CreateRecords 失败,回退 SetValues', eCreateRecords.message);
|
||||
}
|
||||
|
||||
// 方案B: SetValues 批量写入
|
||||
try {
|
||||
addLog('写入输出', 'INFO', '使用 SetValues 批量写入', sheet.Name + ' | rows=' + total);
|
||||
|
||||
for (var start = 1; start <= total; start += batchSize) {
|
||||
var end = start + batchSize - 1;
|
||||
if (end > total) end = total;
|
||||
|
||||
var chunk = rows.slice(start - 1, end);
|
||||
var range = null;
|
||||
var res = null;
|
||||
|
||||
try {
|
||||
range = view.RecordRange(String(start) + ':' + String(end), selectors);
|
||||
res = range.SetValues(chunk, true);
|
||||
} catch (eViewSet) {
|
||||
// 按官方示例,兼容 Application.RecordRange 方式
|
||||
range = Application.RecordRange(String(start) + ':' + String(end), selectors);
|
||||
res = range.SetValues(chunk, true);
|
||||
}
|
||||
|
||||
if (res && res.code !== undefined && res.code !== 0) {
|
||||
var msg = pickFirst(res, ['Message', 'message'], 'SetValues失败');
|
||||
throw new Error('code=' + res.code + ', message=' + msg + ', range=' + start + ':' + end);
|
||||
}
|
||||
|
||||
if (end % step === 0 || end === total) {
|
||||
addLog('写入输出', 'INFO', '写入进度', end + '/' + total);
|
||||
}
|
||||
}
|
||||
|
||||
var verifyB = verifyWriteResult(sheet, total);
|
||||
if (verifyB.ok) {
|
||||
addLog('写入输出', 'INFO', 'SetValues 写入校验通过', sheet.Name + ' | count=' + verifyB.count);
|
||||
return;
|
||||
}
|
||||
addLog('写入输出', 'WARN', 'SetValues 写入后记录数不足,回退逐格写入', sheet.Name + ' | count=' + verifyB.count + ', expected=' + total);
|
||||
} catch (eSetValues) {
|
||||
addLog('写入输出', 'WARN', 'SetValues 批量写入失败,回退逐格写入', eSetValues.message);
|
||||
}
|
||||
|
||||
// 方案C: 逐格写入兜底
|
||||
try {
|
||||
writeRowsByCellFallback(view, rows, step);
|
||||
var verifyC = verifyWriteResult(sheet, total);
|
||||
if (verifyC.ok) {
|
||||
addLog('写入输出', 'INFO', '逐格写入校验通过', sheet.Name + ' | count=' + verifyC.count);
|
||||
return;
|
||||
}
|
||||
addLog('写入输出', 'ERROR', '逐格写入后记录数仍不足', sheet.Name + ' | count=' + verifyC.count + ', expected=' + total);
|
||||
} catch (eFallback) {
|
||||
addLog('写入输出', 'ERROR', '逐格写入失败', eFallback.message);
|
||||
}
|
||||
}
|
||||
|
||||
function pushSchemaRow(obj) {
|
||||
schemaRows.push([
|
||||
obj.runId,
|
||||
obj.tableName,
|
||||
obj.sheetId,
|
||||
obj.fieldIndex,
|
||||
obj.fieldId,
|
||||
obj.fieldName,
|
||||
obj.fieldType,
|
||||
obj.fieldDescription,
|
||||
obj.defaultVal,
|
||||
obj.defaultValType,
|
||||
obj.numberFormat,
|
||||
obj.valueUnique,
|
||||
obj.syncField,
|
||||
obj.source,
|
||||
obj.fullDefinitionJson,
|
||||
obj.rawGetFieldsJson
|
||||
]);
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// 主流程
|
||||
// ----------------------------
|
||||
console.log('======================================================');
|
||||
console.log('AirScript 完整字段定义导出 + 阶段日志落表');
|
||||
console.log('RunId: ' + runId);
|
||||
console.log('======================================================');
|
||||
|
||||
addLog('初始化', 'INFO', '脚本启动', '目标表数量=' + TARGET_TABLES.length);
|
||||
|
||||
var targetSheets = [];
|
||||
for (var t = 0; t < TARGET_TABLES.length; t++) {
|
||||
var tableName = TARGET_TABLES[t];
|
||||
var sheet = findSheetByName(tableName);
|
||||
if (sheet) {
|
||||
targetSheets.push(sheet);
|
||||
addLog('表扫描', 'INFO', '找到目标表', tableName + ' | SheetId=' + sheet.Id);
|
||||
} else {
|
||||
addLog('表扫描', 'WARN', '目标表不存在', tableName);
|
||||
}
|
||||
}
|
||||
|
||||
addLog('表扫描', 'INFO', '有效目标表统计', String(targetSheets.length));
|
||||
|
||||
for (var s = 0; s < targetSheets.length; s++) {
|
||||
var currentSheet = targetSheets[s];
|
||||
var currentName = currentSheet.Name;
|
||||
var currentId = currentSheet.Id;
|
||||
|
||||
addLog('字段采集', 'INFO', '开始采集', currentName + ' | SheetId=' + currentId);
|
||||
|
||||
var rawGetFieldsResult = null;
|
||||
var rawFields = [];
|
||||
var getFieldsErr = '';
|
||||
|
||||
try {
|
||||
rawGetFieldsResult = Application.Field.GetFields({ SheetId: currentId });
|
||||
rawFields = extractFieldArrayFromGetFields(rawGetFieldsResult);
|
||||
addLog(
|
||||
'字段采集',
|
||||
'INFO',
|
||||
'GetFields 成功',
|
||||
currentName + ' | fields=' + rawFields.length
|
||||
);
|
||||
} catch (eGet) {
|
||||
getFieldsErr = eGet.message || String(eGet);
|
||||
addLog('字段采集', 'WARN', 'GetFields 失败', currentName + ' | ' + getFieldsErr);
|
||||
}
|
||||
|
||||
var descriptors = null;
|
||||
var descriptorCount = 0;
|
||||
|
||||
try {
|
||||
descriptors = currentSheet.FieldDescriptors;
|
||||
descriptorCount = descriptors.Count || 0;
|
||||
addLog(
|
||||
'字段采集',
|
||||
'INFO',
|
||||
'FieldDescriptors 可用',
|
||||
currentName + ' | count=' + descriptorCount
|
||||
);
|
||||
} catch (eDesc) {
|
||||
addLog('字段采集', 'ERROR', 'FieldDescriptors 失败', currentName + ' | ' + eDesc.message);
|
||||
}
|
||||
|
||||
if (descriptorCount > 0) {
|
||||
for (var i = 1; i <= descriptorCount; i++) {
|
||||
try {
|
||||
var fd = descriptors.Item(i);
|
||||
var snap = snapshotFieldDescriptor(fd);
|
||||
var rawMatched = chooseRawField(rawFields, snap, i - 1);
|
||||
|
||||
var fullDefObj = {
|
||||
fromFieldDescriptor: snap,
|
||||
fromGetFields: rawMatched
|
||||
};
|
||||
|
||||
pushSchemaRow({
|
||||
runId: runId,
|
||||
tableName: currentName,
|
||||
sheetId: String(currentId),
|
||||
fieldIndex: String(i),
|
||||
fieldId: normalizeCellText(snap.Id),
|
||||
fieldName: normalizeCellText(snap.Name),
|
||||
fieldType: normalizeCellText(snap.Type),
|
||||
fieldDescription: normalizeCellText(snap.Description),
|
||||
defaultVal: normalizeCellText(snap.DefaultVal),
|
||||
defaultValType: normalizeCellText(snap.DefaultValType),
|
||||
numberFormat: normalizeCellText(snap.NumberFormat),
|
||||
valueUnique: normalizeCellText(snap.ValueUnique),
|
||||
syncField: normalizeCellText(snap.SyncField),
|
||||
source: rawMatched ? 'FieldDescriptors+GetFields' : 'FieldDescriptors',
|
||||
fullDefinitionJson: normalizeCellText(fullDefObj),
|
||||
rawGetFieldsJson: normalizeCellText(rawMatched)
|
||||
});
|
||||
} catch (eItem) {
|
||||
addLog(
|
||||
'字段采集',
|
||||
'ERROR',
|
||||
'读取字段描述失败',
|
||||
currentName + ' | index=' + i + ' | ' + eItem.message
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if (rawFields.length > 0) {
|
||||
for (var r = 0; r < rawFields.length; r++) {
|
||||
var rf = rawFields[r];
|
||||
var rfId = pickFirst(rf, ['id', 'Id', 'fieldId', 'FieldId'], '');
|
||||
var rfName = pickFirst(rf, ['name', 'Name', 'fieldName', 'FieldName'], '');
|
||||
var rfType = pickFirst(rf, ['type', 'Type', 'fieldType', 'FieldType'], '');
|
||||
|
||||
pushSchemaRow({
|
||||
runId: runId,
|
||||
tableName: currentName,
|
||||
sheetId: String(currentId),
|
||||
fieldIndex: String(r + 1),
|
||||
fieldId: normalizeCellText(rfId),
|
||||
fieldName: normalizeCellText(rfName),
|
||||
fieldType: normalizeCellText(rfType),
|
||||
fieldDescription: '',
|
||||
defaultVal: '',
|
||||
defaultValType: '',
|
||||
numberFormat: '',
|
||||
valueUnique: '',
|
||||
syncField: '',
|
||||
source: 'GetFields',
|
||||
fullDefinitionJson: normalizeCellText(rf),
|
||||
rawGetFieldsJson: normalizeCellText(rf)
|
||||
});
|
||||
}
|
||||
} else {
|
||||
pushSchemaRow({
|
||||
runId: runId,
|
||||
tableName: currentName,
|
||||
sheetId: String(currentId),
|
||||
fieldIndex: '0',
|
||||
fieldId: '',
|
||||
fieldName: '(未获取到字段定义)',
|
||||
fieldType: '',
|
||||
fieldDescription: getFieldsErr ? 'GetFields错误: ' + getFieldsErr : '',
|
||||
defaultVal: '',
|
||||
defaultValType: '',
|
||||
numberFormat: '',
|
||||
valueUnique: '',
|
||||
syncField: '',
|
||||
source: 'EMPTY',
|
||||
fullDefinitionJson: '',
|
||||
rawGetFieldsJson: ''
|
||||
});
|
||||
}
|
||||
|
||||
addLog('字段采集', 'INFO', '完成采集', currentName);
|
||||
}
|
||||
|
||||
addLog('汇总', 'INFO', '字段记录总数', String(schemaRows.length));
|
||||
|
||||
var schemaFieldDefs = [
|
||||
{ name: '执行批次', type: 'MultiLineText' },
|
||||
{ name: '所属表名', type: 'MultiLineText' },
|
||||
{ name: 'SheetId', type: 'MultiLineText' },
|
||||
{ name: '字段序号', type: 'MultiLineText' },
|
||||
{ name: '字段Id', type: 'MultiLineText' },
|
||||
{ name: '字段名称', type: 'MultiLineText' },
|
||||
{ name: '字段类型', type: 'MultiLineText' },
|
||||
{ name: '字段描述', type: 'MultiLineText' },
|
||||
{ name: '默认值', type: 'MultiLineText' },
|
||||
{ name: '默认值类型', type: 'MultiLineText' },
|
||||
{ name: '数字格式', type: 'MultiLineText' },
|
||||
{ name: '唯一值', type: 'MultiLineText' },
|
||||
{ name: '同步字段', type: 'MultiLineText' },
|
||||
{ name: '定义来源', type: 'MultiLineText' },
|
||||
{ name: '字段完整定义JSON', type: 'MultiLineText' },
|
||||
{ name: 'GetFields原始JSON', type: 'MultiLineText' }
|
||||
];
|
||||
|
||||
var schemaSheet = ensureOutputSheet(EXPORT_SCHEMA_SHEET_NAME, schemaFieldDefs);
|
||||
|
||||
addLog('写入输出', 'INFO', '开始写入字段定义表', EXPORT_SCHEMA_SHEET_NAME);
|
||||
writeRowsToSheet(schemaSheet, schemaFieldDefs, schemaRows, 25);
|
||||
addLog('写入输出', 'INFO', '字段定义表写入完成', EXPORT_SCHEMA_SHEET_NAME);
|
||||
|
||||
// 在写日志表之前先记一条,确保它也能落表
|
||||
addLog('写入输出', 'INFO', '准备写入日志表', EXPORT_LOG_SHEET_NAME);
|
||||
|
||||
var logFieldDefs = [
|
||||
{ name: '执行批次', type: 'MultiLineText' },
|
||||
{ name: '时间', type: 'MultiLineText' },
|
||||
{ name: '阶段', type: 'MultiLineText' },
|
||||
{ name: '级别', type: 'MultiLineText' },
|
||||
{ name: '消息', type: 'MultiLineText' },
|
||||
{ name: '明细', type: 'MultiLineText' }
|
||||
];
|
||||
|
||||
var logSheet = ensureOutputSheet(EXPORT_LOG_SHEET_NAME, logFieldDefs);
|
||||
|
||||
var logRowsForSheet = [];
|
||||
for (var l = 0; l < logRows.length; l++) {
|
||||
var lr = logRows[l];
|
||||
logRowsForSheet.push([
|
||||
lr.runId,
|
||||
lr.time,
|
||||
lr.stage,
|
||||
lr.level,
|
||||
lr.message,
|
||||
lr.detail
|
||||
]);
|
||||
}
|
||||
|
||||
writeRowsToSheet(logSheet, logFieldDefs, logRowsForSheet, 30);
|
||||
|
||||
console.log('');
|
||||
console.log('======================================================');
|
||||
console.log('导出完成');
|
||||
console.log('RunId: ' + runId);
|
||||
console.log('字段定义表: ' + EXPORT_SCHEMA_SHEET_NAME + ' | 行数=' + schemaRows.length);
|
||||
console.log('执行日志表: ' + EXPORT_LOG_SHEET_NAME + ' | 行数=' + logRowsForSheet.length);
|
||||
console.log('======================================================');
|
||||
@@ -7,8 +7,8 @@
|
||||
|
||||
### 项目基本信息表 <行业组人员填写> (todo: 需要能够通过项目名称搜索)
|
||||
1. 项目名称
|
||||
2. 行业组人员名称
|
||||
3. 行业组人员电话
|
||||
2. 行业组接口人
|
||||
3. 行业组接口人电话
|
||||
4. 省份 <下拉选择>
|
||||
5. 城市 <下拉选择>
|
||||
6. 部署飞服平台 <选择>
|
||||
@@ -92,11 +92,18 @@
|
||||
|
||||
上面每个中间件都具备如下的属性
|
||||
1. 是否暴露公网
|
||||
2. 公网端口
|
||||
3. 内网IP
|
||||
4. 内网端口
|
||||
5. 用户名
|
||||
6. 密码 <不允许填写>
|
||||
2. 公网IP<唯一>
|
||||
3. 内网IP<唯一>
|
||||
2. 公网端口A
|
||||
4. 内网端口A
|
||||
5. 公网端口B
|
||||
6. 内网端口B
|
||||
7. 公网端口C
|
||||
8. 内网端口C
|
||||
9. 公网端口D
|
||||
10. 内网端口D
|
||||
11. 用户名
|
||||
12. 密码 <不允许填写>
|
||||
|
||||
### 项目部署业务信息表 <交付部署特战队填写> (todo: 此表如何与项目进行关联)
|
||||
1. 微服务名称
|
||||
@@ -129,12 +136,12 @@
|
||||
├── 项目部署环境信息表(每台主机一行)
|
||||
├── 项目部署网络信息表(每次部署一行)
|
||||
├── 项目部署中间件信息表(每个中间件一行,共7行)
|
||||
└── 项目部署业务信息表(每个微服务一行)
|
||||
└── 项目部署业务信息表(每个微服务一行,不同平台的微服务不同)
|
||||
|
||||
同时在「项目本地化部署状态表」里对每个子表建立双向关联的反向字段,这样在部署状态表中可以直接看到这条部署关联了哪些主机、哪些中间件 。
|
||||
|
||||
项目部署中间件信息表
|
||||
├── 所属部署 ← 单向关联字段(→ 部署状态表),这是你的"projectID"
|
||||
├── 所属部署 ← 单向关联字段(→ 部署状态表)
|
||||
├── 项目名称 ← 引用字段(从部署状态表引用,只读自动填入)
|
||||
├── 中间件类型 ← 单选(MySQL / Redis / RabbitMQ / EMQX / NACOS / K8S Dashboard / MINIO)
|
||||
├── 是否暴露公网
|
||||
|
||||
BIN
18-基础架构及交付部署特战队/1-项目部署-管理/金山多维表格-结构.xlsx
Normal file
BIN
18-基础架构及交付部署特战队/1-项目部署-管理/金山多维表格-结构.xlsx
Normal file
Binary file not shown.
@@ -25,4 +25,12 @@
|
||||
[金山多维表格开发文档](https://365.kdocs.cn/3rd/open/documents/app-integration-dev/guide/dbsheet/dbsheet-standard)
|
||||
[AirScript文档](https://airsheet.wps.cn/docs/guide/summary.html)
|
||||
|
||||
整理得到一份可以执行落地的API脚本用于创建上述的表格 以及人工需要进行操作补充的相关说明
|
||||
整理得到一份可以执行落地的API脚本用于创建上述的表格 以及人工需要进行操作补充的相关说明
|
||||
|
||||
请你分析,根据金山多维表格的的API文档
|
||||
[金山多维表格开发文档](https://365.kdocs.cn/3rd/open/documents/app-integration-dev/guide/dbsheet/dbsheet-standard)
|
||||
[AirScript文档](https://airsheet.wps.cn/docs/guide/summary.html)
|
||||
|
||||
要求完整的获取关于AirScript的相关文档,如果遇到需要递归的字段和网页 请递归查找
|
||||
|
||||
需要整理一份离线的,功能完备的,markdown格式的 airscript-spec-doc.md 文件,用于后续的开发参考
|
||||
39
18-基础架构及交付部署特战队/10-飞书多维表格/0-多维表格开发文档.md
Normal file
39
18-基础架构及交付部署特战队/10-飞书多维表格/0-多维表格开发文档.md
Normal file
@@ -0,0 +1,39 @@
|
||||
|
||||
### V1
|
||||
你是一名优秀的飞书多维表格开发专家,你非常善于阅读并搜集在线的资料,并根据资料完成开发工作。
|
||||
|
||||
请你阅读 [飞书多维表格开发文档](https://open.feishu.cn/document/server-docs/docs/bitable-v1/bitable-overview) 将飞书多维表格的开发文档,包含所有子页面,整理成一份完整的参考文档.
|
||||
|
||||
提示:飞书文档页面存在复制页面的button, 可以直接复制为Markdown格式提供给大模型参考,请熟练使用
|
||||
|
||||
注意:
|
||||
1. 先拉取完整的开发文档,注意不同子页面之间的关联关系
|
||||
2. 后期我会将此部分开发文档制作为AgentSkills
|
||||
3. 将完整的文档输出到 18-基础架构及交付部署特战队\10-飞书多维表格 目录下
|
||||
|
||||
### V2
|
||||
你完成的很好
|
||||
|
||||
1. 现在实现的参考文档中,存在大量的在线链接的内容,我希望的是可以完全离线使用的完整文档
|
||||
2. 请你将在线内容全部下载到本地,并整理成一份完整的参考文档
|
||||
3. 需要将Go SDK的示例代码也一并下载一份
|
||||
4. 请研究[飞书API参考文档](https://feishu.apifox.cn/) 将GolangSDK调用的使用说明 也一并写入文档中
|
||||
|
||||
请你按照上面的要求,重新生成一份完整的参考文档v2版本
|
||||
|
||||
### V3
|
||||
请你详细的阅读飞书的官方文档,完成如下要求的深度研究
|
||||
1. 飞书能够实现的功能有哪些
|
||||
2. 飞书API接口实现的功能有哪些
|
||||
3. 飞书GO SDK能够实现的功能有哪些
|
||||
4. 飞书所有的功能是否都支持API接口修改
|
||||
5. 飞书能否作为项目协同的前端页面平台
|
||||
6. 飞书免费版能否畅快的使用,不受限制
|
||||
|
||||
|
||||
### V4
|
||||
使用dds-to-skill技能,你现在需要根据 [飞书多维表格开发文档-完整参考-v2.md](18-基础架构及交付部署特战队\10-飞书多维表格\飞书多维表格开发文档-完整参考-v2.md) 为大模型制作一份可以参考使用的AgentSkills
|
||||
|
||||
要求如下:
|
||||
1. 输出目录为 18-基础架构及交付部署特战队\10-飞书多维表格
|
||||
2. 需要按照AgentSkills的渐进式暴露原则,实现文档的转换
|
||||
@@ -0,0 +1,20 @@
|
||||
# 全局自检结果
|
||||
|
||||
## 结构完整性
|
||||
- ✅ S1 PASS: 系统级 Skill = 1
|
||||
- ✅ S2 PASS: 模块级 Skills = 6
|
||||
- ✅ S3 PASS: 横切 Skills = 4(>=3)
|
||||
- ✅ S4 PASS: 所有 Skill 含 scripts/verify.sh
|
||||
- ✅ S5 PASS: 已执行 11 个 verify.sh,全部通过
|
||||
|
||||
## 内容质量
|
||||
- ✅ C1 PASS: 所有 SKILL.md < 500 行
|
||||
- ✅ C2 PASS: 所有 SKILL.md 含 Plan/Verify/Execute/Pitfalls
|
||||
- ✅ C3 PASS: 所有 SKILL.md 动态注入命令 >= 2
|
||||
|
||||
## Reference 质量
|
||||
- ✅ R1 PASS: 所有 Skill reference 含 DDS-Section 与 DDS-Lines
|
||||
- ✅ R2 PASS: 模块级 Skill 均含 API + 安全/依赖 + [TBD] 缺失标注文件
|
||||
|
||||
## 总计: 10 PASS / 0 FAIL
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
# 飞书多维表格 AgentSkills(v2 离线文档转换产物)
|
||||
|
||||
本套 Skills 基于 `飞书多维表格开发文档-完整参考-v2.md` 生成,遵循「渐进式暴露」:
|
||||
|
||||
1. 先进入系统级 Skill 判断变更范围。
|
||||
2. 再进入对应模块级 Skill 执行实现/改造。
|
||||
3. 叠加横切 Skill 处理契约、鉴权、并发、SDK 接入。
|
||||
4. 最后下钻 `reference/` 读取可溯源设计细节(含 DDS 章节与行号)。
|
||||
|
||||
## Skill 清单(最终命名)
|
||||
|
||||
### 系统级(1)
|
||||
- `developing-feishu-bitable-system`
|
||||
|
||||
### 模块级(6)
|
||||
- `developing-bitable-app-metadata`
|
||||
- `developing-bitable-table-view`
|
||||
- `developing-bitable-record`
|
||||
- `developing-bitable-field`
|
||||
- `developing-bitable-role-member`
|
||||
- `developing-bitable-event-callback`
|
||||
|
||||
### 横切(4)
|
||||
- `designing-bitable-contracts`
|
||||
- `implementing-bitable-auth-access`
|
||||
- `managing-bitable-write-concurrency`
|
||||
- `implementing-bitable-go-sdk`
|
||||
|
||||
## 命名候选与选择理由
|
||||
|
||||
| Skill | 候选名 | 选择 | 理由 |
|
||||
|---|---|---|---|
|
||||
| 系统级 | `developing-bitable-system` / `developing-feishu-bitable-system` / `developing-lark-bitable-system` | `developing-feishu-bitable-system` | 同时保留平台域(feishu)和产品域(bitable),检索命中更稳定。 |
|
||||
| App | `developing-bitable-app` / `developing-bitable-app-metadata` / `developing-bitable-app-core` | `developing-bitable-app-metadata` | 文档对 App 主要是元数据与高级权限开关,`metadata` 语义更准确。 |
|
||||
| Table/View | `developing-bitable-table` / `developing-bitable-view` / `developing-bitable-table-view` | `developing-bitable-table-view` | 视图严格依附数据表,合并后可减少重复规则。 |
|
||||
| Record | `developing-bitable-record` / `developing-bitable-row` / `developing-bitable-record-crud` | `developing-bitable-record` | 与 OpenAPI 资源命名一致,便于映射接口。 |
|
||||
| Field | `developing-bitable-field` / `developing-bitable-schema-field` / `developing-bitable-column` | `developing-bitable-field` | 与文档“字段 Field”章节一致。 |
|
||||
| Role/Member | `developing-bitable-role` / `developing-bitable-role-member` / `developing-bitable-advanced-permission` | `developing-bitable-role-member` | 将角色与协作者放在同一模块,贴合高级权限操作闭环。 |
|
||||
| Event/Callback | `developing-bitable-event` / `developing-bitable-event-callback` / `developing-bitable-webhook` | `developing-bitable-event-callback` | 同时覆盖事件与回调两条接入路径。 |
|
||||
| 契约 | `designing-bitable-contracts` / `designing-bitable-openapi` / `managing-bitable-contracts` | `designing-bitable-contracts` | 以设计导向统一 API/错误码/资源 ID 契约。 |
|
||||
| 鉴权 | `implementing-bitable-auth` / `implementing-bitable-auth-access` / `managing-bitable-token-auth` | `implementing-bitable-auth-access` | 同时包含 token 与权限模型。 |
|
||||
| 并发 | `managing-bitable-concurrency` / `managing-bitable-write-concurrency` / `implementing-bitable-retry` | `managing-bitable-write-concurrency` | 明确聚焦写操作并发与幂等。 |
|
||||
| Go SDK | `implementing-bitable-sdk` / `implementing-bitable-go-sdk` / `developing-bitable-sdk-integration` | `implementing-bitable-go-sdk` | 技术栈指向清晰,避免多语言混淆。 |
|
||||
|
||||
## 目录约定
|
||||
|
||||
每个 Skill 目录均包含:
|
||||
- `SKILL.md`
|
||||
- `reference/`(按章节分层,含 `DDS-Section` + `DDS-Lines`)
|
||||
- `examples/skeleton.md`
|
||||
- `scripts/verify.sh`
|
||||
|
||||
## 渐进式使用建议
|
||||
|
||||
1. 先读 `developing-feishu-bitable-system/SKILL.md`。
|
||||
2. 根据变更资源进入对应模块 Skill。
|
||||
3. 若涉及跨模块约束,再叠加横切 Skill。
|
||||
4. 编码或审查时仅按需展开目标 `reference/*.md`,避免一次性加载全量文档。
|
||||
@@ -0,0 +1,79 @@
|
||||
---
|
||||
name: designing-bitable-contracts
|
||||
description: 统一飞书多维表格 API 契约设计与审查(Guides cross-module contract design for Feishu Bitable)。包含资源 ID 规范、OpenAPI 路径方法、请求响应字段与错误码一致性。触发场景 Trigger: 新增接口、变更字段、排查上下游契约不一致、做跨模块发布评审。关键词 Keywords: contract, openapi, path, error code, app_token, table_id, bitable。
|
||||
argument-hint: "<scope> <change> - e.g., 'record api add field', 'role api error-code align'"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Glob
|
||||
- Grep
|
||||
- Bash
|
||||
---
|
||||
|
||||
# Designing Bitable Contracts
|
||||
|
||||
用于跨模块统一 API 契约,重点防止路径、参数、错误码与资源 ID 在不同模块实现中产生漂移。
|
||||
|
||||
## Quick Context
|
||||
|
||||
```bash
|
||||
# 汇总 Bitable 相关接口定义
|
||||
!`rg -n "OpenAPI Specification|/bitable/v1/apps|错误码|requestBody|responses" -S . | head -n 150`
|
||||
|
||||
# 扫描资源 ID 使用位置
|
||||
!`rg -n "app_token|table_id|view_id|record_id|field_id|role_id|member_id" -S . | head -n 150`
|
||||
```
|
||||
|
||||
## Plan
|
||||
|
||||
### 产物清单
|
||||
- [ ] 资源 ID 与路径映射表
|
||||
- [ ] 请求/响应字段差异清单
|
||||
- [ ] 错误码冲突清单
|
||||
- [ ] 兼容性评审结论
|
||||
|
||||
### 决策点
|
||||
1. 变更是向后兼容还是破坏性变更?
|
||||
2. 是否需要引入新错误码或复用现有错误码?
|
||||
3. 是否影响多模块消费方(SDK、回调、下游服务)?
|
||||
|
||||
## Verify
|
||||
|
||||
### 资源一致性
|
||||
- [ ] 资源 ID 语义统一,见 `reference/01-resource-ids/dependencies.md`
|
||||
- [ ] 路径层级与资源归属无冲突
|
||||
|
||||
### OpenAPI 一致性
|
||||
- [ ] 方法、路径、参数、响应字段对齐 `reference/02-openapi-contract/apis.md`
|
||||
- [ ] 请求体可选项与默认语义明确
|
||||
|
||||
### 错误模型一致性
|
||||
- [ ] 同错误码语义一致,见 `reference/03-error-model/apis.md`
|
||||
- [ ] 失败场景有明确排查建议
|
||||
|
||||
## Execute
|
||||
|
||||
### 1. 建立契约基线
|
||||
1. 汇总当前模块接口矩阵。
|
||||
2. 锁定资源 ID 与路径层级。
|
||||
|
||||
### 2. 比较差异
|
||||
1. 对请求字段做新增/删除/语义变化对比。
|
||||
2. 对响应字段做兼容性对比。
|
||||
3. 对错误码做含义冲突检查。
|
||||
|
||||
### 3. 输出决策
|
||||
1. 明确兼容级别(兼容/有条件兼容/不兼容)。
|
||||
2. 给出迁移步骤与回滚策略。
|
||||
|
||||
## Pitfalls
|
||||
|
||||
1. **同一资源使用多个命名**:如 token/id 混用导致歧义,见 `reference/01-resource-ids/dependencies.md`。
|
||||
2. **只改请求不改响应示例**:上下游生成代码不一致,见 `reference/02-openapi-contract/apis.md`。
|
||||
3. **错误码复用语义冲突**:排障不可控,见 `reference/03-error-model/apis.md`。
|
||||
|
||||
## Related References
|
||||
|
||||
- `reference/01-resource-ids/dependencies.md`
|
||||
- `reference/02-openapi-contract/apis.md`
|
||||
- `reference/03-error-model/apis.md`
|
||||
- `reference/04-compatibility/state-machine.md`
|
||||
@@ -0,0 +1,4 @@
|
||||
# Skeleton Example
|
||||
|
||||
- 仅提供调用骨架,不包含完整业务实现。
|
||||
- 实际编码前先对照 `reference/` 校验参数、权限、错误码与频控限制。
|
||||
@@ -0,0 +1,19 @@
|
||||
## 资源 ID 规范
|
||||
|
||||
- DDS-Section: 接入指南-参数说明 / A.1 概述
|
||||
- DDS-Lines: L399-L468, L111-L257
|
||||
|
||||
### Extract
|
||||
|
||||
| 资源 | ID 字段 |
|
||||
|---|---|
|
||||
| App | `app_token` |
|
||||
| Table | `table_id` |
|
||||
| View | `view_id` |
|
||||
| Record | `record_id` |
|
||||
| Field | `field_id` |
|
||||
| Role | `role_id` |
|
||||
| Member | `member_id` |
|
||||
|
||||
规则:ID 应与路径段一一对应,禁止跨资源复用。
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
## OpenAPI 契约骨架
|
||||
|
||||
- DDS-Section: A.10~A.49 OpenAPI Specification
|
||||
- DDS-Lines: L1938-L16470
|
||||
|
||||
### Extract
|
||||
|
||||
- 路径命名统一前缀:`/bitable/v1/apps/{app_token}`。
|
||||
- 子资源按层次展开:`tables -> views/records/fields`,`roles -> members`。
|
||||
- 请求体和响应体均以标准 JSON schema 描述,含 code/msg/data 包装。
|
||||
|
||||
## 字段级审查要点
|
||||
|
||||
- 请求字段是否含示例值与必填标注。
|
||||
- 响应字段是否覆盖新增字段。
|
||||
- 分页字段 `page_token`/`has_more` 是否一致。
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
## 错误码模型
|
||||
|
||||
- DDS-Section: 各 OpenAPI 的错误码章节
|
||||
- DDS-Lines: L1957+, L2239+, L2461+, L4723+, L7594+
|
||||
|
||||
### Extract
|
||||
|
||||
| 类别 | 示例 |
|
||||
|---|---|
|
||||
| 参数错误 | `WrongRequestJson`, `WrongRequestBody`, `WrongFieldId` |
|
||||
| 资源错误 | `WrongBaseToken`, `app_token 不存在` |
|
||||
| 并发/处理冲突 | `OperationTypeError`、并发写限制 |
|
||||
| 幂等冲突 | `client token and try again` |
|
||||
|
||||
要求:同错误码在不同模块中保持相同语义与排查动作。
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
## 版本与兼容策略
|
||||
|
||||
- DDS-Section: 全文
|
||||
- DDS-Lines: L1-L18211
|
||||
|
||||
### Extract
|
||||
|
||||
[TBD] 文档未给出正式的 API 版本升级策略(deprecate 生命周期、灰度窗口、双写方案)。
|
||||
|
||||
### 最小补充信息清单
|
||||
|
||||
1. 向后兼容判定标准
|
||||
2. 破坏性变更发布流程
|
||||
3. 旧版本下线时间线
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
# verify.sh - skill structure/content quick validation
|
||||
set -e
|
||||
PASS=0
|
||||
FAIL=0
|
||||
|
||||
pass() {
|
||||
echo "PASS: $1"
|
||||
PASS=$((PASS+1))
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo "FAIL: $1"
|
||||
FAIL=$((FAIL+1))
|
||||
}
|
||||
|
||||
check_cmd() {
|
||||
local desc="$1"
|
||||
shift
|
||||
if "$@"; then
|
||||
pass "$desc"
|
||||
else
|
||||
fail "$desc"
|
||||
fi
|
||||
}
|
||||
|
||||
SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
|
||||
check_cmd "SKILL.md exists" test -f "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "reference dir exists" test -d "$SKILL_DIR/reference"
|
||||
|
||||
line_count=$(wc -l < "$SKILL_DIR/SKILL.md")
|
||||
if [ "$line_count" -lt 500 ]; then
|
||||
pass "SKILL.md < 500 lines"
|
||||
else
|
||||
fail "SKILL.md < 500 lines"
|
||||
fi
|
||||
|
||||
check_cmd "has Plan" grep -q '^## Plan' "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "has Verify" grep -q '^## Verify' "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "has Execute" grep -q '^## Execute' "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "has Pitfalls" grep -q '^## Pitfalls' "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "reference has DDS-Section" grep -rq 'DDS-Section:' "$SKILL_DIR/reference"
|
||||
check_cmd "reference has DDS-Lines" grep -rq 'DDS-Lines:' "$SKILL_DIR/reference"
|
||||
|
||||
echo "RESULT: $PASS PASS / $FAIL FAIL"
|
||||
[ $FAIL -eq 0 ]
|
||||
@@ -0,0 +1,82 @@
|
||||
---
|
||||
name: developing-bitable-app-metadata
|
||||
description: 指导飞书多维表格 App 元数据模块开发(Guides Bitable app metadata module development)。包含获取/更新元数据、高级权限开关、参数与错误码校验。触发场景 Trigger: 新增或修改 app 级配置、切换 advanced permission、排查 app_token 级接口失败。关键词 Keywords: bitable, app, metadata, app_token, advanced permission, 高级权限。
|
||||
argument-hint: "<action> <app_token> - e.g., 'get metadata appbxxx', 'update app name+permission appbxxx'"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
- Edit
|
||||
- Glob
|
||||
- Grep
|
||||
- Bash
|
||||
---
|
||||
|
||||
# Developing Bitable App Metadata
|
||||
|
||||
用于处理 `app_token` 级别能力:读取/更新多维表格元数据与高级权限开关,确保调用身份、前置权限、错误码处理一致。
|
||||
|
||||
## Quick Context
|
||||
|
||||
```bash
|
||||
# 定位 app 级接口定义与调用处
|
||||
!`rg -n "bitable/v1/apps/\{app_token\}|app/update|app/get" -S . | head -n 80`
|
||||
|
||||
# 定位高级权限相关逻辑
|
||||
!`rg -n "advanced.*permission|roles|OperationTypeError|app_token" -S . | head -n 80`
|
||||
```
|
||||
|
||||
## Plan
|
||||
|
||||
### 产物清单
|
||||
- [ ] App 元数据读取与更新调用点
|
||||
- [ ] 参数映射(`app_token`, `name`, `is_advanced`)
|
||||
- [ ] 错误码与重试策略
|
||||
- [ ] 权限前置检查
|
||||
|
||||
### 决策点
|
||||
1. 本次更新是仅改名称,还是同时改高级权限开关?
|
||||
2. 是否接受“非原子更新”带来的部分成功状态?
|
||||
3. 调用身份选择用户态还是应用态 token?
|
||||
|
||||
## Verify
|
||||
|
||||
### API 契约
|
||||
- [ ] 路径与方法与 `reference/02-app-openapi/apis.md` 一致
|
||||
- [ ] 请求体字段只包含本次所需字段,避免误改
|
||||
|
||||
### 权限与鉴权
|
||||
- [ ] token 身份与权限范围匹配,见 `reference/03-security/security-model.md`
|
||||
- [ ] 高级权限开关后续 Role/Member 流程已准备
|
||||
|
||||
### 风险控制
|
||||
- [ ] 已处理接口“先改名后开关权限”的部分成功场景
|
||||
- [ ] 对 `app_token` 错误、无访问权限、未开启高级权限错误有明确分支
|
||||
|
||||
## Execute
|
||||
|
||||
### 1. 读取当前状态
|
||||
1. 调用 `GET /bitable/v1/apps/{app_token}`,确认当前名称与权限开关。
|
||||
2. 记录变更前快照,作为回滚基线。
|
||||
|
||||
### 2. 执行更新
|
||||
1. 调用 `PUT /bitable/v1/apps/{app_token}`。
|
||||
2. 若同时变更多项,先按接口语义评估“部分成功”影响。
|
||||
3. 对已知可重试错误(如权限生效延迟)实施有限重试。
|
||||
|
||||
### 3. 验证结果
|
||||
1. 再次读取元数据核对结果。
|
||||
2. 若开启高级权限,立即验证 Role/Member 接口是否可用。
|
||||
|
||||
## Pitfalls
|
||||
|
||||
1. **把元数据更新当作原子事务**:接口本身可能部分成功,先读 `reference/02-app-openapi/apis.md`。
|
||||
2. **开启高级权限后立即操作角色失败**:存在生效延迟,见 `reference/03-security/security-model.md`。
|
||||
3. **调用身份不匹配**:用户态与应用态 token 混用会造成权限异常,见 `reference/03-security/security-model.md`。
|
||||
4. **遗漏 app_token 来源校验**:URL/块信息提取错误会导致全链路失败,见 `reference/01-resource-overview/dependencies.md`。
|
||||
|
||||
## Related References
|
||||
|
||||
- `reference/01-resource-overview/dependencies.md`
|
||||
- `reference/02-app-openapi/apis.md`
|
||||
- `reference/03-security/security-model.md`
|
||||
- `reference/04-db-schema/db-schema.md`
|
||||
@@ -0,0 +1,4 @@
|
||||
# Skeleton Example
|
||||
|
||||
- 仅提供调用骨架,不包含完整业务实现。
|
||||
- 实际编码前先对照 `reference/` 校验参数、权限、错误码与频控限制。
|
||||
@@ -0,0 +1,22 @@
|
||||
## App 资源定位与依赖
|
||||
|
||||
- DDS-Section: 资源:多维表格应用 App / 接入指南-参数说明 app_token
|
||||
- DDS-Lines: L111-L128, L399-L423
|
||||
|
||||
### Extract
|
||||
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| 主标识 | `app_token` |
|
||||
| 获取方式 | 多维表格 URL、文档 block token 拆分、相关 API 返回 |
|
||||
| 下游依赖 | Table/View/Record/Field/Role/Member 均依赖 `app_token` |
|
||||
|
||||
## 调用链路
|
||||
|
||||
- DDS-Section: 接入指南-内容
|
||||
- DDS-Lines: L294-L303
|
||||
|
||||
### Extract
|
||||
|
||||
`App -> Table -> (View / Record / Field)`,以及 `App -> Role -> Member`。
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
## 获取多维表格元数据
|
||||
|
||||
- DDS-Section: A.10 获取多维表格元数据
|
||||
- DDS-Lines: L1932-L2205
|
||||
|
||||
### Extract
|
||||
|
||||
| 方法 | 路径 | 关键参数 |
|
||||
|---|---|---|
|
||||
| GET | `/bitable/v1/apps/{app_token}` | `app_token` |
|
||||
|
||||
关键约束:
|
||||
- 支持 20 QPS(以接口描述为准)
|
||||
- 错误码含 `WrongBaseToken`、`OperationTypeError` 等
|
||||
|
||||
## 更新多维表格元数据
|
||||
|
||||
- DDS-Section: A.11 更新多维表格元数据
|
||||
- DDS-Lines: L2206-L2430
|
||||
|
||||
### Extract
|
||||
|
||||
| 方法 | 路径 | 请求体关键字段 |
|
||||
|---|---|---|
|
||||
| PUT | `/bitable/v1/apps/{app_token}` | `name`, `is_advanced` |
|
||||
|
||||
关键约束:
|
||||
- 接口非原子:先改名,后切换高级权限
|
||||
- 写接口常见 10 QPS,且不建议并发写
|
||||
- 高级权限开关对后续 Role/Member 接口可用性有直接影响
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
## token 与访问身份
|
||||
|
||||
- DDS-Section: 接入指南-鉴权
|
||||
- DDS-Lines: L311-L371
|
||||
|
||||
### Extract
|
||||
|
||||
| 方式 | 适用 |
|
||||
|---|---|
|
||||
| `user_access_token` | 用户身份调用 |
|
||||
| `tenant_access_token` | 应用身份调用 |
|
||||
|
||||
## 高级权限开关前置
|
||||
|
||||
- DDS-Section: A.7 概述 / A.11 更新多维表格元数据
|
||||
- DDS-Lines: L1547-L1561, L2229-L2233, L2286-L2289
|
||||
|
||||
### Extract
|
||||
|
||||
- 飞书文档/飞书表格/知识库中的多维表格不支持开启高级权限。
|
||||
- 开启高级权限存在生效延迟,可能返回 `OperationTypeError`。
|
||||
- 发生无访问权限时,应检查高级权限设置中是否为应用主体授权。
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
## 服务端持久化结构
|
||||
|
||||
- DDS-Section: 全文
|
||||
- DDS-Lines: L1-L18211
|
||||
|
||||
### Extract
|
||||
|
||||
[TBD] 文档未提供 App 元数据在业务系统侧的数据库结构。
|
||||
|
||||
### 最小补充信息清单
|
||||
|
||||
1. App 元数据缓存表是否存在(字段与 TTL)
|
||||
2. 高级权限开关状态是否持久化本地
|
||||
3. App 配置变更的审计日志模型
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
# verify.sh - skill structure/content quick validation
|
||||
set -e
|
||||
PASS=0
|
||||
FAIL=0
|
||||
|
||||
pass() {
|
||||
echo "PASS: $1"
|
||||
PASS=$((PASS+1))
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo "FAIL: $1"
|
||||
FAIL=$((FAIL+1))
|
||||
}
|
||||
|
||||
check_cmd() {
|
||||
local desc="$1"
|
||||
shift
|
||||
if "$@"; then
|
||||
pass "$desc"
|
||||
else
|
||||
fail "$desc"
|
||||
fi
|
||||
}
|
||||
|
||||
SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
|
||||
check_cmd "SKILL.md exists" test -f "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "reference dir exists" test -d "$SKILL_DIR/reference"
|
||||
|
||||
line_count=$(wc -l < "$SKILL_DIR/SKILL.md")
|
||||
if [ "$line_count" -lt 500 ]; then
|
||||
pass "SKILL.md < 500 lines"
|
||||
else
|
||||
fail "SKILL.md < 500 lines"
|
||||
fi
|
||||
|
||||
check_cmd "has Plan" grep -q '^## Plan' "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "has Verify" grep -q '^## Verify' "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "has Execute" grep -q '^## Execute' "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "has Pitfalls" grep -q '^## Pitfalls' "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "reference has DDS-Section" grep -rq 'DDS-Section:' "$SKILL_DIR/reference"
|
||||
check_cmd "reference has DDS-Lines" grep -rq 'DDS-Lines:' "$SKILL_DIR/reference"
|
||||
|
||||
echo "RESULT: $PASS PASS / $FAIL FAIL"
|
||||
[ $FAIL -eq 0 ]
|
||||
@@ -0,0 +1,87 @@
|
||||
---
|
||||
name: developing-bitable-event-callback
|
||||
description: 指导飞书多维表格事件与回调模块开发(Guides Bitable event and callback integration development)。包含事件类型、长连接/HTTP 回调模式、验签解密参数、3 秒响应与重推去重策略。触发场景 Trigger: 新增事件订阅、接入回调服务、排查重复推送与签名校验失败。关键词 Keywords: event, callback, websocket, webhook, verificationToken, eventEncryptKey, bitable。
|
||||
argument-hint: "<mode> <event-type> - e.g., 'websocket bitable_record_changed', 'http callback card action'"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
- Edit
|
||||
- Glob
|
||||
- Grep
|
||||
- Bash
|
||||
---
|
||||
|
||||
# Developing Bitable Event & Callback
|
||||
|
||||
用于搭建与维护飞书事件/回调接入通道,统一消息投递语义、幂等消费、验签解密配置与故障处理。
|
||||
|
||||
## Quick Context
|
||||
|
||||
```bash
|
||||
# 定位事件处理器、回调路由与 SDK dispatcher
|
||||
!`rg -n "EventDispatcher|OnP2|OnCustomizedEvent|webhook/event|callback" -S . | head -n 120`
|
||||
|
||||
# 定位 3 秒响应、重推、去重逻辑
|
||||
!`rg -n "3 秒|重推|event_id|idempot|retry|verificationToken|eventEncryptKey" -S . | head -n 120`
|
||||
```
|
||||
|
||||
## Plan
|
||||
|
||||
### 产物清单
|
||||
- [ ] 事件订阅清单与处理器映射
|
||||
- [ ] 回调模式(长连接/HTTP)决策
|
||||
- [ ] 验签解密配置与密钥管理
|
||||
- [ ] 重推去重与失败告警策略
|
||||
|
||||
### 决策点
|
||||
1. 采用长连接模式还是 HTTP 回调模式?
|
||||
2. 是否开启加密策略(需要 verificationToken/eventEncryptKey)?
|
||||
3. 幂等去重键是否统一为 `event_id`?
|
||||
|
||||
## Verify
|
||||
|
||||
### 事件契约
|
||||
- [ ] 事件类型与字段对齐 `reference/01-event-types/events-topics.md`
|
||||
- [ ] 处理器注册方法与事件版本匹配(v1/v2)
|
||||
|
||||
### 接入模式
|
||||
- [ ] 模式选择与网络前置条件对齐 `reference/02-callback-modes/dependencies.md`
|
||||
- [ ] HTTP 模式已提供稳定回调地址与快速响应路径
|
||||
|
||||
### 安全与语义
|
||||
- [ ] 验签/解密参数配置对齐 `reference/03-security/security-model.md`
|
||||
- [ ] 3 秒响应与重推语义对齐 `reference/04-delivery-semantics/state-machine.md`
|
||||
|
||||
## Execute
|
||||
|
||||
### 1. 建立接入通道
|
||||
1. 长连接模式:初始化 SDK ws client 并注册 dispatcher。
|
||||
2. HTTP 模式:暴露 `/webhook/event` 路由并接入 handler。
|
||||
|
||||
### 2. 注册事件处理器
|
||||
1. 按事件版本选择 `OnCustomizedEvent` 或 `OnP2*` 方法。
|
||||
2. 在处理器内抽取 `event_id`, `tenant_key`, 资源 ID。
|
||||
|
||||
### 3. 落地幂等与超时控制
|
||||
1. 处理链路控制在 3 秒内返回 ACK。
|
||||
2. 业务处理异步化,核心字段入队。
|
||||
3. 以 `event_id` 去重,避免重推重复消费。
|
||||
|
||||
### 4. 异常处理
|
||||
1. 验签/解密失败:快速拒绝并记录上下文。
|
||||
2. 下游不可用:入队重试并上报告警。
|
||||
|
||||
## Pitfalls
|
||||
|
||||
1. **使用错误的事件版本处理器**:v1/v2 结构不同,见 `reference/01-event-types/events-topics.md`。
|
||||
2. **处理超时超过 3 秒**:平台会重推,见 `reference/04-delivery-semantics/state-machine.md`。
|
||||
3. **开启加密后仍传空密钥**:会导致解密失败,见 `reference/03-security/security-model.md`。
|
||||
4. **长连接当广播使用**:实际是集群随机分发,见 `reference/02-callback-modes/dependencies.md`。
|
||||
|
||||
## Related References
|
||||
|
||||
- `reference/01-event-types/events-topics.md`
|
||||
- `reference/02-callback-modes/dependencies.md`
|
||||
- `reference/03-security/security-model.md`
|
||||
- `reference/04-delivery-semantics/state-machine.md`
|
||||
- `reference/05-db-schema/db-schema.md`
|
||||
@@ -0,0 +1,4 @@
|
||||
# Skeleton Example
|
||||
|
||||
- 仅提供调用骨架,不包含完整业务实现。
|
||||
- 实际编码前先对照 `reference/` 校验参数、权限、错误码与频控限制。
|
||||
@@ -0,0 +1,14 @@
|
||||
## Bitable 事件清单
|
||||
|
||||
- DDS-Section: A.8 多维表格字段变更 / A.9 多维表格记录变更
|
||||
- DDS-Lines: L1599-L1712, L1815-L1915
|
||||
|
||||
### Extract
|
||||
|
||||
| 事件类型 | 场景 | 关键字段 |
|
||||
|---|---|---|
|
||||
| `drive.file.bitable_field_changed_v1` | 字段变更 | `event_id`, `event_type`, `table_id`, `field_id` |
|
||||
| `drive.file.bitable_record_changed_v1` | 记录变更 | `event_id`, `event_type`, `table_id`, `record_id` |
|
||||
|
||||
字段:`tenant_key`, `token`, `subscriber_id_list` 需一并保留用于追踪与授权判定。
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
## 回调接入模式
|
||||
|
||||
- DDS-Section: 3.5 事件与回调处理 / B.4 处理事件 / B.5 处理回调
|
||||
- DDS-Lines: L86-L92, L16849-L17083, L17147-L17397
|
||||
|
||||
### Extract
|
||||
|
||||
| 模式 | 特点 | 前置条件 |
|
||||
|---|---|---|
|
||||
| 长连接(WebSocket) | 接入快、免内网穿透、集群非广播 | 运行环境可访问公网 |
|
||||
| HTTP 回调 | 传统 webhook,平台主动 POST | 需要公网可达地址 |
|
||||
|
||||
## 框架集成
|
||||
|
||||
- DDS-Section: B.4/B.5 示例代码
|
||||
- DDS-Lines: L17028-L17081, L17358-L17397
|
||||
|
||||
### Extract
|
||||
|
||||
支持原生 HTTP、Gin、Hertz 三类接入方式。
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
## 验签与解密配置
|
||||
|
||||
- DDS-Section: B.4 处理事件 / B.5 处理回调
|
||||
- DDS-Lines: L16980-L17018, L17294-L17316
|
||||
|
||||
### Extract
|
||||
|
||||
| 参数 | 说明 |
|
||||
|---|---|
|
||||
| `verificationToken` | 用于签名验证 |
|
||||
| `eventEncryptKey` | 用于消息解密 |
|
||||
|
||||
若在开发者后台启用加密策略,则必须传递上述参数。
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
## 投递语义状态流
|
||||
|
||||
- DDS-Section: 事件/回调处理说明 / FAQ
|
||||
- DDS-Lines: L89, L16871, L17196, L18207-L18209
|
||||
|
||||
### Extract
|
||||
|
||||
```text
|
||||
RECEIVED -> ACK(<3s) -> DONE
|
||||
RECEIVED -> TIMEOUT(>=3s) -> RETRY_PUSH -> DEDUPE_BY_EVENT_ID -> DONE
|
||||
```
|
||||
|
||||
规则:
|
||||
- 必须在 3 秒内完成 ACK。
|
||||
- 超时会触发重推,消费侧必须幂等。
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
## 事件存储结构
|
||||
|
||||
- DDS-Section: 全文
|
||||
- DDS-Lines: L1-L18211
|
||||
|
||||
### Extract
|
||||
|
||||
[TBD] 文档未给出事件落库模型(去重表、死信表、重试表)。
|
||||
|
||||
### 最小补充信息清单
|
||||
|
||||
1. `event_id` 去重存储 TTL
|
||||
2. 失败事件重试与死信策略
|
||||
3. 事件审计字段定义
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
# verify.sh - skill structure/content quick validation
|
||||
set -e
|
||||
PASS=0
|
||||
FAIL=0
|
||||
|
||||
pass() {
|
||||
echo "PASS: $1"
|
||||
PASS=$((PASS+1))
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo "FAIL: $1"
|
||||
FAIL=$((FAIL+1))
|
||||
}
|
||||
|
||||
check_cmd() {
|
||||
local desc="$1"
|
||||
shift
|
||||
if "$@"; then
|
||||
pass "$desc"
|
||||
else
|
||||
fail "$desc"
|
||||
fi
|
||||
}
|
||||
|
||||
SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
|
||||
check_cmd "SKILL.md exists" test -f "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "reference dir exists" test -d "$SKILL_DIR/reference"
|
||||
|
||||
line_count=$(wc -l < "$SKILL_DIR/SKILL.md")
|
||||
if [ "$line_count" -lt 500 ]; then
|
||||
pass "SKILL.md < 500 lines"
|
||||
else
|
||||
fail "SKILL.md < 500 lines"
|
||||
fi
|
||||
|
||||
check_cmd "has Plan" grep -q '^## Plan' "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "has Verify" grep -q '^## Verify' "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "has Execute" grep -q '^## Execute' "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "has Pitfalls" grep -q '^## Pitfalls' "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "reference has DDS-Section" grep -rq 'DDS-Section:' "$SKILL_DIR/reference"
|
||||
check_cmd "reference has DDS-Lines" grep -rq 'DDS-Lines:' "$SKILL_DIR/reference"
|
||||
|
||||
echo "RESULT: $PASS PASS / $FAIL FAIL"
|
||||
[ $FAIL -eq 0 ]
|
||||
@@ -0,0 +1,86 @@
|
||||
---
|
||||
name: developing-bitable-field
|
||||
description: 指导飞书多维表格 Field 模块开发(Guides Bitable field module development)。包含字段 CRUD、字段类型 property 配置、全量更新语义、字段变更事件处理。触发场景 Trigger: 新增/更新/删除字段、扩展字段类型配置、排查 field_id 与 property 不匹配问题。关键词 Keywords: field, field_id, property, option, relation, bitable。
|
||||
argument-hint: "<action> <app_token table_id field_id?> - e.g., 'create field appbxxx tblxxx', 'update field property appbxxx tblxxx fldxxx'"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
- Edit
|
||||
- Glob
|
||||
- Grep
|
||||
- Bash
|
||||
---
|
||||
|
||||
# Developing Bitable Field
|
||||
|
||||
用于字段资源开发:字段列表与增删改、字段类型 `property` 配置、全量更新风险控制,以及字段变更事件消费。
|
||||
|
||||
## Quick Context
|
||||
|
||||
```bash
|
||||
# 定位字段 API 与字段类型处理
|
||||
!`rg -n "app-table-field|fields/\{field_id\}|property|options|table_id" -S . | head -n 120`
|
||||
|
||||
# 定位字段事件与幂等逻辑
|
||||
!`rg -n "bitable_field_changed|field_changed|client_token|幂等" -S . | head -n 100`
|
||||
```
|
||||
|
||||
## Plan
|
||||
|
||||
### 产物清单
|
||||
- [ ] Field CRUD 调用封装
|
||||
- [ ] 各字段类型 `property` 映射
|
||||
- [ ] 更新语义防护(全量覆盖)
|
||||
- [ ] 字段变更事件消费链路
|
||||
|
||||
### 决策点
|
||||
1. 本次是新增字段还是全量更新字段?
|
||||
2. 是否涉及关联字段(单向/双向)与跨表依赖?
|
||||
3. 字段变更是否需要同步下游读写映射?
|
||||
|
||||
## Verify
|
||||
|
||||
### 契约与结构
|
||||
- [ ] API 路径、方法对齐 `reference/01-field-openapi/apis.md`
|
||||
- [ ] 字段类型与 `property` 结构对齐 `reference/02-field-types/db-schema.md`
|
||||
|
||||
### 事件与一致性
|
||||
- [ ] 字段变更事件字段对齐 `reference/03-field-events/events-topics.md`
|
||||
- [ ] 更新后已验证字段列表与读写兼容
|
||||
|
||||
### 权限
|
||||
- [ ] token 与权限范围对齐 `reference/04-security/security-model.md`
|
||||
|
||||
## Execute
|
||||
|
||||
### 1. 字段读取
|
||||
1. 先 `list fields` 获取当前字段与类型。
|
||||
2. 基于目标类型准备 `property` 请求体。
|
||||
|
||||
### 2. 字段写操作
|
||||
1. 新增字段:调用 `create`,最小化初始属性。
|
||||
2. 更新字段:调用 `update` 前先合并旧属性,避免误删。
|
||||
3. 删除字段:确认下游表达式/关联/报表依赖后执行。
|
||||
|
||||
### 3. 关联字段处理
|
||||
1. 单向/双向关联字段需显式绑定目标 `table_id`。
|
||||
2. 双向关联应检查返回的反向字段信息。
|
||||
|
||||
### 4. 事件消费
|
||||
1. 消费 `bitable_field_changed`。
|
||||
2. 以 `event_id` 去重,刷新本地字段缓存。
|
||||
|
||||
## Pitfalls
|
||||
|
||||
1. **把字段更新当成增量 patch**:接口是全量更新,易误删旧选项,见 `reference/02-field-types/db-schema.md`。
|
||||
2. **关联字段只改一侧**:双向关联会产生不一致,见 `reference/02-field-types/db-schema.md`。
|
||||
3. **删字段前未清理筛选或公式依赖**:可能导致查询失败,先查 `reference/01-field-openapi/apis.md`。
|
||||
4. **字段变更事件重复消费**:需按 event_id 去重,见 `reference/03-field-events/events-topics.md`。
|
||||
|
||||
## Related References
|
||||
|
||||
- `reference/01-field-openapi/apis.md`
|
||||
- `reference/02-field-types/db-schema.md`
|
||||
- `reference/03-field-events/events-topics.md`
|
||||
- `reference/04-security/security-model.md`
|
||||
- `reference/05-state-machine/state-machine.md`
|
||||
@@ -0,0 +1,4 @@
|
||||
# Skeleton Example
|
||||
|
||||
- 仅提供调用骨架,不包含完整业务实现。
|
||||
- 实际编码前先对照 `reference/` 校验参数、权限、错误码与频控限制。
|
||||
@@ -0,0 +1,18 @@
|
||||
## Field API 矩阵
|
||||
|
||||
- DDS-Section: A.37~A.40(列出/新增/更新/删除字段)
|
||||
- DDS-Lines: L12086-L13934
|
||||
|
||||
### Extract
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| GET | `/bitable/v1/apps/{app_token}/tables/{table_id}/fields` | 列出字段 |
|
||||
| POST | `/bitable/v1/apps/{app_token}/tables/{table_id}/fields` | 新增字段 |
|
||||
| PUT | `/bitable/v1/apps/{app_token}/tables/{table_id}/fields/{field_id}` | 更新字段 |
|
||||
| DELETE | `/bitable/v1/apps/{app_token}/tables/{table_id}/fields/{field_id}` | 删除字段 |
|
||||
|
||||
约束:
|
||||
- 写接口不建议并发。
|
||||
- 常见写接口频控 10 QPS。
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
## 字段基础结构
|
||||
|
||||
- DDS-Section: A.3 数据结构 / A.5 字段编辑指南
|
||||
- DDS-Lines: L571-L580, L719-L738
|
||||
|
||||
### Extract
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `field_id` | string | 字段 ID |
|
||||
| `field_name` | string | 字段名 |
|
||||
| `type` | int | 字段类型编码 |
|
||||
| `property` | object | 类型相关属性 |
|
||||
|
||||
## 关键类型 property
|
||||
|
||||
- DDS-Section: A.5 字段编辑指南(数字、单选/多选、日期、人员、关联、公式、自动编号)
|
||||
- DDS-Lines: L738-L1344
|
||||
|
||||
### Extract
|
||||
|
||||
| 类型 | 关键 property |
|
||||
|---|---|
|
||||
| 单选/多选 | `options[]` |
|
||||
| 单向/双向关联 | `table_id`, `table_name`, `back_field_*` |
|
||||
| 自动编号 | `type`, `options[]`, `rule_option_type` |
|
||||
|
||||
更新语义:
|
||||
- 字段更新为全量更新,`property` 会被完全覆盖。
|
||||
|
||||
## DB Schema
|
||||
|
||||
- DDS-Section: 全文
|
||||
- DDS-Lines: L1-L18211
|
||||
|
||||
### Extract
|
||||
|
||||
[TBD] 文档无后端数据库字段字典表定义(字段元数据持久化)。
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
## 字段变更事件
|
||||
|
||||
- DDS-Section: A.8 多维表格字段变更
|
||||
- DDS-Lines: L1595-L1712
|
||||
|
||||
### Extract
|
||||
|
||||
| 事件类型 | 关键字段 |
|
||||
|---|---|
|
||||
| `drive.file.bitable_field_changed_v1` | `event_id`, `event_type`, `tenant_key`, `table_id`, `field_id` |
|
||||
|
||||
## 消费策略
|
||||
|
||||
- DDS-Section: 事件回调说明
|
||||
- DDS-Lines: L89, L18209
|
||||
|
||||
### Extract
|
||||
|
||||
- 3 秒内响应,否则重推。
|
||||
- 建议 `event_id` 去重 + 字段缓存增量刷新。
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
## 字段接口权限模型
|
||||
|
||||
- DDS-Section: 接入指南-鉴权 / 字段相关 OpenAPI security
|
||||
- DDS-Lines: L311-L371, L12108+, L12678+, L13147+
|
||||
|
||||
### Extract
|
||||
|
||||
| 项 | 说明 |
|
||||
|---|---|
|
||||
| token | 支持 tenant/user 两种调用身份 |
|
||||
| 高级权限影响 | 部分无访问权限由高级权限策略导致 |
|
||||
| 用户标识字段 | 某些字段(如人员)涉及 user_id 权限要求 |
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
## 字段状态机
|
||||
|
||||
- DDS-Section: 全文
|
||||
- DDS-Lines: L1-L18211
|
||||
|
||||
### Extract
|
||||
|
||||
[TBD] 文档未定义字段生命周期状态机(draft/active/deprecated 等)。
|
||||
|
||||
### 最小补充信息清单
|
||||
|
||||
1. 字段启用/停用状态定义
|
||||
2. 变更审批与回滚流程
|
||||
3. 对历史记录的兼容策略
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
# verify.sh - skill structure/content quick validation
|
||||
set -e
|
||||
PASS=0
|
||||
FAIL=0
|
||||
|
||||
pass() {
|
||||
echo "PASS: $1"
|
||||
PASS=$((PASS+1))
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo "FAIL: $1"
|
||||
FAIL=$((FAIL+1))
|
||||
}
|
||||
|
||||
check_cmd() {
|
||||
local desc="$1"
|
||||
shift
|
||||
if "$@"; then
|
||||
pass "$desc"
|
||||
else
|
||||
fail "$desc"
|
||||
fi
|
||||
}
|
||||
|
||||
SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
|
||||
check_cmd "SKILL.md exists" test -f "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "reference dir exists" test -d "$SKILL_DIR/reference"
|
||||
|
||||
line_count=$(wc -l < "$SKILL_DIR/SKILL.md")
|
||||
if [ "$line_count" -lt 500 ]; then
|
||||
pass "SKILL.md < 500 lines"
|
||||
else
|
||||
fail "SKILL.md < 500 lines"
|
||||
fi
|
||||
|
||||
check_cmd "has Plan" grep -q '^## Plan' "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "has Verify" grep -q '^## Verify' "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "has Execute" grep -q '^## Execute' "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "has Pitfalls" grep -q '^## Pitfalls' "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "reference has DDS-Section" grep -rq 'DDS-Section:' "$SKILL_DIR/reference"
|
||||
check_cmd "reference has DDS-Lines" grep -rq 'DDS-Lines:' "$SKILL_DIR/reference"
|
||||
|
||||
echo "RESULT: $PASS PASS / $FAIL FAIL"
|
||||
[ $FAIL -eq 0 ]
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
name: developing-bitable-record
|
||||
description: 指导飞书多维表格 Record 模块开发(Guides Bitable record module development)。包含记录 CRUD、批量写入、筛选表达式、附件字段协同与变更事件消费。触发场景 Trigger: 新增/更新/删除记录、批量写入、按条件检索、排查 record_id 或 fields 映射问题。关键词 Keywords: record, fields, batch_update, filter, file_token, bitable。
|
||||
argument-hint: "<action> <app_token table_id record_id?> - e.g., 'batch_update appbxxx tblxxx', 'get record appbxxx tblxxx recxxx'"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
- Edit
|
||||
- Glob
|
||||
- Grep
|
||||
- Bash
|
||||
---
|
||||
|
||||
# Developing Bitable Record
|
||||
|
||||
用于记录资源生命周期开发:单条与批量 CRUD、`fields` 结构映射、筛选表达式、附件字段联动与记录变更事件处理。
|
||||
|
||||
## Quick Context
|
||||
|
||||
```bash
|
||||
# 定位记录接口与批量操作
|
||||
!`rg -n "app-table-record|records/batch_|/records/\{record_id\}|filter" -S . | head -n 100`
|
||||
|
||||
# 定位幂等键、重试与附件 file_token 处理
|
||||
!`rg -n "client_token|幂等|file_token|batch_get_tmp_download_url|OperationTypeError" -S . | head -n 100`
|
||||
```
|
||||
|
||||
## Plan
|
||||
|
||||
### 产物清单
|
||||
- [ ] 记录 CRUD 与批量接口封装
|
||||
- [ ] `fields` 映射层(字段类型到值结构)
|
||||
- [ ] 幂等与重试控制
|
||||
- [ ] 记录变更事件消费逻辑
|
||||
|
||||
### 决策点
|
||||
1. 写入是单条还是批量(最多 500 条)?
|
||||
2. 是否需要 `client_token` 幂等保护?
|
||||
3. 是否要同时处理附件上传/下载链路?
|
||||
|
||||
## Verify
|
||||
|
||||
### API 与参数
|
||||
- [ ] 路径、方法、分页参数与 `reference/01-record-openapi/apis.md` 对齐
|
||||
- [ ] `fields` 结构映射与 `reference/02-record-structure/db-schema.md` 对齐
|
||||
|
||||
### 事件与幂等
|
||||
- [ ] 记录变更事件消费字段对齐 `reference/03-events/events-topics.md`
|
||||
- [ ] 写接口幂等与重试策略明确(含 UUID client_token)
|
||||
|
||||
### 安全与权限
|
||||
- [ ] 鉴权模式、字段级敏感信息权限对齐 `reference/04-security/security-model.md`
|
||||
|
||||
## Execute
|
||||
|
||||
### 1. 读取与检索
|
||||
1. 先 `list/get` 获取基线数据和分页状态。
|
||||
2. 按需增加筛选表达式,避免全量扫描。
|
||||
|
||||
### 2. 写入与批量
|
||||
1. 单条场景使用 `create/update/delete`。
|
||||
2. 批量场景使用 `batch_create/batch_update/batch_delete`,每次不超过 500。
|
||||
3. 对写请求设置串行化策略,必要时附 `client_token`。
|
||||
|
||||
### 3. 附件协同
|
||||
1. 先上传素材拿到 `file_token`。
|
||||
2. 再写入记录附件字段。
|
||||
3. 下载时先从记录读取 `file_token` 再调用下载接口。
|
||||
|
||||
### 4. 事件消费
|
||||
1. 处理 `bitable_record_changed` 事件。
|
||||
2. 使用 `event_id` 去重,避免超时重推导致重复处理。
|
||||
|
||||
## Pitfalls
|
||||
|
||||
1. **批量写超出上限**:单次最多 500 条,见 `reference/01-record-openapi/apis.md`。
|
||||
2. **fields 结构硬编码错误**:不同字段类型值结构不同,见 `reference/02-record-structure/db-schema.md`。
|
||||
3. **未使用幂等键导致重复写入**:重试场景可能产生重复副作用,见 `reference/01-record-openapi/apis.md`。
|
||||
4. **附件流程顺序错误**:必须先拿 file_token 再写记录,见 `reference/02-record-structure/db-schema.md`。
|
||||
5. **事件重复消费**:3 秒超时会重推,见 `reference/03-events/events-topics.md`。
|
||||
|
||||
## Related References
|
||||
|
||||
- `reference/01-record-openapi/apis.md`
|
||||
- `reference/02-record-structure/db-schema.md`
|
||||
- `reference/03-events/events-topics.md`
|
||||
- `reference/04-security/security-model.md`
|
||||
- `reference/05-state-machine/state-machine.md`
|
||||
@@ -0,0 +1,4 @@
|
||||
# Skeleton Example
|
||||
|
||||
- 仅提供调用骨架,不包含完整业务实现。
|
||||
- 实际编码前先对照 `reference/` 校验参数、权限、错误码与频控限制。
|
||||
@@ -0,0 +1,23 @@
|
||||
## Record API 矩阵
|
||||
|
||||
- DDS-Section: A.29~A.36(检索/列出/新增/更新/删除/批量)
|
||||
- DDS-Lines: L7926-L12085
|
||||
|
||||
### Extract
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| GET | `/bitable/v1/apps/{app_token}/tables/{table_id}/records/{record_id}` | 检索记录 |
|
||||
| GET | `/bitable/v1/apps/{app_token}/tables/{table_id}/records` | 列出记录 |
|
||||
| POST | `/bitable/v1/apps/{app_token}/tables/{table_id}/records` | 新增记录 |
|
||||
| PUT | `/bitable/v1/apps/{app_token}/tables/{table_id}/records/{record_id}` | 更新记录 |
|
||||
| DELETE | `/bitable/v1/apps/{app_token}/tables/{table_id}/records/{record_id}` | 删除记录 |
|
||||
| POST | `/bitable/v1/apps/{app_token}/tables/{table_id}/records/batch_create` | 批量新增 |
|
||||
| POST | `/bitable/v1/apps/{app_token}/tables/{table_id}/records/batch_update` | 批量更新 |
|
||||
| POST | `/bitable/v1/apps/{app_token}/tables/{table_id}/records/batch_delete` | 批量删除 |
|
||||
|
||||
关键约束:
|
||||
- 批量接口单次最多 500。
|
||||
- 写接口不建议并发。
|
||||
- 部分批量接口支持 `client_token` 幂等。
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
## 记录数据结构
|
||||
|
||||
- DDS-Section: A.3 数据结构(记录/fields/value)
|
||||
- DDS-Lines: L475-L571
|
||||
|
||||
### Extract
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `record_id` | string | 记录唯一标识 |
|
||||
| `fields` | map | 字段名到值的映射 |
|
||||
|
||||
`fields` 值结构按字段类型变化(文本、数字、人员、附件、关联等)。
|
||||
|
||||
## 附件字段协同
|
||||
|
||||
- DDS-Section: A.6 附件字段说明
|
||||
- DDS-Lines: L1370-L1534
|
||||
|
||||
### Extract
|
||||
|
||||
1. 先调用上传素材接口获取 `file_token`。
|
||||
2. 再通过记录写接口将附件写入字段。
|
||||
3. 下载时先通过记录读取 `file_token`,再调用下载接口。
|
||||
|
||||
## 本地 DB Schema
|
||||
|
||||
- DDS-Section: 全文
|
||||
- DDS-Lines: L1-L18211
|
||||
|
||||
### Extract
|
||||
|
||||
[TBD] 文档未给出记录在业务系统持久化时的表结构与索引。
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
## 记录变更事件
|
||||
|
||||
- DDS-Section: A.9 多维表格记录变更
|
||||
- DDS-Lines: L1811-L1915
|
||||
|
||||
### Extract
|
||||
|
||||
| 事件 | 说明 |
|
||||
|---|---|
|
||||
| `drive.file.bitable_record_changed_v1` | 记录变更事件 |
|
||||
|
||||
关键字段:`event_id`, `event_type`, `tenant_key`, `table_id`, `record_id`。
|
||||
|
||||
## 投递语义
|
||||
|
||||
- DDS-Section: 事件与回调处理说明
|
||||
- DDS-Lines: L89, L18209
|
||||
|
||||
### Extract
|
||||
|
||||
- 接收端需在 3 秒内响应,否则重推。
|
||||
- 消费端需做基于 `event_id` 的幂等去重。
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
## Record 接口鉴权
|
||||
|
||||
- DDS-Section: 接入指南-鉴权 / 记录 API security 声明
|
||||
- DDS-Lines: L311-L371, L8529+, L9268+, L9842+
|
||||
|
||||
### Extract
|
||||
|
||||
| 维度 | 说明 |
|
||||
|---|---|
|
||||
| token | `tenant_access_token` 或 `user_access_token` |
|
||||
| 权限失败 | 高级权限或文档协作者权限未覆盖时会拒绝 |
|
||||
| ID 字段 | `app_token` + `table_id` + `record_id` 需一致来源 |
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
## 状态机定义
|
||||
|
||||
- DDS-Section: 全文
|
||||
- DDS-Lines: L1-L18211
|
||||
|
||||
### Extract
|
||||
|
||||
[TBD] 文档未定义 Record 业务状态机(状态枚举、转移守卫、补偿动作)。
|
||||
|
||||
### 最小补充信息清单
|
||||
|
||||
1. 记录业务状态集合(如 draft/active/archived)
|
||||
2. 转移触发条件与角色权限
|
||||
3. 转移失败补偿策略
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
# verify.sh - skill structure/content quick validation
|
||||
set -e
|
||||
PASS=0
|
||||
FAIL=0
|
||||
|
||||
pass() {
|
||||
echo "PASS: $1"
|
||||
PASS=$((PASS+1))
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo "FAIL: $1"
|
||||
FAIL=$((FAIL+1))
|
||||
}
|
||||
|
||||
check_cmd() {
|
||||
local desc="$1"
|
||||
shift
|
||||
if "$@"; then
|
||||
pass "$desc"
|
||||
else
|
||||
fail "$desc"
|
||||
fi
|
||||
}
|
||||
|
||||
SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
|
||||
check_cmd "SKILL.md exists" test -f "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "reference dir exists" test -d "$SKILL_DIR/reference"
|
||||
|
||||
line_count=$(wc -l < "$SKILL_DIR/SKILL.md")
|
||||
if [ "$line_count" -lt 500 ]; then
|
||||
pass "SKILL.md < 500 lines"
|
||||
else
|
||||
fail "SKILL.md < 500 lines"
|
||||
fi
|
||||
|
||||
check_cmd "has Plan" grep -q '^## Plan' "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "has Verify" grep -q '^## Verify' "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "has Execute" grep -q '^## Execute' "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "has Pitfalls" grep -q '^## Pitfalls' "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "reference has DDS-Section" grep -rq 'DDS-Section:' "$SKILL_DIR/reference"
|
||||
check_cmd "reference has DDS-Lines" grep -rq 'DDS-Lines:' "$SKILL_DIR/reference"
|
||||
|
||||
echo "RESULT: $PASS PASS / $FAIL FAIL"
|
||||
[ $FAIL -eq 0 ]
|
||||
@@ -0,0 +1,86 @@
|
||||
---
|
||||
name: developing-bitable-role-member
|
||||
description: 指导飞书多维表格高级权限 Role/Member 模块开发(Guides Bitable advanced permission role/member development)。包含角色管理、协作者管理、前置开关检查与权限联动。触发场景 Trigger: 配置高级权限角色、批量增删协作者、排查 role_id/member_id 相关鉴权失败。关键词 Keywords: role, member, advanced permission, role_id, member_id, bitable。
|
||||
argument-hint: "<action> <app_token role_id?> - e.g., 'create role appbxxx', 'add members appbxxx rolxxx'"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
- Edit
|
||||
- Glob
|
||||
- Grep
|
||||
- Bash
|
||||
---
|
||||
|
||||
# Developing Bitable Role & Member
|
||||
|
||||
用于高级权限域开发:角色(Role)与协作者(Member)的增删改查,及其与元数据开关、文档权限的联动处理。
|
||||
|
||||
## Quick Context
|
||||
|
||||
```bash
|
||||
# 定位角色与协作者 API
|
||||
!`rg -n "app-role|roles/\{role_id\}|role-member|members" -S . | head -n 120`
|
||||
|
||||
# 定位高级权限前置与无权限错误处理
|
||||
!`rg -n "is_advanced|OperationTypeError|无访问权限|permission-member" -S . | head -n 100`
|
||||
```
|
||||
|
||||
## Plan
|
||||
|
||||
### 产物清单
|
||||
- [ ] Role API 封装
|
||||
- [ ] Member API 封装(含批量)
|
||||
- [ ] 高级权限开关前置检查
|
||||
- [ ] 权限联动处理(文档权限)
|
||||
|
||||
### 决策点
|
||||
1. 当前 app 是否已开启高级权限?
|
||||
2. 是否需要同时同步云文档协作者权限?
|
||||
3. 成员操作是否采用批量接口?
|
||||
|
||||
## Verify
|
||||
|
||||
### API 与参数
|
||||
- [ ] 角色接口对齐 `reference/01-role-openapi/apis.md`
|
||||
- [ ] 协作者接口对齐 `reference/02-member-openapi/apis.md`
|
||||
|
||||
### 前置与权限
|
||||
- [ ] 开关前置、延迟生效、无权限分支对齐 `reference/03-security/security-model.md`
|
||||
- [ ] 与文档权限联动策略对齐 `reference/04-dependencies/dependencies.md`
|
||||
|
||||
### 风险控制
|
||||
- [ ] 批量操作有容量与失败处理策略
|
||||
- [ ] 删除角色前已确认成员迁移或清理方案
|
||||
|
||||
## Execute
|
||||
|
||||
### 1. 前置检查
|
||||
1. 调用 app 元数据接口确认高级权限已开启。
|
||||
2. 若未开启,先走 app metadata 更新流程。
|
||||
|
||||
### 2. 角色管理
|
||||
1. `list/create/update/delete role` 按资源策略执行。
|
||||
2. 变更后回读角色详情,校验权限范围。
|
||||
|
||||
### 3. 协作者管理
|
||||
1. 通过 `list/create/delete` 或批量接口维护成员。
|
||||
2. 视场景同步云文档权限,避免“高级权限已加但文档无权限”。
|
||||
|
||||
### 4. 错误恢复
|
||||
1. 对延迟生效错误(`OperationTypeError`)实施短周期重试。
|
||||
2. 对权限缺失错误直接中止并返回修复建议。
|
||||
|
||||
## Pitfalls
|
||||
|
||||
1. **未开启高级权限就调用 Role/Member**:必然失败,见 `reference/03-security/security-model.md`。
|
||||
2. **只加高级权限不加文档权限**:用户仍可能不可访问,见 `reference/04-dependencies/dependencies.md`。
|
||||
3. **角色删除前未迁移成员**:造成权限漂移,需先清理成员,见 `reference/02-member-openapi/apis.md`。
|
||||
4. **忽略开启延迟**:开关后立即调用可能报错,见 `reference/03-security/security-model.md`。
|
||||
|
||||
## Related References
|
||||
|
||||
- `reference/01-role-openapi/apis.md`
|
||||
- `reference/02-member-openapi/apis.md`
|
||||
- `reference/03-security/security-model.md`
|
||||
- `reference/04-dependencies/dependencies.md`
|
||||
- `reference/05-db-schema/db-schema.md`
|
||||
@@ -0,0 +1,4 @@
|
||||
# Skeleton Example
|
||||
|
||||
- 仅提供调用骨架,不包含完整业务实现。
|
||||
- 实际编码前先对照 `reference/` 校验参数、权限、错误码与频控限制。
|
||||
@@ -0,0 +1,18 @@
|
||||
## Role API 矩阵
|
||||
|
||||
- DDS-Section: A.41~A.44(列出/新增/删除/更新自定义角色)
|
||||
- DDS-Lines: L13935-L15372
|
||||
|
||||
### Extract
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| GET | `/bitable/v1/apps/{app_token}/roles` | 列出角色 |
|
||||
| POST | `/bitable/v1/apps/{app_token}/roles/{role_id}` | 新增角色 |
|
||||
| DELETE | `/bitable/v1/apps/{app_token}/roles/{role_id}` | 删除角色 |
|
||||
| PUT | `/bitable/v1/apps/{app_token}/roles/{role_id}` | 更新角色 |
|
||||
|
||||
约束:
|
||||
- 角色接口依赖高级权限开启。
|
||||
- 写操作建议串行处理。
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
## Member API 矩阵
|
||||
|
||||
- DDS-Section: A.45~A.49(批量删/批量增/列出/新增/删除协作者)
|
||||
- DDS-Lines: L15373-L16470
|
||||
|
||||
### Extract
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| POST | `/bitable/v1/apps/{app_token}/roles/{role_id}/members/batch_delete` | 批量删除协作者 |
|
||||
| POST | `/bitable/v1/apps/{app_token}/roles/{role_id}/members/batch_create` | 批量新增协作者 |
|
||||
| GET | `/bitable/v1/apps/{app_token}/roles/{role_id}/members` | 列出协作者 |
|
||||
| POST | `/bitable/v1/apps/{app_token}/roles/{role_id}/members` | 新增协作者 |
|
||||
| DELETE | `/bitable/v1/apps/{app_token}/roles/{role_id}/members/{member_id}` | 删除协作者 |
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
## 高级权限前置与异常
|
||||
|
||||
- DDS-Section: A.7 概述 / A.11 更新元数据
|
||||
- DDS-Lines: L1547-L1565, L2229-L2233, L2286-L2289
|
||||
|
||||
### Extract
|
||||
|
||||
| 规则 | 说明 |
|
||||
|---|---|
|
||||
| 开关前置 | Role/Member 调用前必须开启高级权限 |
|
||||
| 生效延迟 | 开启后短时可能报 `OperationTypeError` |
|
||||
| 无权限 | 需在高级权限中加入包含应用的群并授予读写 |
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
## 权限联动依赖
|
||||
|
||||
- DDS-Section: A.7 概述
|
||||
- DDS-Lines: L1559-L1561
|
||||
|
||||
### Extract
|
||||
|
||||
- 高级权限协作者与云文档协作者是两套身份体系。
|
||||
- 新增高级权限协作者后,建议通过 drive 权限接口同步文档权限。
|
||||
|
||||
## 上下游依赖
|
||||
|
||||
- DDS-Section: 接入指南-内容
|
||||
- DDS-Lines: L294-L303
|
||||
|
||||
### Extract
|
||||
|
||||
`App(is_advanced=true) -> Role -> Member`。
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
## 权限域数据库结构
|
||||
|
||||
- DDS-Section: 全文
|
||||
- DDS-Lines: L1-L18211
|
||||
|
||||
### Extract
|
||||
|
||||
[TBD] 文档未描述 Role/Member 在业务侧持久化结构与审计表。
|
||||
|
||||
### 最小补充信息清单
|
||||
|
||||
1. 角色权限项结构与版本字段
|
||||
2. 成员-角色关系表唯一键
|
||||
3. 权限变更审计日志表
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
# verify.sh - skill structure/content quick validation
|
||||
set -e
|
||||
PASS=0
|
||||
FAIL=0
|
||||
|
||||
pass() {
|
||||
echo "PASS: $1"
|
||||
PASS=$((PASS+1))
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo "FAIL: $1"
|
||||
FAIL=$((FAIL+1))
|
||||
}
|
||||
|
||||
check_cmd() {
|
||||
local desc="$1"
|
||||
shift
|
||||
if "$@"; then
|
||||
pass "$desc"
|
||||
else
|
||||
fail "$desc"
|
||||
fi
|
||||
}
|
||||
|
||||
SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
|
||||
check_cmd "SKILL.md exists" test -f "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "reference dir exists" test -d "$SKILL_DIR/reference"
|
||||
|
||||
line_count=$(wc -l < "$SKILL_DIR/SKILL.md")
|
||||
if [ "$line_count" -lt 500 ]; then
|
||||
pass "SKILL.md < 500 lines"
|
||||
else
|
||||
fail "SKILL.md < 500 lines"
|
||||
fi
|
||||
|
||||
check_cmd "has Plan" grep -q '^## Plan' "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "has Verify" grep -q '^## Verify' "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "has Execute" grep -q '^## Execute' "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "has Pitfalls" grep -q '^## Pitfalls' "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "reference has DDS-Section" grep -rq 'DDS-Section:' "$SKILL_DIR/reference"
|
||||
check_cmd "reference has DDS-Lines" grep -rq 'DDS-Lines:' "$SKILL_DIR/reference"
|
||||
|
||||
echo "RESULT: $PASS PASS / $FAIL FAIL"
|
||||
[ $FAIL -eq 0 ]
|
||||
@@ -0,0 +1,83 @@
|
||||
---
|
||||
name: developing-bitable-table-view
|
||||
description: 指导飞书多维表格 Table 与 View 模块开发(Guides Bitable table and view module development)。包含数据表管理、视图管理、分页与错误处理、写接口并发约束。触发场景 Trigger: 新增/删除数据表、创建/删除视图、调整视图属性、排查 table_id/view_id 相关错误。关键词 Keywords: table, view, table_id, view_id, bitable, openapi。
|
||||
argument-hint: "<action> <app_token/table_id/view_id> - e.g., 'create table appbxxx', 'delete view tblxxx vewxxx'"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
- Edit
|
||||
- Glob
|
||||
- Grep
|
||||
- Bash
|
||||
---
|
||||
|
||||
# Developing Bitable Table & View
|
||||
|
||||
聚焦 `table_id` 与 `view_id` 资源生命周期,包括列表、创建、删除、更新,以及上游 App 与下游 Record/Field 的依赖影响。
|
||||
|
||||
## Quick Context
|
||||
|
||||
```bash
|
||||
# 定位表和视图相关接口
|
||||
!`rg -n "app-table|tables/\{table_id\}|views|app-table-view" -S . | head -n 100`
|
||||
|
||||
# 定位写接口并发与限流防护逻辑
|
||||
!`rg -n "QPS|batch|并发|OperationTypeError|retry|client_token" -S . | head -n 80`
|
||||
```
|
||||
|
||||
## Plan
|
||||
|
||||
### 产物清单
|
||||
- [ ] Table API 调用点与参数封装
|
||||
- [ ] View API 调用点与参数封装
|
||||
- [ ] 分页遍历/幂等/重试策略
|
||||
- [ ] 资源删除后的下游影响评估
|
||||
|
||||
### 决策点
|
||||
1. 是否需要批量建表/删表能力?
|
||||
2. 视图变更是否影响既有筛选与展示语义?
|
||||
3. 是否有并发写风险(同 app 同时发多个写请求)?
|
||||
|
||||
## Verify
|
||||
|
||||
### API 对齐
|
||||
- [ ] Table 接口路径与方法对齐 `reference/02-table-openapi/apis.md`
|
||||
- [ ] View 接口路径与方法对齐 `reference/03-view-openapi/apis.md`
|
||||
|
||||
### 约束检查
|
||||
- [ ] QPS、并发写限制已按 `reference/02-table-openapi/apis.md` 与 `reference/03-view-openapi/apis.md` 落地
|
||||
- [ ] 删除类操作前已验证依赖关系(Record/Field/业务视图)
|
||||
|
||||
### 安全检查
|
||||
- [ ] token 模式与权限范围匹配,见 `reference/04-security/security-model.md`
|
||||
|
||||
## Execute
|
||||
|
||||
### 1. 数据表操作
|
||||
1. 先 `list tables` 确认目标是否已存在。
|
||||
2. 执行 `create / batch_create / delete / batch_delete`。
|
||||
3. 对写请求串行化,避免同 app 并发写。
|
||||
|
||||
### 2. 视图操作
|
||||
1. 先 `list views` 获取目标 `view_id`。
|
||||
2. 执行 `create / update / delete / get`。
|
||||
3. 修改后做一次读取回放,确认属性生效。
|
||||
|
||||
### 3. 失败处理
|
||||
1. 对参数类错误立即失败并回传上下文。
|
||||
2. 对可恢复错误(冲突、短暂不可用)使用有限重试。
|
||||
|
||||
## Pitfalls
|
||||
|
||||
1. **删除表前未处理下游依赖**:Record/Field/业务配置悬空,先看 `reference/01-resource-model/dependencies.md`。
|
||||
2. **同一 app 并发写表/视图**:会触发并发写限制,先看 `reference/02-table-openapi/apis.md`。
|
||||
3. **view_id 获取方式错误**:不同形态下取值路径不同,先看 `reference/01-resource-model/dependencies.md`。
|
||||
4. **分页处理缺失**:list 接口有 `page_token`,漏处理会丢数据,见 `reference/02-table-openapi/apis.md`。
|
||||
|
||||
## Related References
|
||||
|
||||
- `reference/01-resource-model/dependencies.md`
|
||||
- `reference/02-table-openapi/apis.md`
|
||||
- `reference/03-view-openapi/apis.md`
|
||||
- `reference/04-security/security-model.md`
|
||||
- `reference/05-db-schema/db-schema.md`
|
||||
@@ -0,0 +1,4 @@
|
||||
# Skeleton Example
|
||||
|
||||
- 仅提供调用骨架,不包含完整业务实现。
|
||||
- 实际编码前先对照 `reference/` 校验参数、权限、错误码与频控限制。
|
||||
@@ -0,0 +1,16 @@
|
||||
## 资源关系
|
||||
|
||||
- DDS-Section: 资源:数据表 Table / 资源:视图 View / 接入指南-参数说明
|
||||
- DDS-Lines: L130-L170, L442-L452
|
||||
|
||||
### Extract
|
||||
|
||||
| 资源 | 标识 | 关系 |
|
||||
|---|---|---|
|
||||
| Table | `table_id` | 归属 `app_token` |
|
||||
| View | `view_id` | 归属 `table_id` |
|
||||
|
||||
获取建议:
|
||||
- `table_id` 可通过列出数据表接口获取。
|
||||
- `view_id` 可通过列出视图接口获取(部分场景不可直接从 doc block 得到)。
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
## 数据表接口矩阵
|
||||
|
||||
- DDS-Section: A.12~A.17(更新/列出/新增/批量新增/删除/批量删除数据表)
|
||||
- DDS-Lines: L2431-L4013
|
||||
|
||||
### Extract
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| PUT | `/bitable/v1/apps/{app_token}/tables/{table_id}` | 更新数据表 |
|
||||
| GET | `/bitable/v1/apps/{app_token}/tables` | 列出数据表 |
|
||||
| POST | `/bitable/v1/apps/{app_token}/tables` | 新增数据表 |
|
||||
| POST | `/bitable/v1/apps/{app_token}/tables/batch_create` | 批量新增 |
|
||||
| DELETE | `/bitable/v1/apps/{app_token}/tables/{table_id}` | 删除数据表 |
|
||||
| POST | `/bitable/v1/apps/{app_token}/tables/batch_delete` | 批量删除 |
|
||||
|
||||
约束:
|
||||
- 写接口常见 10 QPS;读接口常见 20 QPS。
|
||||
- 不支持并发写接口。
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
## 视图接口矩阵
|
||||
|
||||
- DDS-Section: A.20~A.24(更新/检索/列出/新增/删除视图)
|
||||
- DDS-Lines: L4700-L6501
|
||||
|
||||
### Extract
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| PUT | `/bitable/v1/apps/{app_token}/tables/{table_id}/views/{view_id}` | 更新视图 |
|
||||
| GET | `/bitable/v1/apps/{app_token}/tables/{table_id}/views/{view_id}` | 检索视图 |
|
||||
| GET | `/bitable/v1/apps/{app_token}/tables/{table_id}/views` | 列出视图 |
|
||||
| POST | `/bitable/v1/apps/{app_token}/tables/{table_id}/views` | 新增视图 |
|
||||
| DELETE | `/bitable/v1/apps/{app_token}/tables/{table_id}/views/{view_id}` | 删除视图 |
|
||||
|
||||
约束:
|
||||
- 写接口不建议并发。
|
||||
- 出现计算超时类错误时可按文档建议重试。
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
## 鉴权与权限
|
||||
|
||||
- DDS-Section: 接入指南-鉴权 / 各接口 security 定义
|
||||
- DDS-Lines: L311-L371, L2458+, L5575+
|
||||
|
||||
### Extract
|
||||
|
||||
| 维度 | 说明 |
|
||||
|---|---|
|
||||
| token 类型 | `tenant_access_token` / `user_access_token` |
|
||||
| 权限异常 | 无访问权限常见于高级权限配置未覆盖应用主体 |
|
||||
| 额外要求 | 商店应用场景需关注 `tenant_key` |
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
## 本地持久化结构
|
||||
|
||||
- DDS-Section: 全文
|
||||
- DDS-Lines: L1-L18211
|
||||
|
||||
### Extract
|
||||
|
||||
[TBD] 文档未提供 Table/View 在业务系统落地时的数据库结构定义。
|
||||
|
||||
### 最小补充信息清单
|
||||
|
||||
1. 表视图缓存模型(主键、唯一键)
|
||||
2. 软删除或硬删除策略
|
||||
3. 变更审计字段与索引
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
# verify.sh - skill structure/content quick validation
|
||||
set -e
|
||||
PASS=0
|
||||
FAIL=0
|
||||
|
||||
pass() {
|
||||
echo "PASS: $1"
|
||||
PASS=$((PASS+1))
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo "FAIL: $1"
|
||||
FAIL=$((FAIL+1))
|
||||
}
|
||||
|
||||
check_cmd() {
|
||||
local desc="$1"
|
||||
shift
|
||||
if "$@"; then
|
||||
pass "$desc"
|
||||
else
|
||||
fail "$desc"
|
||||
fi
|
||||
}
|
||||
|
||||
SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
|
||||
check_cmd "SKILL.md exists" test -f "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "reference dir exists" test -d "$SKILL_DIR/reference"
|
||||
|
||||
line_count=$(wc -l < "$SKILL_DIR/SKILL.md")
|
||||
if [ "$line_count" -lt 500 ]; then
|
||||
pass "SKILL.md < 500 lines"
|
||||
else
|
||||
fail "SKILL.md < 500 lines"
|
||||
fi
|
||||
|
||||
check_cmd "has Plan" grep -q '^## Plan' "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "has Verify" grep -q '^## Verify' "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "has Execute" grep -q '^## Execute' "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "has Pitfalls" grep -q '^## Pitfalls' "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "reference has DDS-Section" grep -rq 'DDS-Section:' "$SKILL_DIR/reference"
|
||||
check_cmd "reference has DDS-Lines" grep -rq 'DDS-Lines:' "$SKILL_DIR/reference"
|
||||
|
||||
echo "RESULT: $PASS PASS / $FAIL FAIL"
|
||||
[ $FAIL -eq 0 ]
|
||||
@@ -0,0 +1,114 @@
|
||||
---
|
||||
name: developing-feishu-bitable-system
|
||||
description: 指导飞书多维表格系统级开发与跨模块一致性(Guides system-level development for Feishu Bitable)。包含模块边界、全局约束、鉴权模型、事件交付和变更路由。触发场景 Trigger: 跨模块改造、资源模型调整、接口策略统一、事件接入方案选择。关键词 Keywords: feishu, bitable, system, architecture, app, table, record, field, role, event, callback。
|
||||
argument-hint: "<change-scope> <goal> - e.g., 'record+field add batch update', 'role enable advanced permission'"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Glob
|
||||
- Grep
|
||||
- Bash
|
||||
---
|
||||
|
||||
# Developing Feishu Bitable System
|
||||
|
||||
用于统一飞书多维表格的系统级决策:先判定变更影响域,再路由到对应模块 Skill,并叠加横切约束(契约、鉴权、并发、SDK)。
|
||||
|
||||
## Quick Context
|
||||
|
||||
```bash
|
||||
# 扫描仓库中的 Bitable 资源路径与调用热点
|
||||
!`rg -n "bitable/v1/apps|tables|views|records|fields|roles|members" -S . | head -n 80`
|
||||
|
||||
# 扫描鉴权与事件接入相关实现
|
||||
!`rg -n "WithUserAccessToken|WithTenantAccessToken|WithTenantKey|EventDispatcher|verificationToken|eventEncryptKey" -S . | head -n 80`
|
||||
```
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```text
|
||||
Client/Job
|
||||
-> App Metadata
|
||||
-> Table/View
|
||||
-> Record
|
||||
-> Field
|
||||
-> Role/Member (advanced permission)
|
||||
-> Event/Callback Inbound
|
||||
Cross-cut: Contracts / Auth / Write-Concurrency / Go SDK
|
||||
```
|
||||
|
||||
## Module Registry
|
||||
|
||||
| 模块 | 职责 | 对应 Skill |
|
||||
|---|---|---|
|
||||
| App Metadata | app 元数据与高级权限开关 | `developing-bitable-app-metadata` |
|
||||
| Table & View | 数据表与视图管理 | `developing-bitable-table-view` |
|
||||
| Record | 记录 CRUD 与批量操作 | `developing-bitable-record` |
|
||||
| Field | 字段定义、类型与属性变更 | `developing-bitable-field` |
|
||||
| Role & Member | 高级权限角色与协作者 | `developing-bitable-role-member` |
|
||||
| Event & Callback | 事件订阅、回调处理、3 秒响应约束 | `developing-bitable-event-callback` |
|
||||
|
||||
## Plan
|
||||
|
||||
### 产物清单
|
||||
- [ ] 变更影响模块清单(主模块 + 受影响模块)
|
||||
- [ ] 需要叠加的横切 Skill 清单
|
||||
- [ ] 契约变更面(路径/字段/错误码/权限)
|
||||
- [ ] 风险与回滚点(重点是写并发和幂等)
|
||||
|
||||
### 决策点
|
||||
1. 变更是否跨越两个及以上资源模块?
|
||||
2. 是否触及写接口(需执行串行化与幂等策略)?
|
||||
3. 是否涉及高级权限开关或角色策略?
|
||||
4. 是否需要事件/回调通道而非轮询读取?
|
||||
|
||||
## Verify
|
||||
|
||||
### 架构边界
|
||||
- [ ] 变更只在目标模块内落地,跨模块依赖已在 `reference/01-architecture-overview/dependencies.md` 对齐
|
||||
- [ ] 新增能力没有绕过 App/Table 资源层级
|
||||
|
||||
### 全局约束
|
||||
- [ ] QPS 与批量上限已对齐 `reference/02-global-constraints/apis.md`
|
||||
- [ ] 写接口并发冲突风险已评估并规避(串行或幂等键)
|
||||
|
||||
### 安全与权限
|
||||
- [ ] token 模式与调用身份已对齐 `reference/03-auth-and-permissions/security-model.md`
|
||||
- [ ] 高级权限开启前置条件与延迟影响已覆盖
|
||||
|
||||
### 事件与回调
|
||||
- [ ] 事件订阅/回调路径满足 3 秒响应约束,见 `reference/04-event-delivery/events-topics.md`
|
||||
|
||||
## Execute
|
||||
|
||||
### 1. 识别变更类型
|
||||
1. 标注主资源:`app/table/view/record/field/role/member/event`。
|
||||
2. 判断是否含写操作:`create/update/delete/batch_*`。
|
||||
3. 判断是否含权限变化:高级权限开关、角色或协作者变更。
|
||||
|
||||
### 2. 路由到模块 Skill
|
||||
1. 单模块改动:直接进入对应模块 Skill。
|
||||
2. 跨模块改动:按调用链顺序执行(上游契约先变更、下游消费后变更)。
|
||||
3. 若涉及 SDK 接入方式或调用风格,叠加 `implementing-bitable-go-sdk`。
|
||||
|
||||
### 3. 叠加横切校验
|
||||
1. 契约:执行 `designing-bitable-contracts` 检查路径、参数、错误码。
|
||||
2. 鉴权:执行 `implementing-bitable-auth-access` 检查 token 与权限。
|
||||
3. 并发:执行 `managing-bitable-write-concurrency` 检查 QPS、幂等、重试。
|
||||
|
||||
### 4. 输出变更包
|
||||
1. 给出变更影响矩阵(模块 × 接口 × 权限 × 风险)。
|
||||
2. 明确灰度步骤与回滚触发条件。
|
||||
|
||||
## Pitfalls
|
||||
|
||||
1. **把 Role/Member 当作普通 CRUD 资源处理**:高级权限开关未完成时直接调用会失败,先查 `reference/03-auth-and-permissions/security-model.md`。
|
||||
2. **多写接口并发提交**:文档明确不支持并发写接口,需先查 `reference/02-global-constraints/apis.md`。
|
||||
3. **事件通道没有 3 秒响应保障**:会触发重推与重复消费,先查 `reference/04-event-delivery/events-topics.md`。
|
||||
4. **跨模块修改只改调用方不改契约源**:导致路径或字段漂移,先对照 `reference/01-architecture-overview/dependencies.md`。
|
||||
|
||||
## Related References
|
||||
|
||||
- `reference/01-architecture-overview/dependencies.md`:资源层次、模块依赖、调用链
|
||||
- `reference/02-global-constraints/apis.md`:QPS、批量上限、写并发、幂等约束
|
||||
- `reference/03-auth-and-permissions/security-model.md`:token 模型与高级权限前置条件
|
||||
- `reference/04-event-delivery/events-topics.md`:事件体、回调模式、重推约束
|
||||
@@ -0,0 +1,4 @@
|
||||
# Skeleton Example
|
||||
|
||||
- 仅提供调用骨架,不包含完整业务实现。
|
||||
- 实际编码前先对照 `reference/` 校验参数、权限、错误码与频控限制。
|
||||
@@ -0,0 +1,30 @@
|
||||
## 资源分层与模块边界
|
||||
|
||||
- DDS-Section: A.1 概述 / A.2 接入指南(形态、内容)
|
||||
- DDS-Lines: L111-L257, L272-L303
|
||||
|
||||
### Extract
|
||||
|
||||
| 层级 | 资源 | 核心标识 | 说明 |
|
||||
|---|---|---|---|
|
||||
| L1 | App | `app_token` | 多维表格应用根资源 |
|
||||
| L2 | Table | `table_id` | App 下的数据容器 |
|
||||
| L2 | Role | `role_id` | App 下高级权限角色 |
|
||||
| L3 | View | `view_id` | Table 下视图 |
|
||||
| L3 | Record | `record_id` | Table 下记录 |
|
||||
| L3 | Field | `field_id` | Table 下字段 |
|
||||
| L3 | Member | `member_id` | Role 下协作者 |
|
||||
|
||||
## 跨模块依赖链路
|
||||
|
||||
- DDS-Section: A.2 接入指南 / A.6 附件字段说明 / A.7 高级权限概述
|
||||
- DDS-Lines: L302-L303, L1410-L1534, L1547-L1588
|
||||
|
||||
### Extract
|
||||
|
||||
| 源模块 | 目标模块 | 协议/接口 | 依赖说明 |
|
||||
|---|---|---|---|
|
||||
| Record | Drive Media | upload/download API | 附件字段依赖 file_token |
|
||||
| RoleMember | Drive Permission | permission-member/create | 高级权限协作者建议同步文档权限 |
|
||||
| EventCallback | 业务模块 | WebSocket/HTTP callback | 事件回调驱动记录或字段消费逻辑 |
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
## 全局频控与批量约束
|
||||
|
||||
- DDS-Section: 接入指南-限制 / 各 OpenAPI Specification 描述
|
||||
- DDS-Lines: L377-L384, L1954+, L2233+, L2635+, L8529+, L10629+
|
||||
|
||||
### Extract
|
||||
|
||||
| 规则 | 约束 |
|
||||
|---|---|
|
||||
| QPS | 多数读接口 20 QPS,写接口常见 10 QPS(以具体接口描述为准) |
|
||||
| 批量记录操作 | 单次最多 500 条(batch create/update/delete) |
|
||||
| 批量结果 | 接口语义为整体成功或失败,不提供部分成功结果 |
|
||||
|
||||
## 写并发与幂等
|
||||
|
||||
- DDS-Section: 接入指南注意事项 / 各接口错误码说明
|
||||
- DDS-Lines: L309, L2067+, L3050+, L3339+, L9483+, L10847+, L12965+
|
||||
|
||||
### Extract
|
||||
|
||||
| 规则 | 说明 |
|
||||
|---|---|
|
||||
| 写并发 | 同一多维表格不建议并发调用写接口 |
|
||||
| 幂等键 | 部分写接口支持 `client_token`,要求 UUID 格式 |
|
||||
| 幂等冲突 | 发生冲突时需重生成幂等键再重试 |
|
||||
|
||||
## DB Schema
|
||||
|
||||
- DDS-Section: 全文
|
||||
- DDS-Lines: L1-L18211
|
||||
|
||||
### Extract
|
||||
|
||||
[TBD] 文档未给出服务端数据库表结构、索引、迁移策略。若需要落地后端存储,请补充:
|
||||
1. 业务表与映射表 DDL
|
||||
2. 索引与唯一键策略
|
||||
3. 迁移与回滚方案
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
## token 鉴权模型
|
||||
|
||||
- DDS-Section: 接入指南-鉴权 / Golang SDK 调用说明
|
||||
- DDS-Lines: L311-L371, L39-L41, L67-L69, L16503
|
||||
|
||||
### Extract
|
||||
|
||||
| 身份 | token | 说明 |
|
||||
|---|---|---|
|
||||
| 用户身份 | `user_access_token` | 用户态调用,刷新后旧 token 失效 |
|
||||
| 应用身份 | `tenant_access_token` | 应用态调用,SDK 可托管生命周期 |
|
||||
| 商店应用补充 | `tenant_key` | ISV 场景调用需显式传入 |
|
||||
|
||||
## 高级权限模型
|
||||
|
||||
- DDS-Section: A.7 概述 / 更新多维表格元数据
|
||||
- DDS-Lines: L1547-L1565, L2226-L2233, L2286-L2289
|
||||
|
||||
### Extract
|
||||
|
||||
| 规则 | 说明 |
|
||||
|---|---|
|
||||
| 开启前置 | 调用 Role/Member 接口前需先开启高级权限 |
|
||||
| 延迟生效 | 开启后短时间可能返回 `OperationTypeError`,需重试 |
|
||||
| 权限联动 | 高级权限协作者与云文档协作者不同,必要时同步文档权限 |
|
||||
| 非原子更新 | 更新元数据接口先改名称再开关权限,可能部分成功 |
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
## Bitable 事件定义
|
||||
|
||||
- DDS-Section: A.8 多维表格字段变更 / A.9 多维表格记录变更
|
||||
- DDS-Lines: L1599-L1712, L1815-L1915
|
||||
|
||||
### Extract
|
||||
|
||||
| 事件类型 | 关键字段 |
|
||||
|---|---|
|
||||
| `drive.file.bitable_field_changed_v1` | `event_id`, `event_type`, `tenant_key`, `table_id`, `field_id` |
|
||||
| `drive.file.bitable_record_changed_v1` | `event_id`, `event_type`, `tenant_key`, `table_id`, `record_id` |
|
||||
|
||||
## 回调交付约束
|
||||
|
||||
- DDS-Section: Golang SDK-处理事件 / 处理回调
|
||||
- DDS-Lines: L89, L16853-L16873, L17196-L17263, L18209
|
||||
|
||||
### Extract
|
||||
|
||||
| 约束 | 说明 |
|
||||
|---|---|
|
||||
| 响应时限 | 接收端需在 3 秒内响应,否则会触发重推 |
|
||||
| 长连接模式 | SDK 支持 WebSocket 长连接;集群模式非广播 |
|
||||
| HTTP 回调模式 | 需公网地址,接收 POST 推送 |
|
||||
| 验签/解密 | 若启用加密策略需提供 `verificationToken` 与 `eventEncryptKey` |
|
||||
|
||||
## 消费幂等
|
||||
|
||||
- DDS-Section: 事件与回调说明
|
||||
- DDS-Lines: L1706, L1846, L18209
|
||||
|
||||
### Extract
|
||||
|
||||
| 建议 | 原因 |
|
||||
|---|---|
|
||||
| 以 `event_id` 做去重 | 重推场景下避免重复处理 |
|
||||
| 记录 `tenant_key + resource_id` | 多租户隔离与重放定位 |
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
# verify.sh - skill structure/content quick validation
|
||||
set -e
|
||||
PASS=0
|
||||
FAIL=0
|
||||
|
||||
pass() {
|
||||
echo "PASS: $1"
|
||||
PASS=$((PASS+1))
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo "FAIL: $1"
|
||||
FAIL=$((FAIL+1))
|
||||
}
|
||||
|
||||
check_cmd() {
|
||||
local desc="$1"
|
||||
shift
|
||||
if "$@"; then
|
||||
pass "$desc"
|
||||
else
|
||||
fail "$desc"
|
||||
fi
|
||||
}
|
||||
|
||||
SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
|
||||
check_cmd "SKILL.md exists" test -f "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "reference dir exists" test -d "$SKILL_DIR/reference"
|
||||
|
||||
line_count=$(wc -l < "$SKILL_DIR/SKILL.md")
|
||||
if [ "$line_count" -lt 500 ]; then
|
||||
pass "SKILL.md < 500 lines"
|
||||
else
|
||||
fail "SKILL.md < 500 lines"
|
||||
fi
|
||||
|
||||
check_cmd "has Plan" grep -q '^## Plan' "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "has Verify" grep -q '^## Verify' "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "has Execute" grep -q '^## Execute' "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "has Pitfalls" grep -q '^## Pitfalls' "$SKILL_DIR/SKILL.md"
|
||||
check_cmd "reference has DDS-Section" grep -rq 'DDS-Section:' "$SKILL_DIR/reference"
|
||||
check_cmd "reference has DDS-Lines" grep -rq 'DDS-Lines:' "$SKILL_DIR/reference"
|
||||
|
||||
echo "RESULT: $PASS PASS / $FAIL FAIL"
|
||||
[ $FAIL -eq 0 ]
|
||||
@@ -0,0 +1,84 @@
|
||||
---
|
||||
name: implementing-bitable-auth-access
|
||||
description: 指导飞书多维表格鉴权与访问控制实现(Guides authentication and access control for Feishu Bitable)。包含 tenant/user token 选择、商店应用 tenant_key、高级权限开关与协作者联动。触发场景 Trigger: 鉴权失败排查、身份模式切换、权限策略改造、Role/Member 接口不可用问题。关键词 Keywords: auth, access, tenant_access_token, user_access_token, tenant_key, advanced permission。
|
||||
argument-hint: "<auth-scope> <scenario> - e.g., 'tenant token app-level api', 'role-member no-permission'"
|
||||
allowed-tools:
|
||||
- Read
|
||||
- Write
|
||||
- Edit
|
||||
- Glob
|
||||
- Grep
|
||||
- Bash
|
||||
---
|
||||
|
||||
# Implementing Bitable Auth & Access
|
||||
|
||||
用于统一 Bitable 的鉴权和权限控制实现,确保调用身份、token 生命周期和高级权限策略在跨模块场景下保持一致。
|
||||
|
||||
## Quick Context
|
||||
|
||||
```bash
|
||||
# 扫描 token 与身份调用代码
|
||||
!`rg -n "WithUserAccessToken|WithTenantAccessToken|WithTenantKey|tenant_access_token|user_access_token" -S . | head -n 120`
|
||||
|
||||
# 扫描高级权限开关与无权限处理
|
||||
!`rg -n "is_advanced|OperationTypeError|无访问权限|roles|members" -S . | head -n 120`
|
||||
```
|
||||
|
||||
## Plan
|
||||
|
||||
### 产物清单
|
||||
- [ ] 身份模式选择矩阵(user/tenant)
|
||||
- [ ] token 获取、刷新、缓存策略
|
||||
- [ ] 高级权限前置校验
|
||||
- [ ] 权限异常处理流程
|
||||
|
||||
### 决策点
|
||||
1. 场景是否必须用户身份(user token)?
|
||||
2. 是否为商店应用,需要补 `tenant_key`?
|
||||
3. 是否涉及高级权限域(Role/Member)?
|
||||
|
||||
## Verify
|
||||
|
||||
### token 模型
|
||||
- [ ] token 类型选择与接口权限匹配,见 `reference/01-token-model/security-model.md`
|
||||
- [ ] user token 刷新后旧 token 失效逻辑已处理
|
||||
|
||||
### 高级权限
|
||||
- [ ] app 高级权限开关前置检查已落地,见 `reference/02-advanced-permission/security-model.md`
|
||||
- [ ] Role/Member 与文档协作者权限联动策略已覆盖
|
||||
|
||||
### SDK 选项
|
||||
- [ ] SDK 请求级 auth 选项使用正确,见 `reference/03-sdk-auth-options/dependencies.md`
|
||||
|
||||
## Execute
|
||||
|
||||
### 1. 身份选择
|
||||
1. 先按接口需求判断 user/tenant 身份。
|
||||
2. 商店应用场景补传 `tenant_key`。
|
||||
|
||||
### 2. token 生命周期
|
||||
1. tenant token 优先使用 SDK 托管缓存。
|
||||
2. user token 自建刷新与失效处理。
|
||||
|
||||
### 3. 权限联动
|
||||
1. 高级权限接口前确认 `is_advanced=true`。
|
||||
2. 必要时同步云文档权限协作者。
|
||||
|
||||
### 4. 错误收敛
|
||||
1. 将鉴权失败细分为:身份不匹配、token 失效、权限未开通。
|
||||
2. 每类返回明确修复动作。
|
||||
|
||||
## Pitfalls
|
||||
|
||||
1. **把 user token 当 tenant token 用**:调用语义错误,见 `reference/01-token-model/security-model.md`。
|
||||
2. **商店应用漏传 tenant_key**:跨租户调用失败,见 `reference/03-sdk-auth-options/dependencies.md`。
|
||||
3. **高级权限未开启就调角色接口**:直接失败,见 `reference/02-advanced-permission/security-model.md`。
|
||||
4. **只配高级权限不配文档权限**:用户仍无访问权,见 `reference/02-advanced-permission/security-model.md`。
|
||||
|
||||
## Related References
|
||||
|
||||
- `reference/01-token-model/security-model.md`
|
||||
- `reference/02-advanced-permission/security-model.md`
|
||||
- `reference/03-sdk-auth-options/dependencies.md`
|
||||
- `reference/04-session-lifecycle/state-machine.md`
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user