超大量更新
This commit is contained in:
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()
|
||||
Reference in New Issue
Block a user