@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.
- package/.agents/skills/commit-check/SKILL.md +1 -0
- package/.agents/skills/tdd-implement/SKILL.md +9 -19
- package/.agents/skills/tdd-implement/references/orchestration.md +16 -11
- package/.agents/skills/tdd-implement/references/stages.md +1 -1
- package/bin/cli.js +177 -66
- package/package.json +1 -1
- package/template/.agents/skills/commit-check/SKILL.md +1 -0
- package/template/.agents/skills/tdd-implement/SKILL.md +9 -19
- package/template/.agents/skills/tdd-implement/references/orchestration.md +16 -11
- package/template/.agents/skills/tdd-implement/references/stages.md +1 -1
- package/template/AGENTS.md +1 -1
|
@@ -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
|
|
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`
|
|
15
|
+
- **多 issue 编排**:`.scratch/<feature>/issues/` 下多文件且含 `Blocked by` 时走编排模式——见下节与 [orchestration.md](references/orchestration.md)。
|
|
16
16
|
|
|
17
17
|
## 多 issue 编排(按依赖分层并行)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
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
|
-
|
|
35
|
+
子代理内部仍走上表 ①→⑦(其中 ④ 为相关测试口径,全量由编排器收敛)。
|
|
44
36
|
|
|
45
37
|
### 阶段间流转
|
|
46
38
|
|
|
47
39
|
- 正常流转:出口条件满足即进入下一阶段,不在阶段间停顿。
|
|
48
|
-
- 回退路由:见 [stages.md#回退路由](references/stages.md#回退路由)
|
|
49
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
-
-
|
|
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
|
|
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
|
-
-
|
|
119
|
-
-
|
|
120
|
-
-
|
|
121
|
-
-
|
|
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`
|
|
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
|
|
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 [--
|
|
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
|
|
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
|
-
--
|
|
52
|
-
--force
|
|
53
|
-
--
|
|
68
|
+
--all 范围:含非编程与可选独有(默认仅编程 22)
|
|
69
|
+
--force 力度:硬盖(备份 .bak + 删多余,全量 add/update/remove)
|
|
70
|
+
--dry-run 预演:只比对不写盘
|
|
54
71
|
--dest <path> Target directory (default: current directory)
|
|
55
|
-
--
|
|
56
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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,
|
|
334
|
+
async function syncCommand({ dest, force, all, dryRun, json, upstreamUrl, ref }) {
|
|
284
335
|
const onlyProgramming = !all;
|
|
285
|
-
|
|
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
|
-
|
|
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
|
|
313
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
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')
|
|
489
|
-
|
|
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')
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
else if (arg.startsWith('--
|
|
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
|
-
|
|
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')
|
|
511
|
-
|
|
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')
|
|
515
|
-
|
|
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
|
-
|
|
563
|
-
|
|
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(
|
|
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
|
|
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
|
@@ -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
|
|
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`
|
|
15
|
+
- **多 issue 编排**:`.scratch/<feature>/issues/` 下多文件且含 `Blocked by` 时走编排模式——见下节与 [orchestration.md](references/orchestration.md)。
|
|
16
16
|
|
|
17
17
|
## 多 issue 编排(按依赖分层并行)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
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
|
-
|
|
35
|
+
子代理内部仍走上表 ①→⑦(其中 ④ 为相关测试口径,全量由编排器收敛)。
|
|
44
36
|
|
|
45
37
|
### 阶段间流转
|
|
46
38
|
|
|
47
39
|
- 正常流转:出口条件满足即进入下一阶段,不在阶段间停顿。
|
|
48
|
-
- 回退路由:见 [stages.md#回退路由](references/stages.md#回退路由)
|
|
49
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
-
-
|
|
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
|
|
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
|
-
-
|
|
119
|
-
-
|
|
120
|
-
-
|
|
121
|
-
-
|
|
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`
|
|
230
|
+
6. **保持目录卫生**:仅清理本次实现产生的临时产物——`[DEBUG-...]` 标记的调试代码/日志、一次性脚本、临时文件与备份文件;用 `git status` 确认工作区只含预期改动,无残留未跟踪文件后才结束。Git 历史保护与禁令见本文件阶段③ [Git 安全前置](#git-安全前置历史保护)与 `docs/agents/skill-design.md` Rule 4,仅删本次临时产物,禁止为达干净而执行 git 层破坏性命令。
|
|
231
231
|
|
|
232
232
|
### 出口条件
|
|
233
233
|
|
package/template/AGENTS.md
CHANGED
|
@@ -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,三项)
|