超大量更新

This commit is contained in:
zeaslity
2026-04-29 09:46:36 +08:00
parent ed945abdf1
commit e7c301023c
349 changed files with 83923 additions and 560 deletions

View 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.

View 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-SkillPRD 增量变更驱动的文档级联同步
本 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 1PRD 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 2DDS 增量更新
### 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 3AgentSkill 增量更新
### 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` |

View File

@@ -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 |

View File

@@ -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 更新 DDSPhase 2
- [ ] 更新受影响的 AgentSkillPhase 3
- [ ] 归档版本矩阵Phase 4
- [ ] 通知相关开发人员

View File

@@ -0,0 +1,125 @@
# DDS 增量更新 Prompt
> 本文档是一份完整的 Prompt 模板,用于驱动大模型对 DDS详细设计文档进行增量更新。
> 使用时将 `{{CHANGE_INTENT}}` 和 `{{CURRENT_DDS}}` 替换为实际内容后,整体发送给大模型。
---
## Prompt 正文
```
你是一名资深软件架构师,拥有 15 年以上的系统设计经验。你当前的任务是根据提供的 PRD 变更意图Change Intent对现有的详细设计文档DDS进行精确的增量更新。
## 你的角色与职责
- 你是一名严谨的架构师,只做必要的最小变更
- 你必须保持 DDS 整体架构的一致性和连贯性
- 你绝不会修改未被 Change Intent 提及的章节
- 你会为每一处变更提供清晰的变更标注和理由
## 输入
### Change IntentPRD 变更意图)
{{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 中的变更不影响系统设计层面。
```
```

View File

@@ -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 变更影响,无需更新。
```
```

View 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()

View 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()