@heihei0299/matt-skills 1.5.4 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -17,6 +17,7 @@ description: "Run the pre-commit gate before any commit: verify docs match the i
17
17
  - 发现不一致 → 先修文档(或更新实现),再进入下一步
18
18
  - 改动涉及项目结构、分发文件、技能/命令清单时,检查 README 中对应的结构说明、映射表、清单是否同步
19
19
  - 改动涉及用法/CLI/配置/示例时,检查 README 对应描述与实际一致
20
+ - **重点聚焦(README + matt-skills 流程)**:本次门禁优先对齐 `README.md` 与 matt-skills 流程相关文件——`AGENTS.md`(路由)、`CONTEXT.md`(术语)、`docs/agents/*`(`skill-design.md`/`runtime-discipline.md`/`issue-tracker.md`/`triage-labels.md`/`domain.md`)、`template/` 镜像(含 `.agents/skills` 全量、`AGENTS.md`、`CONTEXT.md`、`docs/agents`)、`.agents/skills/*`(技能正文与 `references/`)、`config/*` 与 `scripts/build-template.js`;改动触及上述任一文件时,逐项核对 README 的结构说明/清单/映射表与模板镜像是否同步,未同步先修复再 commit
20
21
  - 存在模板镜像/分发副本时,确认源文件与副本同步(如有守护测试,跑一遍确认)
21
22
  - **特例**:`AGENTS.md` 的 `tdd-implement ↔ implement` 路由行 + 技能文件 + `.gitignore` 的 `.pi/` 忽略,且存在 `AGENTS.md.bak` 时,视为模板同步预期增量,不回滚
22
23
  ### ② 保持目录卫生
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: tdd-implement
3
- description: "TDD seam red-green loop: use when the user provides a spec/ticket to implement test-first, or mentions TDD/red-green/test-first and wants the full loop through typecheck, review, commit and closeout. For implementation without TDD use implement; for TDD technique alone use tdd."
3
+ description: "TDD seam red-green loop: use when the user provides a spec/ticket for test-first implementation or asks for red-green loop through typecheck/review/commit. For non-TDD implementation use implement; for technique alone use tdd."
4
4
  ---
5
5
 
6
6
  # TDD Implement
@@ -12,20 +12,12 @@ description: "TDD seam red-green loop: use when the user provides a spec/ticket
12
12
  ## 分支
13
13
 
14
14
  - **单线**:单 spec / 单 issue,走下节 Steps ①→⑦(详规见 [stages.md](references/stages.md))。
15
- - **多 issue 编排**:`.scratch/<feature>/issues/` 下多文件且含 `Blocked by` 时走编排模式——编排器按依赖分层并行调度,子代理各自治完成 ①→⑦。主过程见下节 [多 issue 编排](#多-issue-编排按依赖分层并行),详规见 [orchestration.md](references/orchestration.md)。
15
+ - **多 issue 编排**:`.scratch/<feature>/issues/` 下多文件且含 `Blocked by` 时走编排模式——见下节与 [orchestration.md](references/orchestration.md)。
16
16
 
17
17
  ## 多 issue 编排(按依赖分层并行)
18
- 精简主过程(可执行约束全保留,详规见 [orchestration.md](references/orchestration.md);`references/orchestration.md` 全量保留为详规真相源)。
19
- - **触发**:`.scratch/<feature>/issues/` 下多文件且至少部分含 `Blocked by` 依赖声明;单 issue /spec 不走本节,走 [stages.md](references/stages.md) 单线 ①→⑦。
20
- - **A0 依赖图**:扫描 `NN-<slug>.md` 解析 `Blocked by`(`None`/`(无`/无此行→无依赖;`01, 02`/`01(…)`→依赖对应编号;无法解析→视无依赖并告警),以编号为节点构建 DAG,环则报错列环上节点不调度。
21
- - **A1 Kahn 分层**:对 DAG 做 Kahn BFS 拓扑分层 `L1=入度0 → L2=移除L1后入度0 → … → Ln`;层内无依赖可并行,层间串行;分层结果展示给用户确认后派发。
22
- - **A2 分层调度**:`for Li in L1..Ln: 并行派发(逐个 subagent single 模式,禁 parallel tasks 数组)→ 等待全层回执卡片 → 按 A3 逐 issue 验收(只认回执+抽检,不消费全量日志)→ 层收敛 4 项全过才进下一层`;层收敛 4 项:①该层全部 issue 验收通过 ②相关测试通过 ③`git status` 卫生(仅删 `[DEBUG-...]` 临时产物,禁 `reset --hard/checkout ./clean -fd/stash --include-untracked`)④`git merge-base --is-ancestor $BASE_HEAD HEAD` 通过;任一失败按 A5 回退重派。层间回合连续性:一层收敛后立即派下一层。
23
- - **A3 子代理契约**:每子代理为完整单 issue ①→⑦执行单元(输入:`spec.md`+ 单 `NN-<slug>.md`+`CONTEXT/adr`;执行:①读 issue →②该 issue seams →③红-绿+typecheck →④相关测试(非全量)→⑤双轴 review →⑥commit-check+commit →⑦文档对齐+`Status: resolved`+`## 实施总结`+卫生;产出:独立 commit `feat(<feature>): <title> (#NN)`+ 落盘+测试全绿;禁止跨 issue/跳过⑤⑥)。
24
- - **输出约束**:仅回执卡片(≤30 行,不透传全量日志):`[回执] #NN <标题>` + 提交/ seams/ 测试(相关)/ typecheck/ review(Standards/Spec)/ 验收 checkbox/ 文档/ 遗留;缺字段视不通过。
25
- - **主代理验收(5 项,任一不过打回重派)**:①落盘(`git log` 含 `#NN` + issue 文件 `resolved`+总结)②抽检(相关测试/`tsc --noEmit`)③改动边界(`git diff <base>..HEAD --name-only` 无跨改)④卫生(无 `[DEBUG-...]` 残留)⑤提交关联(message 含 `#NN` 且一致);子代理同样 `BASE_HEAD`+回合计 Chunk/历史保护与单线一致。
26
- - **A4 全量收敛**:全部层验收后编排器执行唯一全量测试 + `merge-base --is-ancestor $BASE_HEAD HEAD` 历史校验(失败经 `reflog` 恢复)+ `git status` 卫生(含禁令)+ 会话汇总回执关键信息(不另写汇总文件)。
27
- - **A5 回退与冲突**:子代理内按 `stages.md` 回退表闭环;层收敛失败→该 issue 保持原 Status、不进下一层、修复后重派;全量失败→定位归属 issue 重派;文件冲突→后完成者 rebase 解决后重跑 typecheck+相关测试,禁丢弃提交、必校验 `merge-base` 与 `git log` 全含;环依赖→A0 即终止。
28
- - **出口/边界**:全部 issue `Status: resolved`+`## 实施总结` 落盘 + 全量测试通过 + 工作区干净;单 issue 不走本节、子代理不跨改、编排器不替写代码、汇总只对话输出;**必须先编排子代理计划(输出依赖图/DAG/Kahn 分层 `L1..Ln` 并确认)后才派子代理,禁止跳过计划直接派发导致重复调度**;TDD 语义以 `tdd` 技能为唯一事实源。
18
+
19
+ 触发见 [orchestration.md](references/orchestration.md);`.scratch/<feature>/issues/` 下多文件且部分含 `Blocked by` 时触发,主过程 A0 依赖图 → A1 Kahn 分层 L1入度0→L2→Ln → A2 分层调度(层内 `N>1` 时每 issue 独立 `git worktree` + `wt/<feature>-#NN` 分支隔离、按序 `merge --no-ff` 归集,`N==1` 退化共享) → A3 子代理契约 → A4 全量收敛 → A5 回退与冲突(最小重派:按失败点精确回退、失败分支增量 fix、精确定位单 issue seam,全量保留为详规真相源)。必须先编排子代理计划(输出依赖图/DAG Kahn 分层 `L1..Ln` 并确认)后才派发,禁止跳过计划直接派发导致重复调度;层收敛 4 项(验收/相关测试/`git status`仅删`[DEBUG-...]`/ `BASE_HEAD`历史校验 `git merge-base --is-ancestor`)与子代理回执卡片(≤30行、缺字段视为不通过)、打回重派(重建全新 worktree 最小 fix)、merge 冲突显式化等可执行约束全量见 orchestration.md
20
+
29
21
  ## Steps
30
22
 
31
23
  按序执行,每步达到完成条件才进入下一步;进入任一步前先读取其在 [stages.md](references/stages.md) 的定义。
@@ -34,20 +26,19 @@ description: "TDD seam red-green loop: use when the user provides a spec/ticket
34
26
  |------|--------|-------------------|------|
35
27
  | ① 理解需求 | 读取 spec/ticket + `CONTEXT.md`/`docs/adr/`,澄清歧义 | 能复述需求且无未澄清歧义 | [stages.md#阶段-①](references/stages.md#阶段-①理解需求) |
36
28
  | ② 确认 Seams | 列出待测公共接口 seams(名称+输入+预期输出),向用户确认并生成 Todo | 用户明确同意 seams 清单;Todo 已生成 | [stages.md#阶段-②](references/stages.md#阶段-②确认-seams测试接缝) |
37
- | ③ TDD 开发循环 | 逐 seam 红-绿循环,串行推进至全绿 | 所有 seams 红-绿完成 + typecheck 通过 | [stages.md#阶段-③](references/stages.md#阶段-③tdd-开发循环) |
29
+ | ③ TDD 开发循环 | 逐 seam 红-绿循环(红→绿→typecheck)串行推进至全绿 | 所有 seams 红-绿完成 + typecheck 通过 | [stages.md#阶段-③](references/stages.md#阶段-③tdd-开发循环) |
38
30
  | ④ 完整测试套件 | 跑全量测试 | 全部测试通过(失败回 ③) | [stages.md#阶段-④](references/stages.md#阶段-④完整测试套件) |
39
31
  | ⑤ Code Review | 按 [code-review](.agents/skills/code-review/SKILL.md) 双轴审查(Standards + Spec) | 双轴均通过 | [stages.md#阶段-⑤](references/stages.md#阶段-⑤code-review) |
40
32
  | ⑥ Commit | 跑 [commit-check](.agents/skills/commit-check/SKILL.md) 门禁四项后提交 | commit 完成且历史校验通过 | [stages.md#阶段-⑥](references/stages.md#阶段-⑥commit) |
41
33
  | ⑦ 收尾 | 文档对齐 → issue 状态与实施总结 → 目录卫生 | 文档已对齐、issue 已 `resolved`+总结落盘、工作区干净 | [stages.md#阶段-⑦](references/stages.md#阶段-⑦收尾文档对齐--issue-状态--实施总结) |
42
34
 
43
- 编排主过程见上节 [多 issue 编排](#多-issue-编排按依赖分层并行),详规见 [orchestration.md](references/orchestration.md);子代理内部仍走上表 ①→⑦(其中 ④ 为相关测试口径,全量由编排器收敛)。
35
+ 子代理内部仍走上表 ①→⑦(其中 ④ 为相关测试口径,全量由编排器收敛)。
44
36
 
45
37
  ### 阶段间流转
46
38
 
47
39
  - 正常流转:出口条件满足即进入下一阶段,不在阶段间停顿。
48
- - 回退路由:见 [stages.md#回退路由](references/stages.md#回退路由);编排模式回退见上节 [多 issue 编排 A5](#多-issue-编排按依赖分层并行) 与 [orchestration.md#A5](references/orchestration.md#a5-回退与冲突)。
49
- - 回合连续性:阶段内连续动作(红→绿→typecheck→下一 seam)在**一个回合内串行完成**,直至阶段出口;预告下一步后立即执行。详规见 [stages.md ③-3e](references/stages.md#3e-回合连续性) 与上节 [多 issue 编排 A2](#多-issue-编排按依赖分层并行) / [orchestration.md#A2](references/orchestration.md#a2-分层调度)。
50
- - 任务分解:巨型写入拆小步——`write` 超 ~150 行先写骨架再分批补全,`replace` 超 ~5 处分批执行并验证。详见 [stages.md ③-3f](references/stages.md#3f-任务分解chunking)。
40
+ - 回退路由:见 [stages.md#回退路由](references/stages.md#回退路由);编排模式回退见 [orchestration.md#A5](references/orchestration.md#a5-回退与冲突)。
41
+ - 回合连续性与任务分解:见 [stages.md ③-3e/3f](references/stages.md#阶段-③tdd-开发循环)(红→绿→typecheck→下一 seam 一个回合内串行完成,直至阶段出口;预告下一步后立即执行;write>150 行/replace>5 处拆小步)。
51
42
 
52
43
  ## 引用
53
44
 
@@ -56,5 +47,4 @@ description: "TDD seam red-green loop: use when the user provides a spec/ticket
56
47
  - Mock 指南:[tdd/mocking.md](.agents/skills/tdd/mocking.md)
57
48
  - Commit 门禁:[commit-check](.agents/skills/commit-check/SKILL.md)
58
49
  - 单线详规:[stages.md](references/stages.md)
59
- - 多 issue 编排主过程:本文件 [多 issue 编排](#多-issue-编排按依赖分层并行)
60
50
  - 多 issue 编排详规:[orchestration.md](references/orchestration.md)(全量保留)
@@ -43,17 +43,21 @@ Ln = 最后一层
43
43
 
44
44
  ```
45
45
  for each 层 Li in L1..Ln:
46
- 并行派发:为 Li 中每个 issue 启动一个子代理(single 模式,禁止 parallel tasks 数组)
46
+ 准备:若 Li N>1 则为每个 issue 创建独立 worktree——幂等清理同名 `wt/<feature>-#NN` `.worktrees/<feature>-#NN` 后 `git worktree add .worktrees/<feature>-#NN -b wt/<feature>-#NN $BASE_HEAD`;N==1 时复用主 worktree,不创建。
47
+ 并行派发:为 Li 中每个 issue 启动一个子代理(single 模式,cwd 为对应 worktree,禁止 parallel tasks 数组)
47
48
  等待:阻塞直到 Li 全部子代理返回回执卡片
48
- 验收:编排器按 A3 验收清单逐 issue 验收(只认回执卡片的关键信息 + 抽检验证,不消费全量日志)
49
- 层收敛验证:验收全通过进入全量验证(完成条件 4 项,全部通过才进下一层,任一失败按 A5 回退):①该层全部 issue 验收通过 ②相关测试套件通过(全量仅在 A4) ③`git status` 卫生(仅删本次临时产物,正向;护栏:禁止 `git reset --hard`/`git checkout .`/`git clean -fd`/`git stash push --include-untracked`)④历史校验 `git merge-base --is-ancestor $BASE_HEAD HEAD` 通过;验收不通过或相关/卫生/历史任一失败按 A5 回退重派该 issue
49
+ 验收:编排器按 A3 验收清单逐 issue 验收(只认回执卡片的关键信息 + 抽检验证在对应 worktree/归集后主分支上执行,不消费全量日志)
50
+ 归集:若 N>1 按编号升序在主分支上 `git merge --no-ff wt/<feature>-#NN` 顺序归集各分支;N==1 时归集即该分支已在主分支。归集冲突按 A5 仅打回冲突单 issue
51
+ 层收敛验证:验收全通过进入全量验证(完成条件 4 项,全部通过且归集成功才进下一层,任一失败按 A5 最小重派该 issue):①该层全部 issue 验收通过 ②相关测试套件通过(全量仅在 A4) ③`git status` 卫生(归集后主分支仅删本次临时产物,正向;护栏:禁止 `git reset --hard`/`git checkout .`/`git clean -fd`/`git stash push --include-untracked`)④历史校验 `git merge-base --is-ancestor $BASE_HEAD HEAD` 通过;验收不通过或相关/卫生/历史任一失败按 A5 重建全新 worktree 最小重派该 issue
52
+ 清理:该层归集后幂等清理该层 worktree(`git worktree remove -f .worktrees/<feature>-#NN && git branch -D wt/<feature>-#NN && git worktree prune`),失败 issue 的 worktree 保留至重派时以失败分支为增量基础重建
50
53
  全部层层收敛通过后进入 A4 全量收敛
51
54
  ```
52
55
 
53
56
  - **派发纪律**:与阶段⑤双轴审查一致——逐个 `subagent` 派发,禁止 `parallel tasks` 数组(同因:中文报告截断)。
54
- - **等待语义**:层内任一子代理失败不取消同层其他子代理;待层内全部返回后统一按 A5 处理。
57
+ - **等待语义**:层内任一子代理失败不取消同层其他子代理;待层内全部返回后统一按 A5 最小重派处理;已通过者的归集先行,不阻塞归集。
58
+ - **Worktree 隔离**:层内 `N>1` 时文件系统级隔离,后完成者不再覆盖先完成者工作区;`N==1` 退化为共享 worktree 以省成本。worktree 路径 `.worktrees/<feature>-#NN` 不纳入 `git status`,分支 `wt/<feature>-#NN` 与 worktree 一一对应。
55
59
  - **回合连续性**:编排器在层间不结束回合——一层收敛后立即派发下一层,直到全部层完成或外部阻塞;预告下一层后立即执行。
56
- - **Git 历史保护(正向:仅追加;护栏:禁改写)**:编排器在分层调度前记录 `BASE_HEAD=$(git rev-parse HEAD)`,每层收敛后校验 `git merge-base --is-ancestor $BASE_HEAD HEAD`,失败即经 `git reflog` 恢复;为达 `git status` 干净仅删本次产生的 `[DEBUG-...]`临时产物(正向),护栏:禁止 `git reset --hard`/`git checkout .`/`git clean -fd`/`git stash push --include-untracked`/`git push --force` 等(需显式确认)。
60
+ - **Git 历史保护(正向:仅追加;护栏:禁改写)**:编排器在分层调度前记录 `BASE_HEAD=$(git rev-parse HEAD)`,每层归集后校验 `git merge-base --is-ancestor $BASE_HEAD HEAD`,失败即经 `git reflog` 恢复;为达 `git status` 干净仅删本次产生的 `[DEBUG-...]`临时产物(正向),护栏:禁止 `git reset --hard`/`git checkout .`/`git clean -fd`/`git stash push --include-untracked`/`git push --force` 等(需显式确认)。归集后主分支 `git log --oneline` 含全部层已通过 issue 的 `#NN`。
57
61
 
58
62
  ### A3. 子代理契约(单 issue 单代理)
59
63
 
@@ -65,7 +69,8 @@ for each 层 Li in L1..Ln:
65
69
  - `spec.md`(feature 级共享 spec,若无则以该 issue 正文为准)
66
70
  - 分配的单个 `NN-<slug>.md`(唯一 issue 输入)
67
71
  - `CONTEXT.md` + `docs/adr/`(术语与决策一致性)
68
- - **执行**:严格走 tdd-implement ①→⑦全流程——①理解需求(读 spec + issue)→ ②确认 seams(该 issue 范围内)→ ③红-绿循环(每 cycle 后 typecheck + 相关测试)→ ④相关测试套件(仅该 issue 相关 + typecheck,不跑全量;全量由编排器在 A2 层收敛/A4 统一执行,单 issue 单线模式仍跑全量)→ ⑤双轴 review → ⑥commit-check 门禁 + commit → ⑦文档对齐(仅该 issue 相关描述)+ `Status: resolved` + `## 实施总结` 落盘 + 目录卫生。TDD 语义以 [tdd 技能](.agents/skills/tdd/SKILL.md) 为唯一事实源,不在子代理内重写。
72
+ - (worktree 隔离时)分配的独立 worktree 路径 `.worktrees/<feature>-#NN` 与分支 `wt/<feature>-#NN`,`cwd` 为该 worktree
73
+ - **执行**:严格走 tdd-implement ①→⑦全流程——①理解需求(读 spec + issue)→ ②确认 seams(该 issue 范围内)→ ③红-绿循环(每 cycle 后 typecheck + 相关测试)→ ④相关测试套件(仅该 issue 相关 + typecheck,不跑全量;全量由编排器在 A2 层收敛/A4 统一执行,单 issue 单线模式仍跑全量)→ ⑤双轴 review → ⑥commit-check 门禁 + commit → ⑦文档对齐(仅该 issue 相关描述)+ `Status: resolved` + `## 实施总结` 落盘 + 目录卫生。TDD 语义以 [tdd 技能](.agents/skills/tdd/SKILL.md) 为唯一事实源,不在子代理内重写。worktree 下在独立分支上执行,入口记录 `WT_BASE_HEAD=$(git rev-parse HEAD)` 并自检 `merge-base --is-ancestor`,`N==1` 时复用主 worktree。
69
74
  - **产出**:
70
75
  - 独立 commit(message 含 issue 编号,如 `feat(<feature>): <issue title> (#NN)`)
71
76
  - 该 issue 文件 `Status: resolved` + 底部 `## 实施总结`
@@ -102,7 +107,7 @@ for each 层 Li in L1..Ln:
102
107
 
103
108
  任一项不通过 → 打回重派该子代理(仅该 issue),层内其他已通过不受影响;验收通过才计入层收敛。验收结论随层收敛一并输出。
104
109
 
105
- 子代理内部的回合连续性、任务分解、Todo 规定、Git 历史保护与单线模式完全一致(见 [stages.md 阶段③ 3e/3f/3h](stages.md#阶段-③tdd-开发循环) 与 Git 安全前置)。子代理同样在入口记录 `BASE_HEAD` 并在每阶段出口校验 `git merge-base --is-ancestor $BASE_HEAD HEAD`,禁止 `git reset --hard`/`git checkout .`/`git clean -fd`/`git stash push --include-untracked` 等。
110
+ 子代理内部的回合连续性、任务分解、Todo 规定、Git 历史保护与单线模式完全一致(见 [stages.md 阶段③ 3e/3f/3h](stages.md#阶段-③tdd-开发循环) 与 Git 安全前置)。子代理同样在入口记录 `BASE_HEAD`(worktree 下为 `WT_BASE_HEAD`)并在每阶段出口校验 `git merge-base --is-ancestor $BASE_HEAD HEAD`,禁止 `git reset --hard`/`git checkout .`/`git clean -fd`/`git stash push --include-untracked` 等;worktree 入口幂等清理同名残留。
106
111
 
107
112
  ### A4. 全量收敛
108
113
 
@@ -115,10 +120,10 @@ for each 层 Li in L1..Ln:
115
120
 
116
121
  ### A5. 回退与冲突
117
122
 
118
- - **子代理内回退**:按 [stages.md 回退路由](stages.md#回退路由) 在子代理内闭环(typecheck 失败 ③、测试失败 ③、review 不通过 ③/②/①)。
119
- - **层收敛失败**:层内任一子代理未达到 `resolved`(测试失败 / review 不通过 / commit-check 门禁失败)→ issue 保持原 `Status`,编排器在层等待结束后报告失败清单,不自动进入下一层;待修复后重派该层失败节点。
120
- - **全量收敛失败**:A4 全量测试失败 → 定位到失败测试归属的 issue,回到其所在层重派对应子代理。
121
- - **文件冲突**:同层子代理若触及同一文件,后完成者 rebase 解决冲突后重跑 typecheck + 相关测试;跨层天然串行无冲突。冲突解决禁止使用 `git reset --hard`/`git checkout .`/`git clean -fd`/`git stash push --include-untracked` 丢弃对方提交,rebase 后必校验 `git merge-base --is-ancestor $BASE_HEAD HEAD` 且 `git log --oneline` 含全部层提交;冲突检测以 `git` 合并结果为准,编排器不做静态预判。
123
+ - **子代理内回退(最小单元)**:按 [stages.md 回退路由](stages.md#回退路由) 精确回退——`typecheck 失败→③`、`测试失败→③`、`review Standards 味→⑤重构`、`review Spec 偏离→①`、`review seams 遗漏→②补 seams`、`commit-check 文档/卫生/message 失败→⑥/⑦ 对应阶段`。失败点之前的已 `done` seam/Todo 永不回退,仅重跑失败阶段及下游;`seams 清单` 与已绿 seam 默认复用,仅 `seams 遗漏/需求偏差` 两类才回到 `②/①` 重确认。
124
+ - **层收敛失败(最小重派)**:层内任一子代理未达到 `resolved`(含验收 5 项、相关测试、卫生、历史校验任一不过)→ issue 保持原 `Status`,编排器在层等待结束后报告失败清单,已通过者先归集到主分支(按编号升序顺序 merge),不自动进入下一层;待修复后仅重派失败节点(重建全新 worktree,不复用旧目录;worktree 下以失败分支 `wt/<feature>-#NN` 为增量基础叠加最小 fix,仅当 `merge 冲突` 导致分叉时以主分支最新重建并 cherry-pick 已通过部分),同层其他已通过不受影响。层原子语义保持:`Li` 未全 `resolved` 不派 `L_{i+1}`,但已通过者的归集先行以减二次冲突。
125
+ - **全量收敛失败(精确定位)**:A4 全量测试失败 → 以测试文件路径/报错栈精确定位到单 issue 单 seam,回到其所在层仅重派该 issue 的失败 seam + 相关测试,全量由编排器在重派后再次 A4 统一验证;无法精确定位时退化到层级重派,不重跑无关联 issue。
126
+ - **文件冲突(显式化)**:worktree 隔离下冲突在归集 `merge` 时显式暴露(不再以共享 worktree 的“后完成者 rebase 覆盖”形式静默重跑),仅打回冲突归属的单 issue;跨层天然串行无冲突。冲突解决禁止使用 `git reset --hard`/`git checkout .`/`git clean -fd`/`git stash push --include-untracked` 丢弃对方提交,解决后必校验 `git merge-base --is-ancestor $BASE_HEAD HEAD` 且 `git log --oneline` 含全部层提交;冲突检测以 `git` 合并结果为准,编排器不做静态预判。
122
127
  - **环依赖**:A0 检测到环即报错终止,不派发任何子代理。
123
128
 
124
129
  ### 出口条件
@@ -227,7 +227,7 @@
227
227
  ```
228
228
 
229
229
  5. 无关联 issue(直接实现用户给的 spec)→ 跳过状态更新,将总结作为会话最终输出
230
- 6. **保持目录卫生**:仅清理本次实现产生的临时产物——`[DEBUG-...]` 标记的调试代码/日志、一次性脚本、临时文件与备份文件;用 `git status` 确认工作区只含预期改动,无残留未跟踪文件后才结束。禁止为达干净而执行 `git reset --hard`、`git checkout .`、`git clean -fd`、`git stash push --include-untracked` 等(需显式用户确认;`stash` 如需使用改用 `--keep-index` 并在 `pop` 后校验 `git merge-base --is-ancestor $BASE_HEAD HEAD`)。
230
+ 6. **保持目录卫生**:仅清理本次实现产生的临时产物——`[DEBUG-...]` 标记的调试代码/日志、一次性脚本、临时文件与备份文件;用 `git status` 确认工作区只含预期改动,无残留未跟踪文件后才结束。Git 历史保护与禁令见本文件阶段③ [Git 安全前置](#git-安全前置历史保护)与 `docs/agents/skill-design.md` Rule 4,仅删本次临时产物,禁止为达干净而执行 git 层破坏性命令。
231
231
 
232
232
  ### 出口条件
233
233
 
package/bin/cli.js CHANGED
@@ -32,37 +32,83 @@ process.stdout.on('error', (err) => {
32
32
  throw err;
33
33
  });
34
34
 
35
- const HELP = `matt-skills — install and manage this skill collection
35
+ const HELP_GLOBAL = `matt-skills — install and manage this skill collection
36
36
 
37
37
  Usage:
38
38
  matt-skills init [options] Initialize a project: template + skills (.agents/skills)
39
- matt-skills sync [--apply|--force] [--all] [--dest <path>] [--upstream <url>] [--ref <ref>] [--json] Sync existing project to latest template + skills
39
+ matt-skills sync [--all|--force|--dry-run] [--dest <path>] Sync existing project to latest template + skills
40
40
  matt-skills list [--all] [--json] List available skills and their descriptions
41
41
  matt-skills install [options] Install skills (interactive by default)
42
42
  matt-skills check [--all] [--json] [--upstream <url>] [--ref <ref>]
43
43
  Check if upstream skills are up to date (read-only)
44
- matt-skills --help Show this help
44
+ matt-skills --help | -h Show this help
45
+ matt-skills --version | -v Show version
46
+ `;
47
+
48
+ const HELP_INIT = `matt-skills init [options] — Initialize a project: template + skills (.agents/skills)
49
+
50
+ Usage:
51
+ matt-skills init [options]
45
52
 
46
53
  Init options:
47
54
  --dest <path> Target directory (default: current directory)
48
55
  --force Overwrite existing files
49
56
  --all Include non-programming skills (productivity) and optional proprietary; default only core programming (engineering 18 + default proprietary 4 → 22)
57
+ --help, -h Show this help
58
+
59
+ 提示:matt-skills --help 查看全量
60
+ `;
61
+
62
+ const HELP_SYNC = `matt-skills sync — Sync existing project to latest template + skills
63
+
64
+ Usage:
65
+ matt-skills sync [--all|--force|--dry-run] [--dest <path>]
66
+
50
67
  Sync options:
51
- --apply Apply changes (safe incremental, respects custom AGENTS.md)
52
- --force Hard overwrite (backup .bak + full sync)
53
- --all Include non-programming and optional proprietary; default only core programming (22)
68
+ --all 范围:含非编程与可选独有(默认仅编程 22)
69
+ --force 力度:硬盖(备份 .bak + 删多余,全量 add/update/remove)
70
+ --dry-run 预演:只比对不写盘
54
71
  --dest <path> Target directory (default: current directory)
55
- --upstream <url> Upstream repo URL (default: https://github.com/mattpocock/skills.git)
56
- --ref <ref> Upstream ref (default: HEAD)
72
+ --help, -h Show this help
73
+
74
+ 说明:默认不带参即安全增量同步默认技能(22);--all 与 --force 互斥。
75
+
76
+ 提示:matt-skills --help 查看全量
77
+ `;
78
+
79
+ const HELP_LIST = `matt-skills list — List available skills
80
+
81
+ Usage:
82
+ matt-skills list [--all] [--json]
83
+
84
+ List options:
85
+ --all List all skills (default only core programming 22)
57
86
  --json Output as JSON
87
+ --help, -h Show this help
88
+
89
+ 提示:matt-skills --help 查看全量
90
+ `;
91
+
92
+ const HELP_CHECK = `matt-skills check — Check if upstream skills are up to date (read-only)
93
+
94
+ Usage:
95
+ matt-skills check [--all] [--json] [--upstream <url>] [--ref <ref>]
96
+
58
97
  Check options:
59
98
  --all Include non-programming and optional proprietary; default only core programming (22)
60
99
  --json Output as JSON
61
100
  --upstream <url> Upstream repo URL (default: https://github.com/mattpocock/skills.git)
62
101
  --ref <ref> Upstream ref (default: HEAD)
63
- List options:
64
- --all List all skills (default only core programming 22)
65
- --json Output as JSON
102
+ --help, -h Show this help
103
+
104
+ 提示:matt-skills --help 查看全量
105
+ `;
106
+
107
+ const HELP_INSTALL = `matt-skills install — Install skills (interactive by default)
108
+
109
+ Usage:
110
+ matt-skills install [options]
111
+
66
112
  Install options:
67
113
  --tools <a,b> Install for the given tools (codex, pi, opencode, claude); skips tool selection — 共享技能统一指向 .agents/skills,.pi/skills/.opencode/skills 仅用于项目自定义
68
114
  --all Install all skills (default only core programming 22); skips skill selection
@@ -70,8 +116,13 @@ Install options:
70
116
  --global Install to the user's global skill directories
71
117
  --project Install to project skill directories (default)
72
118
  --dest <path> Install everything into a single custom directory (overrides --tools)
119
+ --help, -h Show this help
120
+
121
+ 提示:matt-skills --help 查看全量
73
122
  `;
74
123
 
124
+ const HELP = HELP_GLOBAL;
125
+
75
126
  function parseFrontmatter(text) {
76
127
  const match = text.match(/^---\r?\n([\s\S]*?)\r?\n---/);
77
128
  if (!match) return {};
@@ -280,25 +331,22 @@ async function initCommand({ dest, force, all }) {
280
331
  }
281
332
  process.stdout.write(`目标路径:${target}\n`);
282
333
  }
283
- async function syncCommand({ dest, force, apply, upstreamUrl, ref, json, all }) {
334
+ async function syncCommand({ dest, force, all, dryRun, json, upstreamUrl, ref }) {
284
335
  const onlyProgramming = !all;
285
- // 默认:仅对比不写盘 (check 模式),--apply / --force 显式写盘
286
- const doApply = apply || force;
287
- if (!doApply) {
336
+ if (dryRun) {
288
337
  const { compare } = await import('../scripts/sync-upstream.js');
289
338
  const cmp = await compare({ upstreamUrl, ref, onlyProgramming });
290
339
  if (json) {
291
340
  process.stdout.write(JSON.stringify({ head: cmp.head, counts: cmp.counts, result: cmp.result, onlyProgramming }, null, 2) + '\n');
292
341
  } else {
342
+ const modeHint = onlyProgramming ? '(仅编程)' : '(全量)';
293
343
  const lines = [];
294
344
  lines.push(`上游 HEAD: ${cmp.head}`);
295
- const modeHint = onlyProgramming ? '(仅编程)' : '(全量)';
296
345
  lines.push(`本地非独有: ${cmp.counts.local} 上游: ${cmp.counts.upstream} ${modeHint}`);
297
346
  lines.push('');
298
347
  const totalDiff = cmp.result.added.length + cmp.result.updated.length + cmp.result.removed.length + cmp.result.renamed.length;
299
- if (totalDiff === 0) {
300
- lines.push('✅ 已是最新,无差异');
301
- } else {
348
+ if (totalDiff === 0) lines.push('✅ 已是最新,无差异');
349
+ else {
302
350
  if (cmp.result.added.length) lines.push(`新增 (${cmp.result.added.length}): ${cmp.result.added.join(', ')}`);
303
351
  if (cmp.result.updated.length) lines.push(`更新 (${cmp.result.updated.length}): ${cmp.result.updated.join(', ')}`);
304
352
  if (cmp.result.renamed.length) lines.push(`重命名 (${cmp.result.renamed.length}): ${cmp.result.renamed.map((r) => `${r.from}→${r.to}`).join(', ')}`);
@@ -309,8 +357,8 @@ async function syncCommand({ dest, force, apply, upstreamUrl, ref, json, all })
309
357
  }
310
358
  const { rm } = await import('node:fs/promises');
311
359
  await rm(cmp.dest, { recursive: true, force: true });
312
- const hasDiff = cmp.result.added.length + cmp.result.updated.length + cmp.result.removed.length + cmp.result.renamed.length > 0;
313
- if (hasDiff) process.exitCode = 1;
360
+ const totalDiff = cmp.result.added.length + cmp.result.updated.length + cmp.result.removed.length + cmp.result.renamed.length;
361
+ if (totalDiff > 0) process.exitCode = 1;
314
362
  return;
315
363
  }
316
364
 
@@ -341,22 +389,16 @@ async function syncCommand({ dest, force, apply, upstreamUrl, ref, json, all })
341
389
  }
342
390
  if (!(await pathExists(marker))) {
343
391
  process.stdout.write('未检测到现有项目(AGENTS.md 不存在),将执行全新初始化\n');
344
- if (onlyProgramming) {
345
- await copyTemplateFiltered();
346
- } else {
347
- await cp(TEMPLATE_DIR, target, { recursive: true, force: true });
348
- }
392
+ if (onlyProgramming) await copyTemplateFiltered();
393
+ else await cp(TEMPLATE_DIR, target, { recursive: true, force: true });
349
394
  process.stdout.write('模板:已复制(AGENTS.md、.agents/skills、.opencode/、.pi/)\n');
350
395
  } else if (force) {
351
396
  process.stdout.write('同步:检测到现有项目,将增量更新\n');
352
397
  await backupIfExists(path.join(target, 'AGENTS.md'));
353
- if (onlyProgramming) {
354
- await copyTemplateFiltered();
355
- } else {
356
- await cp(TEMPLATE_DIR, target, { recursive: true, force: true });
357
- }
398
+ if (onlyProgramming) await copyTemplateFiltered();
399
+ else await cp(TEMPLATE_DIR, target, { recursive: true, force: true });
358
400
  process.stdout.write('模板:已覆盖(AGENTS.md、.agents/skills、.opencode/、.pi/)\n');
359
- } else if (apply) {
401
+ } else {
360
402
  process.stdout.write('同步:检测到现有项目,将增量更新\n');
361
403
  let skipAgents = false;
362
404
  try {
@@ -382,21 +424,10 @@ async function syncCommand({ dest, force, apply, upstreamUrl, ref, json, all })
382
424
  }
383
425
  process.stdout.write('模板:已同步(AGENTS.md 跳过,已含定制)\n');
384
426
  } else {
385
- if (onlyProgramming) {
386
- await copyTemplateFiltered();
387
- } else {
388
- await cp(TEMPLATE_DIR, target, { recursive: true, force: true });
389
- }
427
+ if (onlyProgramming) await copyTemplateFiltered();
428
+ else await cp(TEMPLATE_DIR, target, { recursive: true, force: true });
390
429
  process.stdout.write('模板:已同步(AGENTS.md、.agents/skills、.opencode/、.pi/)\n');
391
430
  }
392
- } else {
393
- process.stdout.write('同步:检测到现有项目,将增量更新\n');
394
- if (onlyProgramming) {
395
- await copyTemplateFiltered();
396
- } else {
397
- await cp(TEMPLATE_DIR, target, { recursive: true, force: true });
398
- }
399
- process.stdout.write('模板:已同步(AGENTS.md、.agents/skills、.opencode/、.pi/)\n');
400
431
  }
401
432
  // 技能同步:按编程过滤(默认仅编程,--all 全量)
402
433
  const entries = await readdir(SKILLS_DIR, { withFileTypes: true });
@@ -428,7 +459,7 @@ async function syncCommand({ dest, force, apply, upstreamUrl, ref, json, all })
428
459
  }
429
460
  }
430
461
  // --force 时删除多余;仅编程模式下多余指不在编程集合中的,--all 模式下多余指不在全量中的
431
- // 默认 --apply 保留多余(不删除),符合“默认保留、--force 删除”
462
+ // 默认安全增量保留多余(不删除),符合“默认保留、--force 删除”
432
463
  if (force) {
433
464
  let localEntries = [];
434
465
  try {
@@ -478,25 +509,55 @@ async function syncCommand({ dest, force, apply, upstreamUrl, ref, json, all })
478
509
  function parseInitArgs(args) {
479
510
  let dest;
480
511
  let force = false;
481
- let apply = false;
482
- let json = false;
483
512
  let all = false;
513
+ for (let i = 0; i < args.length; i++) {
514
+ const arg = args[i];
515
+ if (arg === '--dest') {
516
+ if (i + 1 >= args.length || args[i + 1].startsWith('-')) throw new Error(`unknown option '--dest' requires a value`);
517
+ dest = args[++i];
518
+ } else if (arg.startsWith('--dest=')) dest = arg.slice('--dest='.length);
519
+ else if (arg === '--force') force = true;
520
+ else if (arg === '--all') all = true;
521
+ else if (arg === '--help' || arg === '-h') {} // handled at main level, ignore here
522
+ else if (arg.startsWith('-')) throw new Error(`unknown option '${arg}' for command 'init'`);
523
+ else throw new Error(`unknown argument '${arg}' for command 'init'`);
524
+ }
525
+ return { dest, force, all };
526
+ }
527
+
528
+ function parseSyncArgs(args) {
529
+ let dest;
530
+ let force = false;
531
+ let all = false;
532
+ let dryRun = false;
533
+ let json = false;
484
534
  let upstreamUrl;
485
535
  let ref;
486
536
  for (let i = 0; i < args.length; i++) {
487
537
  const arg = args[i];
488
- if (arg === '--dest') dest = args[++i];
489
- else if (arg.startsWith('--dest=')) dest = arg.slice('--dest='.length);
538
+ if (arg === '--dest') {
539
+ if (i + 1 >= args.length || args[i + 1].startsWith('-')) throw new Error(`unknown option '--dest' requires a value`);
540
+ dest = args[++i];
541
+ } else if (arg.startsWith('--dest=')) dest = arg.slice('--dest='.length);
490
542
  else if (arg === '--force') force = true;
491
- else if (arg === '--apply') apply = true;
492
543
  else if (arg === '--all') all = true;
544
+ else if (arg === '--dry-run') dryRun = true;
493
545
  else if (arg === '--json') json = true;
494
- else if (arg === '--upstream') upstreamUrl = args[++i];
495
- else if (arg.startsWith('--upstream=')) upstreamUrl = arg.slice('--upstream='.length);
496
- else if (arg === '--ref') ref = args[++i];
497
- else if (arg.startsWith('--ref=')) ref = arg.slice('--ref='.length);
546
+ else if (arg === '--upstream') {
547
+ if (i + 1 >= args.length || args[i + 1].startsWith('-')) throw new Error(`unknown option '--upstream' requires a value`);
548
+ upstreamUrl = args[++i];
549
+ } else if (arg.startsWith('--upstream=')) upstreamUrl = arg.slice('--upstream='.length);
550
+ else if (arg === '--ref') {
551
+ if (i + 1 >= args.length || args[i + 1].startsWith('-')) throw new Error(`unknown option '--ref' requires a value`);
552
+ ref = args[++i];
553
+ } else if (arg.startsWith('--ref=')) ref = arg.slice('--ref='.length);
554
+ else if (arg === '--help' || arg === '-h') {} // handled at main
555
+ else if (arg === '--apply') {} // deprecated alias, same as default safe incremental
556
+ else if (arg.startsWith('-')) throw new Error(`unknown option '${arg}' for command 'sync'`);
557
+ else throw new Error(`unknown argument '${arg}' for command 'sync'`);
498
558
  }
499
- return { dest, force, apply, json, all, upstreamUrl, ref };
559
+ if (all && force) throw new Error(`--all and --force are mutually exclusive, choose one`);
560
+ return { dest, force, all, dryRun, json, upstreamUrl, ref };
500
561
  }
501
562
 
502
563
  function parseInstallArgs(args) {
@@ -507,14 +568,21 @@ function parseInstallArgs(args) {
507
568
  let toolsArg;
508
569
  for (let i = 0; i < args.length; i++) {
509
570
  const arg = args[i];
510
- if (arg === '--dest') dest = args[++i];
511
- else if (arg.startsWith('--dest=')) dest = arg.slice('--dest='.length);
571
+ if (arg === '--dest') {
572
+ if (i + 1 >= args.length || args[i + 1].startsWith('-')) throw new Error(`unknown option '--dest' requires a value`);
573
+ dest = args[++i];
574
+ } else if (arg.startsWith('--dest=')) dest = arg.slice('--dest='.length);
512
575
  else if (arg === '--all') all = true;
513
576
  else if (arg === '--force') force = true;
514
- else if (arg === '--tools') toolsArg = args[++i];
515
- else if (arg.startsWith('--tools=')) toolsArg = arg.slice('--tools='.length);
577
+ else if (arg === '--tools') {
578
+ if (i + 1 >= args.length || args[i + 1].startsWith('-')) throw new Error(`unknown option '--tools' requires a value`);
579
+ toolsArg = args[++i];
580
+ } else if (arg.startsWith('--tools=')) toolsArg = arg.slice('--tools='.length);
516
581
  else if (arg === '--global') global = true;
517
582
  else if (arg === '--project') global = false;
583
+ else if (arg === '--help' || arg === '-h') {} // handled at main
584
+ else if (arg.startsWith('-')) throw new Error(`unknown option '${arg}' for command 'install'`);
585
+ else throw new Error(`unknown argument '${arg}' for command 'install'`);
518
586
  }
519
587
  const tools = toolsArg
520
588
  ? toolsArg.split(',').map((t) => t.trim()).filter(Boolean)
@@ -559,12 +627,49 @@ async function checkCommand(args) {
559
627
 
560
628
  async function main() {
561
629
  const args = process.argv.slice(2);
562
- if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
563
- process.stdout.write(HELP);
630
+ // -v/--version highest priority, anywhere
631
+ if (args.includes('-v') || args.includes('--version')) {
632
+ try {
633
+ const pkgRaw = await readFile(path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'package.json'), 'utf8');
634
+ const pkg = JSON.parse(pkgRaw);
635
+ process.stdout.write(`${pkg.version}\n`);
636
+ } catch {
637
+ process.stdout.write('unknown\n');
638
+ }
639
+ return;
640
+ }
641
+ // -h/--help suffix handling (global vs per-command)
642
+ if (args.includes('-h') || args.includes('--help')) {
643
+ const known = ['init', 'sync', 'list', 'install', 'check'];
644
+ const first = args[0];
645
+ const cmd = known.includes(first) ? first : null;
646
+ if (cmd === 'init') { process.stdout.write(HELP_INIT); return; }
647
+ if (cmd === 'sync') { process.stdout.write(HELP_SYNC); return; }
648
+ if (cmd === 'list') { process.stdout.write(HELP_LIST); return; }
649
+ if (cmd === 'check') { process.stdout.write(HELP_CHECK); return; }
650
+ if (cmd === 'install') { process.stdout.write(HELP_INSTALL); return; }
651
+ process.stdout.write(HELP_GLOBAL);
652
+ return;
653
+ }
654
+ if (args.length === 0) {
655
+ process.stdout.write(HELP_GLOBAL);
564
656
  return;
565
657
  }
566
658
  const [command, ...rest] = args;
659
+ const knownCommands = new Set(['list', 'init', 'sync', 'install', 'check', 'update']);
660
+ if (!knownCommands.has(command)) {
661
+ process.stderr.write(`error: unknown command '${command}'\n`);
662
+ process.stderr.write(`Run 'matt-skills --help' for usage.\n`);
663
+ process.exitCode = 1;
664
+ return;
665
+ }
567
666
  if (command === 'list') {
667
+ // list strict: only --all/--json/--help allowed, rest handled via parse but we keep simple
668
+ for (const a of rest) {
669
+ if (a === '--all' || a === '--json' || a === '--help' || a === '-h') continue;
670
+ if (a.startsWith('-')) { process.stderr.write(`error: unknown option '${a}' for command 'list'\n`); process.stderr.write(`Run 'matt-skills list --help' for usage.\n`); process.exitCode = 1; return; }
671
+ process.stderr.write(`error: unknown argument '${a}' for command 'list'\n`); process.stderr.write(`Run 'matt-skills list --help' for usage.\n`); process.exitCode = 1; return;
672
+ }
568
673
  const onlyProgramming = !rest.includes('--all');
569
674
  const skills = await listSkills({ onlyProgramming });
570
675
  if (rest.includes('--json')) {
@@ -581,7 +686,7 @@ async function main() {
581
686
  return;
582
687
  }
583
688
  if (command === 'sync') {
584
- await syncCommand(parseInitArgs(rest));
689
+ await syncCommand(parseSyncArgs(rest));
585
690
  return;
586
691
  }
587
692
  if (command === 'install') {
@@ -589,16 +694,22 @@ async function main() {
589
694
  return;
590
695
  }
591
696
  if (command === 'check') {
697
+ // check strict: allow --all/--json/--upstream/--ref/--help
698
+ for (const a of rest) {
699
+ if (a === '--all' || a === '--json' || a === '--help' || a === '-h') continue;
700
+ if (a === '--upstream' || a.startsWith('--upstream=')) continue;
701
+ if (a === '--ref' || a.startsWith('--ref=')) continue;
702
+ if (a.startsWith('-')) { process.stderr.write(`error: unknown option '${a}' for command 'check'\n`); process.stderr.write(`Run 'matt-skills check --help' for usage.\n`); process.exitCode = 1; return; }
703
+ process.stderr.write(`error: unknown argument '${a}' for command 'check'\n`); process.stderr.write(`Run 'matt-skills check --help' for usage.\n`); process.exitCode = 1; return;
704
+ }
592
705
  await checkCommand(rest);
593
706
  return;
594
707
  }
595
708
  if (command === 'update') {
596
- process.stderr.write('update 已合并到 sync --apply\n');
709
+ process.stderr.write('update 已合并到 sync(默认即增量同步)\n');
597
710
  process.exitCode = 1;
598
711
  return;
599
712
  }
600
- process.stderr.write(HELP);
601
- process.exitCode = 1;
602
713
  }
603
714
  main().catch((error) => {
604
715
  process.stderr.write(`error: ${error.message}\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heihei0299/matt-skills",
3
- "version": "1.5.4",
3
+ "version": "1.6.1",
4
4
  "description": "Agent skills + 项目配置模板:一条命令初始化 opencode / pi-agent 项目(含 mattpocock/skills 上游技能)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,6 +17,7 @@ description: "Run the pre-commit gate before any commit: verify docs match the i
17
17
  - 发现不一致 → 先修文档(或更新实现),再进入下一步
18
18
  - 改动涉及项目结构、分发文件、技能/命令清单时,检查 README 中对应的结构说明、映射表、清单是否同步
19
19
  - 改动涉及用法/CLI/配置/示例时,检查 README 对应描述与实际一致
20
+ - **重点聚焦(README + matt-skills 流程)**:本次门禁优先对齐 `README.md` 与 matt-skills 流程相关文件——`AGENTS.md`(路由)、`CONTEXT.md`(术语)、`docs/agents/*`(`skill-design.md`/`runtime-discipline.md`/`issue-tracker.md`/`triage-labels.md`/`domain.md`)、`template/` 镜像(含 `.agents/skills` 全量、`AGENTS.md`、`CONTEXT.md`、`docs/agents`)、`.agents/skills/*`(技能正文与 `references/`)、`config/*` 与 `scripts/build-template.js`;改动触及上述任一文件时,逐项核对 README 的结构说明/清单/映射表与模板镜像是否同步,未同步先修复再 commit
20
21
  - 存在模板镜像/分发副本时,确认源文件与副本同步(如有守护测试,跑一遍确认)
21
22
  - **特例**:`AGENTS.md` 的 `tdd-implement ↔ implement` 路由行 + 技能文件 + `.gitignore` 的 `.pi/` 忽略,且存在 `AGENTS.md.bak` 时,视为模板同步预期增量,不回滚
22
23
  ### ② 保持目录卫生
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: tdd-implement
3
- description: "TDD seam red-green loop: use when the user provides a spec/ticket to implement test-first, or mentions TDD/red-green/test-first and wants the full loop through typecheck, review, commit and closeout. For implementation without TDD use implement; for TDD technique alone use tdd."
3
+ description: "TDD seam red-green loop: use when the user provides a spec/ticket for test-first implementation or asks for red-green loop through typecheck/review/commit. For non-TDD implementation use implement; for technique alone use tdd."
4
4
  ---
5
5
 
6
6
  # TDD Implement
@@ -12,20 +12,12 @@ description: "TDD seam red-green loop: use when the user provides a spec/ticket
12
12
  ## 分支
13
13
 
14
14
  - **单线**:单 spec / 单 issue,走下节 Steps ①→⑦(详规见 [stages.md](references/stages.md))。
15
- - **多 issue 编排**:`.scratch/<feature>/issues/` 下多文件且含 `Blocked by` 时走编排模式——编排器按依赖分层并行调度,子代理各自治完成 ①→⑦。主过程见下节 [多 issue 编排](#多-issue-编排按依赖分层并行),详规见 [orchestration.md](references/orchestration.md)。
15
+ - **多 issue 编排**:`.scratch/<feature>/issues/` 下多文件且含 `Blocked by` 时走编排模式——见下节与 [orchestration.md](references/orchestration.md)。
16
16
 
17
17
  ## 多 issue 编排(按依赖分层并行)
18
- 精简主过程(可执行约束全保留,详规见 [orchestration.md](references/orchestration.md);`references/orchestration.md` 全量保留为详规真相源)。
19
- - **触发**:`.scratch/<feature>/issues/` 下多文件且至少部分含 `Blocked by` 依赖声明;单 issue /spec 不走本节,走 [stages.md](references/stages.md) 单线 ①→⑦。
20
- - **A0 依赖图**:扫描 `NN-<slug>.md` 解析 `Blocked by`(`None`/`(无`/无此行→无依赖;`01, 02`/`01(…)`→依赖对应编号;无法解析→视无依赖并告警),以编号为节点构建 DAG,环则报错列环上节点不调度。
21
- - **A1 Kahn 分层**:对 DAG 做 Kahn BFS 拓扑分层 `L1=入度0 → L2=移除L1后入度0 → … → Ln`;层内无依赖可并行,层间串行;分层结果展示给用户确认后派发。
22
- - **A2 分层调度**:`for Li in L1..Ln: 并行派发(逐个 subagent single 模式,禁 parallel tasks 数组)→ 等待全层回执卡片 → 按 A3 逐 issue 验收(只认回执+抽检,不消费全量日志)→ 层收敛 4 项全过才进下一层`;层收敛 4 项:①该层全部 issue 验收通过 ②相关测试通过 ③`git status` 卫生(仅删 `[DEBUG-...]` 临时产物,禁 `reset --hard/checkout ./clean -fd/stash --include-untracked`)④`git merge-base --is-ancestor $BASE_HEAD HEAD` 通过;任一失败按 A5 回退重派。层间回合连续性:一层收敛后立即派下一层。
23
- - **A3 子代理契约**:每子代理为完整单 issue ①→⑦执行单元(输入:`spec.md`+ 单 `NN-<slug>.md`+`CONTEXT/adr`;执行:①读 issue →②该 issue seams →③红-绿+typecheck →④相关测试(非全量)→⑤双轴 review →⑥commit-check+commit →⑦文档对齐+`Status: resolved`+`## 实施总结`+卫生;产出:独立 commit `feat(<feature>): <title> (#NN)`+ 落盘+测试全绿;禁止跨 issue/跳过⑤⑥)。
24
- - **输出约束**:仅回执卡片(≤30 行,不透传全量日志):`[回执] #NN <标题>` + 提交/ seams/ 测试(相关)/ typecheck/ review(Standards/Spec)/ 验收 checkbox/ 文档/ 遗留;缺字段视不通过。
25
- - **主代理验收(5 项,任一不过打回重派)**:①落盘(`git log` 含 `#NN` + issue 文件 `resolved`+总结)②抽检(相关测试/`tsc --noEmit`)③改动边界(`git diff <base>..HEAD --name-only` 无跨改)④卫生(无 `[DEBUG-...]` 残留)⑤提交关联(message 含 `#NN` 且一致);子代理同样 `BASE_HEAD`+回合计 Chunk/历史保护与单线一致。
26
- - **A4 全量收敛**:全部层验收后编排器执行唯一全量测试 + `merge-base --is-ancestor $BASE_HEAD HEAD` 历史校验(失败经 `reflog` 恢复)+ `git status` 卫生(含禁令)+ 会话汇总回执关键信息(不另写汇总文件)。
27
- - **A5 回退与冲突**:子代理内按 `stages.md` 回退表闭环;层收敛失败→该 issue 保持原 Status、不进下一层、修复后重派;全量失败→定位归属 issue 重派;文件冲突→后完成者 rebase 解决后重跑 typecheck+相关测试,禁丢弃提交、必校验 `merge-base` 与 `git log` 全含;环依赖→A0 即终止。
28
- - **出口/边界**:全部 issue `Status: resolved`+`## 实施总结` 落盘 + 全量测试通过 + 工作区干净;单 issue 不走本节、子代理不跨改、编排器不替写代码、汇总只对话输出;**必须先编排子代理计划(输出依赖图/DAG/Kahn 分层 `L1..Ln` 并确认)后才派子代理,禁止跳过计划直接派发导致重复调度**;TDD 语义以 `tdd` 技能为唯一事实源。
18
+
19
+ 触发见 [orchestration.md](references/orchestration.md);`.scratch/<feature>/issues/` 下多文件且部分含 `Blocked by` 时触发,主过程 A0 依赖图 → A1 Kahn 分层 L1入度0→L2→Ln → A2 分层调度(层内 `N>1` 时每 issue 独立 `git worktree` + `wt/<feature>-#NN` 分支隔离、按序 `merge --no-ff` 归集,`N==1` 退化共享) → A3 子代理契约 → A4 全量收敛 → A5 回退与冲突(最小重派:按失败点精确回退、失败分支增量 fix、精确定位单 issue seam,全量保留为详规真相源)。必须先编排子代理计划(输出依赖图/DAG Kahn 分层 `L1..Ln` 并确认)后才派发,禁止跳过计划直接派发导致重复调度;层收敛 4 项(验收/相关测试/`git status`仅删`[DEBUG-...]`/ `BASE_HEAD`历史校验 `git merge-base --is-ancestor`)与子代理回执卡片(≤30行、缺字段视为不通过)、打回重派(重建全新 worktree 最小 fix)、merge 冲突显式化等可执行约束全量见 orchestration.md
20
+
29
21
  ## Steps
30
22
 
31
23
  按序执行,每步达到完成条件才进入下一步;进入任一步前先读取其在 [stages.md](references/stages.md) 的定义。
@@ -34,20 +26,19 @@ description: "TDD seam red-green loop: use when the user provides a spec/ticket
34
26
  |------|--------|-------------------|------|
35
27
  | ① 理解需求 | 读取 spec/ticket + `CONTEXT.md`/`docs/adr/`,澄清歧义 | 能复述需求且无未澄清歧义 | [stages.md#阶段-①](references/stages.md#阶段-①理解需求) |
36
28
  | ② 确认 Seams | 列出待测公共接口 seams(名称+输入+预期输出),向用户确认并生成 Todo | 用户明确同意 seams 清单;Todo 已生成 | [stages.md#阶段-②](references/stages.md#阶段-②确认-seams测试接缝) |
37
- | ③ TDD 开发循环 | 逐 seam 红-绿循环,串行推进至全绿 | 所有 seams 红-绿完成 + typecheck 通过 | [stages.md#阶段-③](references/stages.md#阶段-③tdd-开发循环) |
29
+ | ③ TDD 开发循环 | 逐 seam 红-绿循环(红→绿→typecheck)串行推进至全绿 | 所有 seams 红-绿完成 + typecheck 通过 | [stages.md#阶段-③](references/stages.md#阶段-③tdd-开发循环) |
38
30
  | ④ 完整测试套件 | 跑全量测试 | 全部测试通过(失败回 ③) | [stages.md#阶段-④](references/stages.md#阶段-④完整测试套件) |
39
31
  | ⑤ Code Review | 按 [code-review](.agents/skills/code-review/SKILL.md) 双轴审查(Standards + Spec) | 双轴均通过 | [stages.md#阶段-⑤](references/stages.md#阶段-⑤code-review) |
40
32
  | ⑥ Commit | 跑 [commit-check](.agents/skills/commit-check/SKILL.md) 门禁四项后提交 | commit 完成且历史校验通过 | [stages.md#阶段-⑥](references/stages.md#阶段-⑥commit) |
41
33
  | ⑦ 收尾 | 文档对齐 → issue 状态与实施总结 → 目录卫生 | 文档已对齐、issue 已 `resolved`+总结落盘、工作区干净 | [stages.md#阶段-⑦](references/stages.md#阶段-⑦收尾文档对齐--issue-状态--实施总结) |
42
34
 
43
- 编排主过程见上节 [多 issue 编排](#多-issue-编排按依赖分层并行),详规见 [orchestration.md](references/orchestration.md);子代理内部仍走上表 ①→⑦(其中 ④ 为相关测试口径,全量由编排器收敛)。
35
+ 子代理内部仍走上表 ①→⑦(其中 ④ 为相关测试口径,全量由编排器收敛)。
44
36
 
45
37
  ### 阶段间流转
46
38
 
47
39
  - 正常流转:出口条件满足即进入下一阶段,不在阶段间停顿。
48
- - 回退路由:见 [stages.md#回退路由](references/stages.md#回退路由);编排模式回退见上节 [多 issue 编排 A5](#多-issue-编排按依赖分层并行) 与 [orchestration.md#A5](references/orchestration.md#a5-回退与冲突)。
49
- - 回合连续性:阶段内连续动作(红→绿→typecheck→下一 seam)在**一个回合内串行完成**,直至阶段出口;预告下一步后立即执行。详规见 [stages.md ③-3e](references/stages.md#3e-回合连续性) 与上节 [多 issue 编排 A2](#多-issue-编排按依赖分层并行) / [orchestration.md#A2](references/orchestration.md#a2-分层调度)。
50
- - 任务分解:巨型写入拆小步——`write` 超 ~150 行先写骨架再分批补全,`replace` 超 ~5 处分批执行并验证。详见 [stages.md ③-3f](references/stages.md#3f-任务分解chunking)。
40
+ - 回退路由:见 [stages.md#回退路由](references/stages.md#回退路由);编排模式回退见 [orchestration.md#A5](references/orchestration.md#a5-回退与冲突)。
41
+ - 回合连续性与任务分解:见 [stages.md ③-3e/3f](references/stages.md#阶段-③tdd-开发循环)(红→绿→typecheck→下一 seam 一个回合内串行完成,直至阶段出口;预告下一步后立即执行;write>150 行/replace>5 处拆小步)。
51
42
 
52
43
  ## 引用
53
44
 
@@ -56,5 +47,4 @@ description: "TDD seam red-green loop: use when the user provides a spec/ticket
56
47
  - Mock 指南:[tdd/mocking.md](.agents/skills/tdd/mocking.md)
57
48
  - Commit 门禁:[commit-check](.agents/skills/commit-check/SKILL.md)
58
49
  - 单线详规:[stages.md](references/stages.md)
59
- - 多 issue 编排主过程:本文件 [多 issue 编排](#多-issue-编排按依赖分层并行)
60
50
  - 多 issue 编排详规:[orchestration.md](references/orchestration.md)(全量保留)
@@ -43,17 +43,21 @@ Ln = 最后一层
43
43
 
44
44
  ```
45
45
  for each 层 Li in L1..Ln:
46
- 并行派发:为 Li 中每个 issue 启动一个子代理(single 模式,禁止 parallel tasks 数组)
46
+ 准备:若 Li N>1 则为每个 issue 创建独立 worktree——幂等清理同名 `wt/<feature>-#NN` `.worktrees/<feature>-#NN` 后 `git worktree add .worktrees/<feature>-#NN -b wt/<feature>-#NN $BASE_HEAD`;N==1 时复用主 worktree,不创建。
47
+ 并行派发:为 Li 中每个 issue 启动一个子代理(single 模式,cwd 为对应 worktree,禁止 parallel tasks 数组)
47
48
  等待:阻塞直到 Li 全部子代理返回回执卡片
48
- 验收:编排器按 A3 验收清单逐 issue 验收(只认回执卡片的关键信息 + 抽检验证,不消费全量日志)
49
- 层收敛验证:验收全通过进入全量验证(完成条件 4 项,全部通过才进下一层,任一失败按 A5 回退):①该层全部 issue 验收通过 ②相关测试套件通过(全量仅在 A4) ③`git status` 卫生(仅删本次临时产物,正向;护栏:禁止 `git reset --hard`/`git checkout .`/`git clean -fd`/`git stash push --include-untracked`)④历史校验 `git merge-base --is-ancestor $BASE_HEAD HEAD` 通过;验收不通过或相关/卫生/历史任一失败按 A5 回退重派该 issue
49
+ 验收:编排器按 A3 验收清单逐 issue 验收(只认回执卡片的关键信息 + 抽检验证在对应 worktree/归集后主分支上执行,不消费全量日志)
50
+ 归集:若 N>1 按编号升序在主分支上 `git merge --no-ff wt/<feature>-#NN` 顺序归集各分支;N==1 时归集即该分支已在主分支。归集冲突按 A5 仅打回冲突单 issue
51
+ 层收敛验证:验收全通过进入全量验证(完成条件 4 项,全部通过且归集成功才进下一层,任一失败按 A5 最小重派该 issue):①该层全部 issue 验收通过 ②相关测试套件通过(全量仅在 A4) ③`git status` 卫生(归集后主分支仅删本次临时产物,正向;护栏:禁止 `git reset --hard`/`git checkout .`/`git clean -fd`/`git stash push --include-untracked`)④历史校验 `git merge-base --is-ancestor $BASE_HEAD HEAD` 通过;验收不通过或相关/卫生/历史任一失败按 A5 重建全新 worktree 最小重派该 issue
52
+ 清理:该层归集后幂等清理该层 worktree(`git worktree remove -f .worktrees/<feature>-#NN && git branch -D wt/<feature>-#NN && git worktree prune`),失败 issue 的 worktree 保留至重派时以失败分支为增量基础重建
50
53
  全部层层收敛通过后进入 A4 全量收敛
51
54
  ```
52
55
 
53
56
  - **派发纪律**:与阶段⑤双轴审查一致——逐个 `subagent` 派发,禁止 `parallel tasks` 数组(同因:中文报告截断)。
54
- - **等待语义**:层内任一子代理失败不取消同层其他子代理;待层内全部返回后统一按 A5 处理。
57
+ - **等待语义**:层内任一子代理失败不取消同层其他子代理;待层内全部返回后统一按 A5 最小重派处理;已通过者的归集先行,不阻塞归集。
58
+ - **Worktree 隔离**:层内 `N>1` 时文件系统级隔离,后完成者不再覆盖先完成者工作区;`N==1` 退化为共享 worktree 以省成本。worktree 路径 `.worktrees/<feature>-#NN` 不纳入 `git status`,分支 `wt/<feature>-#NN` 与 worktree 一一对应。
55
59
  - **回合连续性**:编排器在层间不结束回合——一层收敛后立即派发下一层,直到全部层完成或外部阻塞;预告下一层后立即执行。
56
- - **Git 历史保护(正向:仅追加;护栏:禁改写)**:编排器在分层调度前记录 `BASE_HEAD=$(git rev-parse HEAD)`,每层收敛后校验 `git merge-base --is-ancestor $BASE_HEAD HEAD`,失败即经 `git reflog` 恢复;为达 `git status` 干净仅删本次产生的 `[DEBUG-...]`临时产物(正向),护栏:禁止 `git reset --hard`/`git checkout .`/`git clean -fd`/`git stash push --include-untracked`/`git push --force` 等(需显式确认)。
60
+ - **Git 历史保护(正向:仅追加;护栏:禁改写)**:编排器在分层调度前记录 `BASE_HEAD=$(git rev-parse HEAD)`,每层归集后校验 `git merge-base --is-ancestor $BASE_HEAD HEAD`,失败即经 `git reflog` 恢复;为达 `git status` 干净仅删本次产生的 `[DEBUG-...]`临时产物(正向),护栏:禁止 `git reset --hard`/`git checkout .`/`git clean -fd`/`git stash push --include-untracked`/`git push --force` 等(需显式确认)。归集后主分支 `git log --oneline` 含全部层已通过 issue 的 `#NN`。
57
61
 
58
62
  ### A3. 子代理契约(单 issue 单代理)
59
63
 
@@ -65,7 +69,8 @@ for each 层 Li in L1..Ln:
65
69
  - `spec.md`(feature 级共享 spec,若无则以该 issue 正文为准)
66
70
  - 分配的单个 `NN-<slug>.md`(唯一 issue 输入)
67
71
  - `CONTEXT.md` + `docs/adr/`(术语与决策一致性)
68
- - **执行**:严格走 tdd-implement ①→⑦全流程——①理解需求(读 spec + issue)→ ②确认 seams(该 issue 范围内)→ ③红-绿循环(每 cycle 后 typecheck + 相关测试)→ ④相关测试套件(仅该 issue 相关 + typecheck,不跑全量;全量由编排器在 A2 层收敛/A4 统一执行,单 issue 单线模式仍跑全量)→ ⑤双轴 review → ⑥commit-check 门禁 + commit → ⑦文档对齐(仅该 issue 相关描述)+ `Status: resolved` + `## 实施总结` 落盘 + 目录卫生。TDD 语义以 [tdd 技能](.agents/skills/tdd/SKILL.md) 为唯一事实源,不在子代理内重写。
72
+ - (worktree 隔离时)分配的独立 worktree 路径 `.worktrees/<feature>-#NN` 与分支 `wt/<feature>-#NN`,`cwd` 为该 worktree
73
+ - **执行**:严格走 tdd-implement ①→⑦全流程——①理解需求(读 spec + issue)→ ②确认 seams(该 issue 范围内)→ ③红-绿循环(每 cycle 后 typecheck + 相关测试)→ ④相关测试套件(仅该 issue 相关 + typecheck,不跑全量;全量由编排器在 A2 层收敛/A4 统一执行,单 issue 单线模式仍跑全量)→ ⑤双轴 review → ⑥commit-check 门禁 + commit → ⑦文档对齐(仅该 issue 相关描述)+ `Status: resolved` + `## 实施总结` 落盘 + 目录卫生。TDD 语义以 [tdd 技能](.agents/skills/tdd/SKILL.md) 为唯一事实源,不在子代理内重写。worktree 下在独立分支上执行,入口记录 `WT_BASE_HEAD=$(git rev-parse HEAD)` 并自检 `merge-base --is-ancestor`,`N==1` 时复用主 worktree。
69
74
  - **产出**:
70
75
  - 独立 commit(message 含 issue 编号,如 `feat(<feature>): <issue title> (#NN)`)
71
76
  - 该 issue 文件 `Status: resolved` + 底部 `## 实施总结`
@@ -102,7 +107,7 @@ for each 层 Li in L1..Ln:
102
107
 
103
108
  任一项不通过 → 打回重派该子代理(仅该 issue),层内其他已通过不受影响;验收通过才计入层收敛。验收结论随层收敛一并输出。
104
109
 
105
- 子代理内部的回合连续性、任务分解、Todo 规定、Git 历史保护与单线模式完全一致(见 [stages.md 阶段③ 3e/3f/3h](stages.md#阶段-③tdd-开发循环) 与 Git 安全前置)。子代理同样在入口记录 `BASE_HEAD` 并在每阶段出口校验 `git merge-base --is-ancestor $BASE_HEAD HEAD`,禁止 `git reset --hard`/`git checkout .`/`git clean -fd`/`git stash push --include-untracked` 等。
110
+ 子代理内部的回合连续性、任务分解、Todo 规定、Git 历史保护与单线模式完全一致(见 [stages.md 阶段③ 3e/3f/3h](stages.md#阶段-③tdd-开发循环) 与 Git 安全前置)。子代理同样在入口记录 `BASE_HEAD`(worktree 下为 `WT_BASE_HEAD`)并在每阶段出口校验 `git merge-base --is-ancestor $BASE_HEAD HEAD`,禁止 `git reset --hard`/`git checkout .`/`git clean -fd`/`git stash push --include-untracked` 等;worktree 入口幂等清理同名残留。
106
111
 
107
112
  ### A4. 全量收敛
108
113
 
@@ -115,10 +120,10 @@ for each 层 Li in L1..Ln:
115
120
 
116
121
  ### A5. 回退与冲突
117
122
 
118
- - **子代理内回退**:按 [stages.md 回退路由](stages.md#回退路由) 在子代理内闭环(typecheck 失败 ③、测试失败 ③、review 不通过 ③/②/①)。
119
- - **层收敛失败**:层内任一子代理未达到 `resolved`(测试失败 / review 不通过 / commit-check 门禁失败)→ issue 保持原 `Status`,编排器在层等待结束后报告失败清单,不自动进入下一层;待修复后重派该层失败节点。
120
- - **全量收敛失败**:A4 全量测试失败 → 定位到失败测试归属的 issue,回到其所在层重派对应子代理。
121
- - **文件冲突**:同层子代理若触及同一文件,后完成者 rebase 解决冲突后重跑 typecheck + 相关测试;跨层天然串行无冲突。冲突解决禁止使用 `git reset --hard`/`git checkout .`/`git clean -fd`/`git stash push --include-untracked` 丢弃对方提交,rebase 后必校验 `git merge-base --is-ancestor $BASE_HEAD HEAD` 且 `git log --oneline` 含全部层提交;冲突检测以 `git` 合并结果为准,编排器不做静态预判。
123
+ - **子代理内回退(最小单元)**:按 [stages.md 回退路由](stages.md#回退路由) 精确回退——`typecheck 失败→③`、`测试失败→③`、`review Standards 味→⑤重构`、`review Spec 偏离→①`、`review seams 遗漏→②补 seams`、`commit-check 文档/卫生/message 失败→⑥/⑦ 对应阶段`。失败点之前的已 `done` seam/Todo 永不回退,仅重跑失败阶段及下游;`seams 清单` 与已绿 seam 默认复用,仅 `seams 遗漏/需求偏差` 两类才回到 `②/①` 重确认。
124
+ - **层收敛失败(最小重派)**:层内任一子代理未达到 `resolved`(含验收 5 项、相关测试、卫生、历史校验任一不过)→ issue 保持原 `Status`,编排器在层等待结束后报告失败清单,已通过者先归集到主分支(按编号升序顺序 merge),不自动进入下一层;待修复后仅重派失败节点(重建全新 worktree,不复用旧目录;worktree 下以失败分支 `wt/<feature>-#NN` 为增量基础叠加最小 fix,仅当 `merge 冲突` 导致分叉时以主分支最新重建并 cherry-pick 已通过部分),同层其他已通过不受影响。层原子语义保持:`Li` 未全 `resolved` 不派 `L_{i+1}`,但已通过者的归集先行以减二次冲突。
125
+ - **全量收敛失败(精确定位)**:A4 全量测试失败 → 以测试文件路径/报错栈精确定位到单 issue 单 seam,回到其所在层仅重派该 issue 的失败 seam + 相关测试,全量由编排器在重派后再次 A4 统一验证;无法精确定位时退化到层级重派,不重跑无关联 issue。
126
+ - **文件冲突(显式化)**:worktree 隔离下冲突在归集 `merge` 时显式暴露(不再以共享 worktree 的“后完成者 rebase 覆盖”形式静默重跑),仅打回冲突归属的单 issue;跨层天然串行无冲突。冲突解决禁止使用 `git reset --hard`/`git checkout .`/`git clean -fd`/`git stash push --include-untracked` 丢弃对方提交,解决后必校验 `git merge-base --is-ancestor $BASE_HEAD HEAD` 且 `git log --oneline` 含全部层提交;冲突检测以 `git` 合并结果为准,编排器不做静态预判。
122
127
  - **环依赖**:A0 检测到环即报错终止,不派发任何子代理。
123
128
 
124
129
  ### 出口条件
@@ -227,7 +227,7 @@
227
227
  ```
228
228
 
229
229
  5. 无关联 issue(直接实现用户给的 spec)→ 跳过状态更新,将总结作为会话最终输出
230
- 6. **保持目录卫生**:仅清理本次实现产生的临时产物——`[DEBUG-...]` 标记的调试代码/日志、一次性脚本、临时文件与备份文件;用 `git status` 确认工作区只含预期改动,无残留未跟踪文件后才结束。禁止为达干净而执行 `git reset --hard`、`git checkout .`、`git clean -fd`、`git stash push --include-untracked` 等(需显式用户确认;`stash` 如需使用改用 `--keep-index` 并在 `pop` 后校验 `git merge-base --is-ancestor $BASE_HEAD HEAD`)。
230
+ 6. **保持目录卫生**:仅清理本次实现产生的临时产物——`[DEBUG-...]` 标记的调试代码/日志、一次性脚本、临时文件与备份文件;用 `git status` 确认工作区只含预期改动,无残留未跟踪文件后才结束。Git 历史保护与禁令见本文件阶段③ [Git 安全前置](#git-安全前置历史保护)与 `docs/agents/skill-design.md` Rule 4,仅删本次临时产物,禁止为达干净而执行 git 层破坏性命令。
231
231
 
232
232
  ### 出口条件
233
233
 
@@ -32,7 +32,7 @@
32
32
 
33
33
  - 探索/定位/理解代码库 → 有 `.codegraph/` 索引:`codegraph explore`(CLI)优先;无索引用 explore 子代理
34
34
  - 后台调研 → research;原型验证 → prototype
35
- - 实现(有 spec)→ implement(无 spec 先 to-spec);测试先行 → tdd
35
+ - 实现(有 spec 且要求 TDD/测试先行)→ tdd-implement(seam red-green);实现(有 spec 不要求 TDD)→ implement(无 spec 先 to-spec);测试先行 → tdd
36
36
  - 设计打磨 → grilling;达成共识→spec → grill-to-spec(grilling→domain-modeling→to-spec)
37
37
  - 领域术语/ADR → domain-modeling;模块接口 → codebase-design;巨型规划 → wayfinder
38
38
  - 诊断 → diagnose-fix(编排 diagnosing-bugs + tdd,硬门槛);审查 → code-review;合并冲突 → resolving-merge-conflicts;提交前 → commit-check(文档一致性 → 目录卫生 → commit message,三项)