@heihei0299/matt-skills 1.6.1 → 1.6.3
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/tdd-implement/SKILL.md +1 -1
- package/.agents/skills/tdd-implement/references/orchestration.md +13 -18
- package/README.md +16 -16
- package/bin/cli.js +47 -26
- package/package.json +1 -1
- package/scripts/sync-upstream.js +19 -7
- package/template/.agents/skills/tdd-implement/SKILL.md +1 -1
- package/template/.agents/skills/tdd-implement/references/orchestration.md +13 -18
- package/template/AGENTS.md +11 -9
|
@@ -16,7 +16,7 @@ description: "TDD seam red-green loop: use when the user provides a spec/ticket
|
|
|
16
16
|
|
|
17
17
|
## 多 issue 编排(按依赖分层并行)
|
|
18
18
|
|
|
19
|
-
触发见 [orchestration.md](references/orchestration.md);`.scratch/<feature>/issues/` 下多文件且部分含 `Blocked by` 时触发,主过程 A0 依赖图 → A1 Kahn 分层 L1入度0→L2→Ln → A2
|
|
19
|
+
触发见 [orchestration.md](references/orchestration.md);`.scratch/<feature>/issues/` 下多文件且部分含 `Blocked by` 时触发,主过程 A0 依赖图 → A1 Kahn 分层 L1入度0→L2→Ln → A2 分层调度(逐个 subagent single 派发、共享 working tree,禁止主会话直做;`N>1` 时串行错峰派发以减同文件竞写,文件冲突由后完成者 rebase 解决) → A3 子代理契约 → A4 全量收敛 → A5 回退与冲突(最小重派:按失败点精确回退、精确定位单 issue 单 seam,全量保留为详规真相源)。必须先编排子代理计划(输出依赖图/DAG 与 Kahn 分层 `L1..Ln` 并确认)后才派发,禁止跳过计划直接派发导致重复调度;编排模式下所有 issue 的 `①→⑦` 必须经子代理执行、主会话仅编排与验收,禁止任何“为省开销/效率”在主会话直做;层收敛 4 项(验收/相关测试/`git status`仅删`[DEBUG-...]`/ `BASE_HEAD`历史校验 `git merge-base --is-ancestor`)与子代理回执卡片(≤30行、缺字段视为不通过)、打回重派、rebase 冲突处理等可执行约束全量见 orchestration.md。
|
|
20
20
|
|
|
21
21
|
## Steps
|
|
22
22
|
|
|
@@ -38,27 +38,23 @@ Ln = 最后一层
|
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
每层内节点互无依赖,可并行;层间有依赖,必须串行。分层结果在编排开始前一次性展示给用户确认(合规交互点),确认后才派发。
|
|
41
|
-
|
|
41
|
+
若两 issue 在阶段②已声明预期改动同一文件,编排器在 A0 后提示建议追加 `Blocked by` 使其串行(轻提示,不强制);主要仍靠 A2 串行错峰自然错峰。
|
|
42
42
|
### A2. 分层调度
|
|
43
43
|
|
|
44
44
|
```
|
|
45
45
|
for each 层 Li in L1..Ln:
|
|
46
|
-
|
|
47
|
-
并行派发:为 Li 中每个 issue 启动一个子代理(single 模式,cwd 为对应 worktree,禁止 parallel tasks 数组)
|
|
46
|
+
并行派发:为 Li 中每个 issue 启动一个子代理(single 模式,共享 working tree,禁止 parallel tasks 数组;N>1 时串行错峰派发)
|
|
48
47
|
等待:阻塞直到 Li 全部子代理返回回执卡片
|
|
49
|
-
验收:编排器按 A3 验收清单逐 issue 验收(只认回执卡片的关键信息 +
|
|
50
|
-
|
|
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 保留至重派时以失败分支为增量基础重建
|
|
48
|
+
验收:编排器按 A3 验收清单逐 issue 验收(只认回执卡片的关键信息 + 抽检验证在最新 HEAD 上执行,不消费全量日志)
|
|
49
|
+
层收敛验证:验收全通过进入全量验证(完成条件 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 重派该 issue
|
|
53
50
|
全部层层收敛通过后进入 A4 全量收敛
|
|
54
51
|
```
|
|
55
52
|
|
|
56
53
|
- **派发纪律**:与阶段⑤双轴审查一致——逐个 `subagent` 派发,禁止 `parallel tasks` 数组(同因:中文报告截断)。
|
|
57
|
-
- **等待语义**:层内任一子代理失败不取消同层其他子代理;待层内全部返回后统一按 A5
|
|
58
|
-
-
|
|
54
|
+
- **等待语义**:层内任一子代理失败不取消同层其他子代理;待层内全部返回后统一按 A5 最小重派处理。
|
|
55
|
+
- **冲突判定(Q1/Q2)**:同文件即冲突,以 `HEAD` 已移动(`git log` 已含先完成者 `#NN`)为准判定慢者前后不一致;不在工作区瞬时覆盖时判定。
|
|
59
56
|
- **回合连续性**:编排器在层间不结束回合——一层收敛后立即派发下一层,直到全部层完成或外部阻塞;预告下一层后立即执行。
|
|
60
|
-
- **Git 历史保护(正向:仅追加;护栏:禁改写)**:编排器在分层调度前记录 `BASE_HEAD=$(git rev-parse HEAD)
|
|
61
|
-
|
|
57
|
+
- **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` 等(需显式确认)。
|
|
62
58
|
### A3. 子代理契约(单 issue 单代理)
|
|
63
59
|
|
|
64
60
|
每个子代理是一个**完整的 tdd-implement 单 issue 执行单元**,输入与产出严格界定:
|
|
@@ -69,8 +65,7 @@ for each 层 Li in L1..Ln:
|
|
|
69
65
|
- `spec.md`(feature 级共享 spec,若无则以该 issue 正文为准)
|
|
70
66
|
- 分配的单个 `NN-<slug>.md`(唯一 issue 输入)
|
|
71
67
|
- `CONTEXT.md` + `docs/adr/`(术语与决策一致性)
|
|
72
|
-
|
|
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。
|
|
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) 为唯一事实源,不在子代理内重写。
|
|
74
69
|
- **产出**:
|
|
75
70
|
- 独立 commit(message 含 issue 编号,如 `feat(<feature>): <issue title> (#NN)`)
|
|
76
71
|
- 该 issue 文件 `Status: resolved` + 底部 `## 实施总结`
|
|
@@ -107,7 +102,7 @@ for each 层 Li in L1..Ln:
|
|
|
107
102
|
|
|
108
103
|
任一项不通过 → 打回重派该子代理(仅该 issue),层内其他已通过不受影响;验收通过才计入层收敛。验收结论随层收敛一并输出。
|
|
109
104
|
|
|
110
|
-
子代理内部的回合连续性、任务分解、Todo 规定、Git 历史保护与单线模式完全一致(见 [stages.md 阶段③ 3e/3f/3h](stages.md#阶段-③tdd-开发循环) 与 Git 安全前置)。子代理同样在入口记录 `BASE_HEAD
|
|
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` 等。
|
|
111
106
|
|
|
112
107
|
### A4. 全量收敛
|
|
113
108
|
|
|
@@ -121,9 +116,9 @@ for each 层 Li in L1..Ln:
|
|
|
121
116
|
### A5. 回退与冲突
|
|
122
117
|
|
|
123
118
|
- **子代理内回退(最小单元)**:按 [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
|
|
119
|
+
- **层收敛失败(最小重派)**:层内任一子代理未达到 `resolved`(含验收 5 项、相关测试、卫生、历史校验任一不过)→ 该 issue 保持原 `Status`,编排器在层等待结束后报告失败清单,不自动进入下一层;待修复后仅重派失败节点,同层其他已通过不受影响。层原子语义保持:`Li` 未全 `resolved` 不派 `L_{i+1}`。
|
|
125
120
|
- **全量收敛失败(精确定位)**:A4 全量测试失败 → 以测试文件路径/报错栈精确定位到单 issue 单 seam,回到其所在层仅重派该 issue 的失败 seam + 相关测试,全量由编排器在重派后再次 A4 统一验证;无法精确定位时退化到层级重派,不重跑无关联 issue。
|
|
126
|
-
-
|
|
121
|
+
- **文件冲突(Q1-Q4)**:同文件即冲突(Q1 慢者因 `HEAD` 已移动致前后不一致);慢者完成当前 seam 的 `红→绿→typecheck` 后再以新 HEAD 为基线 rebase,仅重做该冲突文件关联的 seam(其余已绿复用,Q3/Q4 最小化);后完成者 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` 合并结果(`HEAD` 已移动)为准,编排器不做静态文件监听预判。
|
|
127
122
|
- **环依赖**:A0 检测到环即报错终止,不派发任何子代理。
|
|
128
123
|
|
|
129
124
|
### 出口条件
|
|
@@ -134,8 +129,8 @@ for each 层 Li in L1..Ln:
|
|
|
134
129
|
|
|
135
130
|
### 边界
|
|
136
131
|
|
|
137
|
-
- 单 issue / 单 spec
|
|
138
|
-
- 子代理不跨 issue
|
|
132
|
+
- 单 issue / 单 spec 不走本文件;但一旦进入编排模式(多 issue 且含 Blocked by),无论 N==1 还是 N>1,所有 issue 的 ①→⑦ 必须经子代理 single 派发完成,禁止主会话直做(编排器仅编排、验收、归集、清理)
|
|
133
|
+
- 子代理不跨 issue 改动;编排器不替子代理写实现代码;主会话不直接写业务代码/测试/文档(仅做编排与验收)
|
|
139
134
|
- 汇总总结只在对话输出,不落盘额外汇总文件
|
|
140
135
|
- 必须先编排子代理计划(输出依赖图/DAG/Kahn 分层 `L1..Ln` 并确认)后才派子代理,禁止跳过计划直接派发导致重复调度
|
|
141
136
|
- TDD 语义以 [tdd 技能](.agents/skills/tdd/SKILL.md) 为唯一事实源,不在本文件重写
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ mattpocock/skills(`skills/engineering` + `skills/productivity`)的配置仓
|
|
|
8
8
|
template/
|
|
9
9
|
├── AGENTS.md 项目级全局配置(行为路由 + 分文件指针)
|
|
10
10
|
├── .agents/
|
|
11
|
-
│ └── skills/ 32 个技能(上游 26 + 独有 6:ci-guard、tdd-implement、grill-to-spec、diagnose-fix、commit-check、scaffold-functional-test)单一源;模板全量 32
|
|
11
|
+
│ └── skills/ 32 个技能(上游 26 + 独有 6:ci-guard、tdd-implement、grill-to-spec、diagnose-fix、commit-check、scaffold-functional-test)单一源;模板全量 32,默认安装编程相关 23(engineering 18 + 独有所需 1 + 核心独有 4,见 config/engineering.json 与 config/required.json),--all 展开全量
|
|
12
12
|
├── .pi/ pi-agent 项目配置
|
|
13
13
|
│ ├── skills/ 空占位(项目自定义技能,含 .gitkeep + README.md)
|
|
14
14
|
│ ├── prompts/ issue-audit 命令(prompt template)
|
|
@@ -29,17 +29,17 @@ template/
|
|
|
29
29
|
在目标仓库根目录执行一条命令:
|
|
30
30
|
|
|
31
31
|
```sh
|
|
32
|
-
npx @heihei0299/matt-skills init #
|
|
32
|
+
npx @heihei0299/matt-skills init # 默认编程相关 23(engineering 18 + 独有所需 1 + 核心独有 4),--all 展开全量 32
|
|
33
33
|
npx @heihei0299/matt-skills init --all # 安装全量 32(含 productivity)
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
`init` 做一件事:复制 `template/` 快照(`AGENTS.md`、`.agents/skills/`、`.opencode/`、`.pi/`)到当前目录;模板全量 32
|
|
36
|
+
`init` 做一件事:复制 `template/` 快照(`AGENTS.md`、`.agents/skills/`、`.opencode/`、`.pi/`)到当前目录;模板全量 32,默认仅安装默认范围 23(engineering 18 + 独有所需 1 + 核心独有 4,见 config/engineering.json 与 config/required.json),`--all` 展开全量,无需二次拉取上游。
|
|
37
37
|
|
|
38
|
-
选项:`--dest <path>` 指定目标目录(默认当前目录);`--force` 覆盖已存在的文件(直接覆盖,`AGENTS.md` 会备份到 `.bak`)(默认跳过);`--all` 包含非编程技能(productivity
|
|
38
|
+
选项:`--dest <path>` 指定目标目录(默认当前目录);`--force` 覆盖已存在的文件(直接覆盖,`AGENTS.md` 会备份到 `.bak`)(默认跳过);`--all` 包含非编程技能(productivity,默认编程 23:engineering 18 + 独有所需 1 + 核心独有 4)。
|
|
39
39
|
**增量同步(已有项目)**:已有项目更新到最新模板与技能:
|
|
40
40
|
|
|
41
41
|
```sh
|
|
42
|
-
npx @heihei0299/matt-skills sync # 默认仅对比不写盘(check),有差异 exit 1,--json
|
|
42
|
+
npx @heihei0299/matt-skills sync # 默认仅对比不写盘(check),有差异 exit 1,--json 可解析(默认范围)
|
|
43
43
|
npx @heihei0299/matt-skills sync --all --json # 全量对比
|
|
44
44
|
npx @heihei0299/matt-skills sync --apply # 安全增量:AGENTS.md 定制跳过,编程技能 rm+cp,不删多余 productivity(默认保留)
|
|
45
45
|
npx @heihei0299/matt-skills sync --apply --all # 安全增量全量
|
|
@@ -48,8 +48,8 @@ npx @heihei0299/matt-skills sync --force --all # 硬盖全量
|
|
|
48
48
|
npx @heihei0299/matt-skills sync --apply --dest <path> --upstream <url> --ref <ref> --json # 选项可组合
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
`sync` 专为已有项目设计,三档语义:默认 `check`
|
|
52
|
-
目标仓库会话即自动加载共享技能(`.agents/skills/` 单一源,默认编程
|
|
51
|
+
`sync` 专为已有项目设计,三档语义:默认 `check` 仅对比不写盘(默认范围 engineering + 独有所需,`--all` 展开全量,打印“上游 HEAD / 本地非独有 vs 上游 / 新增/更新/删除/一致”表,`--json` 可解析,有差异 `exit 1`);`--apply` 安全增量写盘(默认模式:`AGENTS.md` 若含独有路由如 `tdd-implement` 则跳过,`.agents/skills` 按默认子集 `rm+cp force` 覆盖但不 `remove` 范围外 productivity,`template/.agents/.opencode/.pi` 按过滤增量 `add/update`,旧镜像 `.pi/skills` + `.opencode/skills` 中残留的共享技能自动清理但保留项目自定义;`--all` 则全量);`--force` 硬盖(默认模式:`AGENTS.md` 备份 `.bak` 后强制覆盖,默认技能 `add/update/remove` 全做(含范围外 productivity 删除与旧镜像清理);`--all` 则全量硬盖)。`--dest`、`--upstream`、`--ref`、`--json`、`--all` 在三档均可透传。
|
|
52
|
+
目标仓库会话即自动加载共享技能(`.agents/skills/` 单一源,默认编程 23,`--all` 全量 32)与项目级全局配置(行为路由表、分文件约定);项目自定义技能可按需放入 `.pi/skills/` 或 `.opencode/skills/`(按 harness 自动发现);`issue-audit` 以子代理 + 命令形式分发(`.opencode/agents/`、`.opencode/commands/`);9 个显式触发技能注册为 opencode 命令(`.opencode/commands/`,`/命令名` 触发)。
|
|
53
53
|
**pi-agent 用户**:初始化命令完全相同。pi 从 `.agents/skills/` 自动发现全部共享技能,无需额外指向;`.pi/skills/` 仅用于项目自定义。首次在目标仓库交互启动时 pi 会询问项目信任,用 `/trust` 保存即可。
|
|
54
54
|
|
|
55
55
|
**手动方式(备选)**:无 npx 环境时,将 `template/` 整个文件夹复制到目标仓库根目录即可(已含全量技能):
|
|
@@ -103,13 +103,13 @@ pi 下对应能力以内置工具或已装扩展为准(`AGENTS.md`「能力边
|
|
|
103
103
|
|
|
104
104
|
- **全局**:`~/.pi/agent/skills/`、`~/.agents/skills/`(用户级技能,自动发现);配置在 `~/.pi/agent/settings.json`
|
|
105
105
|
- **项目**:
|
|
106
|
-
- `.agents/skills/` — 共享技能单一源(默认编程
|
|
106
|
+
- `.agents/skills/` — 共享技能单一源(默认编程 23,`--all` 全量 32,自动发现)
|
|
107
107
|
- `.pi/skills/` — 项目自定义技能(pi 标准结构,自动发现,仅放项目本地技能)
|
|
108
108
|
- `.pi/prompts/` — pi 命令(prompt template)自动发现,如 `issue-audit.md` → `/issue-audit`
|
|
109
109
|
- `.pi/settings.json` — 已简化为空对象(历史指向 `.opencode/skills` 已移除,共享技能走 `.agents/skills`)
|
|
110
110
|
### opencode
|
|
111
111
|
|
|
112
|
-
- **项目**:`.agents/skills/`(共享技能单一源,默认编程
|
|
112
|
+
- **项目**:`.agents/skills/`(共享技能单一源,默认编程 23,`--all` 全量 32)、`.opencode/skills/`(项目自定义技能)、`.opencode/agents/`(子代理)、`.opencode/commands/`(命令:issue-audit + 9 个显式触发技能,`/命令名` 触发)、`.opencode/docs/`(文档)
|
|
113
113
|
|
|
114
114
|
同一份技能(Agent Skills 标准)与 `AGENTS.md` 行为路由在两种 harness 下均可加载:pi 与 codex/claude 从 `.agents/skills/` 自动发现;opencode 按本模板约定同样优先读取 `.agents/skills/`(`.opencode/skills/` 仅用于项目自定义)。
|
|
115
115
|
|
|
@@ -118,23 +118,23 @@ pi 下对应能力以内置工具或已装扩展为准(`AGENTS.md`「能力边
|
|
|
118
118
|
仓库内提供安装管理 CLI(`bin/cli.js`,依赖 `prompts`,见 `package.json`),同时作为 npm 包 `@heihei0299/matt-skills` 分发(`npx @heihei0299/matt-skills <command>`):
|
|
119
119
|
|
|
120
120
|
```sh
|
|
121
|
-
node bin/cli.js init [--dest <dir>] [--force] [--all] # 初始化项目:template 全量 32,默认编程
|
|
121
|
+
node bin/cli.js init [--dest <dir>] [--force] [--all] # 初始化项目:template 全量 32,默认编程 23
|
|
122
122
|
node bin/cli.js sync [--apply|--force] [--all] [--dest <path>] [--upstream <url>] [--ref <ref>] [--json] # 同步已有项目到最新(默认编程)
|
|
123
123
|
node bin/cli.js list [--json] [--all] # 列出技能(默认编程)
|
|
124
124
|
node bin/cli.js install [选项] # 把技能复制到目标工具目录(交互式选择,默认编程)
|
|
125
|
-
node bin/cli.js check [--json] [--all] [--upstream <url>] [--ref <ref>] # 只读检查上游技能是否最新(等价 sync
|
|
125
|
+
node bin/cli.js check [--json] [--all] [--upstream <url>] [--ref <ref>] # 只读检查上游技能是否最新(等价 sync 默认,默认范围)
|
|
126
126
|
```
|
|
127
127
|
|
|
128
|
-
`init` 选项:`--dest <path>` 指定目标目录(默认当前目录);`--force` 覆盖已存在的文件(直接覆盖,`AGENTS.md` 会备份到 `.bak`)(默认跳过);`--all` 包含非编程(productivity
|
|
129
|
-
`sync` 选项:`--apply`
|
|
130
|
-
`check` 选项:`--json`、`--all
|
|
128
|
+
`init` 选项:`--dest <path>` 指定目标目录(默认当前目录);`--force` 覆盖已存在的文件(直接覆盖,`AGENTS.md` 会备份到 `.bak`)(默认跳过);`--all` 包含非编程(productivity,默认编程 23:engineering 18 + 独有所需 1 + 核心独有 4),见「初始化」。
|
|
129
|
+
`sync` 选项:`--apply` 安全增量(默认模式:`AGENTS.md` 定制跳过、默认技能不删范围外 productivity,旧镜像清理保留项目自定义;`--all` 全量),`--force` 硬盖(默认模式:`AGENTS.md` 备份后硬盖、默认技能与模板全量 `remove` 含范围外 productivity 删除与旧镜像清理;`--all` 全量);`--all` 展开全量;`--dest <path>` 目标目录;`--upstream <url>` 上游地址;`--ref <ref>` 上游分支;`--json` JSON 输出;默认无参等价 `check` 仅对比不写盘(默认范围,`--all` 全量),`--json` 可解析,有差异 `exit 1`。
|
|
130
|
+
`check` 选项:`--json`、`--all`(默认范围:engineering + 独有所需)、`--upstream <url>`、`--ref <ref>`(等价 `sync` 默认 `check`)。
|
|
131
131
|
|
|
132
132
|
`install` 选项:
|
|
133
133
|
|
|
134
134
|
- `--dest <dir>`:复制到指定目录(覆盖工具映射)
|
|
135
135
|
- `--tools <t1,t2>`:指定工具,项目级已统一 `codex/pi/opencode/claude → .agents/skills`(共享技能单一源,`.pi/skills`/`.opencode/skills` 仅用于项目自定义)
|
|
136
136
|
- `--global`:安装到全局目录(`~/.codex/skills`、`~/.pi/agent/skills`、`~/.config/opencode/skills`、`~/.claude/skills`);`--project` 回到项目级
|
|
137
|
-
- `--all
|
|
137
|
+
- `--all`:安装全部技能(默认编程 23,`--all` 32,交互勾选时仅列默认范围);`--force`:覆盖已存在的技能
|
|
138
138
|
|
|
139
139
|
|
|
140
140
|
### 上游同步(自动更新)
|
|
@@ -153,7 +153,7 @@ node scripts/sync-upstream.js --check # 等价底层脚本(CLI s
|
|
|
153
153
|
node scripts/sync-upstream.js --apply --dry-run
|
|
154
154
|
```
|
|
155
155
|
|
|
156
|
-
实现细节:`scripts/sync-upstream.js` 为单一事实源(CLI 与 Actions 共用),以 `config/proprietary.json` 为独有白名单 + `config/engineering.json`
|
|
156
|
+
实现细节:`scripts/sync-upstream.js` 为单一事实源(CLI 与 Actions 共用),以 `config/proprietary.json` 为独有白名单 + `config/engineering.json` 为编程白名单 + `config/required.json` 为独有所需白名单(当前仅 grilling:grill-to-spec 经 grill-with-docs 所需,属 productivity 但默认同步;其余 productivity 默认不装,`--all` 展开),上游通过 `git clone --depth 1 https://github.com/mattpocock/skills.git` 获取,比对 `SKILL.md` 的 sha256,自动处理新增/更新/重命名/删除;Actions 提 PR 后需人工合入,合入后按“发布”节打 `v*` 标签即发布(自动 patch 发版可在后续扩展为 PR 合入后自动 bump)。
|
|
157
157
|
上游重命名映射:`RENAMES = { "writing-great-skills": "writing-for-agents" }`,Actions/CLI 均会删除旧目录并复制新目录。
|
|
158
158
|
## 发布
|
|
159
159
|
|
package/bin/cli.js
CHANGED
|
@@ -10,6 +10,7 @@ const TEMPLATE_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), '..
|
|
|
10
10
|
const PROPRIETARY_SKILLS = new Set(['ci-guard', 'tdd-implement', 'grill-to-spec', 'diagnose-fix', 'commit-check', 'scaffold-functional-test']);
|
|
11
11
|
const PROPRIETARY_DEFAULT = new Set(['tdd-implement', 'diagnose-fix', 'commit-check', 'grill-to-spec']); // 默认仅装核心 3,--all 才装全部 6
|
|
12
12
|
const ENGINEERING_PATH = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'config', 'engineering.json');
|
|
13
|
+
const REQUIRED_PATH = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'config', 'required.json');
|
|
13
14
|
let ENGINEERING_SKILLS = null;
|
|
14
15
|
async function loadEngineeringSkills() {
|
|
15
16
|
if (ENGINEERING_SKILLS) return ENGINEERING_SKILLS;
|
|
@@ -21,8 +22,19 @@ async function loadEngineeringSkills() {
|
|
|
21
22
|
}
|
|
22
23
|
return ENGINEERING_SKILLS;
|
|
23
24
|
}
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
let REQUIRED_SKILLS = null;
|
|
26
|
+
async function loadRequiredSkills() {
|
|
27
|
+
if (REQUIRED_SKILLS) return REQUIRED_SKILLS;
|
|
28
|
+
try {
|
|
29
|
+
const raw = await readFile(REQUIRED_PATH, 'utf8');
|
|
30
|
+
REQUIRED_SKILLS = new Set(JSON.parse(raw));
|
|
31
|
+
} catch {
|
|
32
|
+
REQUIRED_SKILLS = new Set(['grilling']);
|
|
33
|
+
}
|
|
34
|
+
return REQUIRED_SKILLS;
|
|
35
|
+
}
|
|
36
|
+
function isProgrammingSkill(name, engineering, required) {
|
|
37
|
+
return PROPRIETARY_DEFAULT.has(name) || engineering.has(name) || (required && required.has(name));
|
|
26
38
|
}
|
|
27
39
|
function isProgrammingAll(name, engineering) {
|
|
28
40
|
return PROPRIETARY_SKILLS.has(name) || engineering.has(name);
|
|
@@ -53,7 +65,7 @@ Usage:
|
|
|
53
65
|
Init options:
|
|
54
66
|
--dest <path> Target directory (default: current directory)
|
|
55
67
|
--force Overwrite existing files
|
|
56
|
-
--all Include non-programming skills (productivity) and optional proprietary; default only core programming (engineering 18 + default proprietary 4 →
|
|
68
|
+
--all Include non-programming skills (productivity) and optional proprietary; default only core programming (engineering 18 + required 1 + default proprietary 4 → 23)
|
|
57
69
|
--help, -h Show this help
|
|
58
70
|
|
|
59
71
|
提示:matt-skills --help 查看全量
|
|
@@ -65,13 +77,13 @@ Usage:
|
|
|
65
77
|
matt-skills sync [--all|--force|--dry-run] [--dest <path>]
|
|
66
78
|
|
|
67
79
|
Sync options:
|
|
68
|
-
--all
|
|
80
|
+
--all 范围:含非编程与可选独有(默认编程 23:engineering 18 + 独有所需 1 + 核心独有 4)
|
|
69
81
|
--force 力度:硬盖(备份 .bak + 删多余,全量 add/update/remove)
|
|
70
82
|
--dry-run 预演:只比对不写盘
|
|
71
83
|
--dest <path> Target directory (default: current directory)
|
|
72
84
|
--help, -h Show this help
|
|
73
85
|
|
|
74
|
-
说明:默认不带参即安全增量同步默认技能(
|
|
86
|
+
说明:默认不带参即安全增量同步默认技能(23);--all 与 --force 互斥。
|
|
75
87
|
|
|
76
88
|
提示:matt-skills --help 查看全量
|
|
77
89
|
`;
|
|
@@ -82,7 +94,7 @@ Usage:
|
|
|
82
94
|
matt-skills list [--all] [--json]
|
|
83
95
|
|
|
84
96
|
List options:
|
|
85
|
-
--all List all skills (default only core programming
|
|
97
|
+
--all List all skills (default only core programming 23)
|
|
86
98
|
--json Output as JSON
|
|
87
99
|
--help, -h Show this help
|
|
88
100
|
|
|
@@ -95,7 +107,7 @@ Usage:
|
|
|
95
107
|
matt-skills check [--all] [--json] [--upstream <url>] [--ref <ref>]
|
|
96
108
|
|
|
97
109
|
Check options:
|
|
98
|
-
--all Include non-programming and optional proprietary; default only core programming (
|
|
110
|
+
--all Include non-programming and optional proprietary; default only core programming (23: engineering 18 + required 1 + default proprietary 4)
|
|
99
111
|
--json Output as JSON
|
|
100
112
|
--upstream <url> Upstream repo URL (default: https://github.com/mattpocock/skills.git)
|
|
101
113
|
--ref <ref> Upstream ref (default: HEAD)
|
|
@@ -111,7 +123,7 @@ Usage:
|
|
|
111
123
|
|
|
112
124
|
Install options:
|
|
113
125
|
--tools <a,b> Install for the given tools (codex, pi, opencode, claude); skips tool selection — 共享技能统一指向 .agents/skills,.pi/skills/.opencode/skills 仅用于项目自定义
|
|
114
|
-
--all Install all skills (default only core programming
|
|
126
|
+
--all Install all skills (default only core programming 23); skips skill selection
|
|
115
127
|
--force Overwrite existing skills
|
|
116
128
|
--global Install to the user's global skill directories
|
|
117
129
|
--project Install to project skill directories (default)
|
|
@@ -143,12 +155,14 @@ async function listSkills({ onlyProgramming = false } = {}) {
|
|
|
143
155
|
const entries = await readdir(SKILLS_DIR, { withFileTypes: true });
|
|
144
156
|
const skills = [];
|
|
145
157
|
let engineering = null;
|
|
158
|
+
let required = null;
|
|
146
159
|
if (onlyProgramming) engineering = await loadEngineeringSkills();
|
|
160
|
+
if (onlyProgramming) required = await loadRequiredSkills();
|
|
147
161
|
for (const entry of entries) {
|
|
148
162
|
if (!entry.isDirectory()) continue;
|
|
149
163
|
if (entry.name.endsWith('.bak')) continue;
|
|
150
164
|
if (entry.name === 'skill-creator') continue;
|
|
151
|
-
if (onlyProgramming && !isProgrammingSkill(entry.name, engineering)) continue;
|
|
165
|
+
if (onlyProgramming && !isProgrammingSkill(entry.name, engineering, required)) continue;
|
|
152
166
|
let content;
|
|
153
167
|
try {
|
|
154
168
|
content = await readFile(path.join(SKILLS_DIR, entry.name, 'SKILL.md'), 'utf8');
|
|
@@ -225,8 +239,9 @@ async function promptSkills(skills) {
|
|
|
225
239
|
async function installCommand({ dest, all, force, tools, global }) {
|
|
226
240
|
const onlyProgramming = !all;
|
|
227
241
|
const engineering = onlyProgramming ? await loadEngineeringSkills() : null;
|
|
242
|
+
const required = onlyProgramming ? await loadRequiredSkills() : null;
|
|
228
243
|
const skillsAll = await listSkills({ onlyProgramming: false });
|
|
229
|
-
const skills = onlyProgramming ? skillsAll.filter(s => isProgrammingSkill(s.name, engineering)) : skillsAll;
|
|
244
|
+
const skills = onlyProgramming ? skillsAll.filter(s => isProgrammingSkill(s.name, engineering, required)) : skillsAll;
|
|
230
245
|
let targets;
|
|
231
246
|
if (dest) {
|
|
232
247
|
targets = [{ tool: null, dir: path.resolve(process.cwd(), dest) }];
|
|
@@ -290,16 +305,17 @@ async function initCommand({ dest, force, all }) {
|
|
|
290
305
|
await cp(TEMPLATE_DIR, target, { recursive: true, force: true });
|
|
291
306
|
process.stdout.write('模板:已复制(AGENTS.md、.agents/skills、.opencode/、.pi/)\n');
|
|
292
307
|
}
|
|
293
|
-
//
|
|
308
|
+
// 默认范围(engineering + 独有所需 + 默认独有),--all 才保留其余 productivity
|
|
294
309
|
if (onlyProgramming && path.resolve(target) !== path.resolve(path.join(path.dirname(fileURLToPath(import.meta.url)), '..'))) {
|
|
295
310
|
const engineering = await loadEngineeringSkills();
|
|
311
|
+
const required = await loadRequiredSkills();
|
|
296
312
|
const skillsDirFilter = path.join(target, '.agents', 'skills');
|
|
297
313
|
try {
|
|
298
314
|
const entries = await readdir(skillsDirFilter, { withFileTypes: true });
|
|
299
315
|
for (const e of entries) {
|
|
300
316
|
if (!e.isDirectory()) continue;
|
|
301
317
|
if (e.name.endsWith('.bak') || e.name === '.git' || e.name === 'skill-creator') continue;
|
|
302
|
-
if (!isProgrammingSkill(e.name, engineering)) {
|
|
318
|
+
if (!isProgrammingSkill(e.name, engineering, required)) {
|
|
303
319
|
await rm(path.join(skillsDirFilter, e.name), { recursive: true, force: true });
|
|
304
320
|
}
|
|
305
321
|
}
|
|
@@ -315,9 +331,10 @@ async function initCommand({ dest, force, all }) {
|
|
|
315
331
|
} catch {}
|
|
316
332
|
const allSkillsFull = await listSkills({ onlyProgramming: false });
|
|
317
333
|
const engineeringForStats = await loadEngineeringSkills();
|
|
318
|
-
const
|
|
334
|
+
const requiredForStats = await loadRequiredSkills();
|
|
335
|
+
const programmingCount = allSkillsFull.filter(s => isProgrammingSkill(s.name, engineeringForStats, requiredForStats)).length;
|
|
319
336
|
const upstreamFull = allSkillsFull.filter((s) => !PROPRIETARY_SKILLS.has(s.name)).length;
|
|
320
|
-
const upstreamProg = allSkillsFull.filter((s) => !PROPRIETARY_SKILLS.has(s.name) && engineeringForStats.has(s.name)).length;
|
|
337
|
+
const upstreamProg = allSkillsFull.filter((s) => !PROPRIETARY_SKILLS.has(s.name) && (engineeringForStats.has(s.name) || requiredForStats.has(s.name))).length;
|
|
321
338
|
const displayTotal = onlyProgramming ? programmingCount : allSkillsFull.length;
|
|
322
339
|
const displayUpstream = onlyProgramming ? upstreamProg : upstreamFull;
|
|
323
340
|
if (path.resolve(skillsDir) === path.resolve(SKILLS_DIR)) {
|
|
@@ -339,7 +356,7 @@ async function syncCommand({ dest, force, all, dryRun, json, upstreamUrl, ref })
|
|
|
339
356
|
if (json) {
|
|
340
357
|
process.stdout.write(JSON.stringify({ head: cmp.head, counts: cmp.counts, result: cmp.result, onlyProgramming }, null, 2) + '\n');
|
|
341
358
|
} else {
|
|
342
|
-
const modeHint = onlyProgramming ? '
|
|
359
|
+
const modeHint = onlyProgramming ? '(默认:engineering + 独有所需)' : '(全量)';
|
|
343
360
|
const lines = [];
|
|
344
361
|
lines.push(`上游 HEAD: ${cmp.head}`);
|
|
345
362
|
lines.push(`本地非独有: ${cmp.counts.local} 上游: ${cmp.counts.upstream} ${modeHint}`);
|
|
@@ -364,13 +381,13 @@ async function syncCommand({ dest, force, all, dryRun, json, upstreamUrl, ref })
|
|
|
364
381
|
|
|
365
382
|
const target = dest ? path.resolve(process.cwd(), dest) : process.cwd();
|
|
366
383
|
const marker = path.join(target, 'AGENTS.md');
|
|
367
|
-
//
|
|
384
|
+
// 模板同步:默认模式下过滤 skills,仅同步默认子集
|
|
368
385
|
async function copyTemplateFiltered() {
|
|
369
386
|
if (!onlyProgramming) {
|
|
370
387
|
await cp(TEMPLATE_DIR, target, { recursive: true, force: true });
|
|
371
388
|
return;
|
|
372
389
|
}
|
|
373
|
-
//
|
|
390
|
+
// 默认范围:分别复制非 skills 部分,skills 由后续 allSkills 循环处理
|
|
374
391
|
await cp(path.join(TEMPLATE_DIR, 'AGENTS.md'), path.join(target, 'AGENTS.md'), { force: true });
|
|
375
392
|
await cp(path.join(TEMPLATE_DIR, '.opencode'), path.join(target, '.opencode'), { recursive: true, force: true });
|
|
376
393
|
await cp(path.join(TEMPLATE_DIR, '.pi'), path.join(target, '.pi'), { recursive: true, force: true });
|
|
@@ -429,13 +446,14 @@ async function syncCommand({ dest, force, all, dryRun, json, upstreamUrl, ref })
|
|
|
429
446
|
process.stdout.write('模板:已同步(AGENTS.md、.agents/skills、.opencode/、.pi/)\n');
|
|
430
447
|
}
|
|
431
448
|
}
|
|
432
|
-
//
|
|
449
|
+
// 技能同步:按默认范围过滤(engineering + 独有所需 + 默认独有,--all 全量)
|
|
433
450
|
const entries = await readdir(SKILLS_DIR, { withFileTypes: true });
|
|
434
451
|
const allNames = entries.filter((e) => e.isDirectory() && !e.name.endsWith('.bak') && e.name !== 'skill-creator' && e.name !== '.git').map((e) => e.name);
|
|
435
452
|
let allSkills = allNames.sort();
|
|
436
453
|
if (onlyProgramming) {
|
|
437
454
|
const engineering = await loadEngineeringSkills();
|
|
438
|
-
|
|
455
|
+
const required = await loadRequiredSkills();
|
|
456
|
+
allSkills = allSkills.filter(n => isProgrammingSkill(n, engineering, required));
|
|
439
457
|
}
|
|
440
458
|
const skillsDir = path.join(target, '.agents', 'skills');
|
|
441
459
|
await mkdir(skillsDir, { recursive: true });
|
|
@@ -458,7 +476,7 @@ async function syncCommand({ dest, force, all, dryRun, json, upstreamUrl, ref })
|
|
|
458
476
|
installed++;
|
|
459
477
|
}
|
|
460
478
|
}
|
|
461
|
-
// --force
|
|
479
|
+
// --force 时删除多余;默认模式下多余指不在默认集合中的,--all 模式下多余指不在全量中的
|
|
462
480
|
// 默认安全增量保留多余(不删除),符合“默认保留、--force 删除”
|
|
463
481
|
if (force) {
|
|
464
482
|
let localEntries = [];
|
|
@@ -595,16 +613,18 @@ async function checkCommand(args) {
|
|
|
595
613
|
const json = args.includes('--json');
|
|
596
614
|
const onlyProgramming = !args.includes('--all');
|
|
597
615
|
const upstreamIdx = args.indexOf('--upstream');
|
|
598
|
-
const
|
|
616
|
+
const upstreamEq = args.find((x) => x.startsWith('--upstream='));
|
|
617
|
+
const upstreamUrl = upstreamIdx !== -1 ? args[upstreamIdx + 1] : (upstreamEq ? upstreamEq.slice('--upstream='.length) : undefined);
|
|
599
618
|
const refIdx = args.indexOf('--ref');
|
|
600
|
-
const
|
|
619
|
+
const refEq = args.find((x) => x.startsWith('--ref='));
|
|
620
|
+
const ref = refIdx !== -1 ? args[refIdx + 1] : (refEq ? refEq.slice('--ref='.length) : undefined);
|
|
601
621
|
const cmp = await compare({ upstreamUrl, ref, onlyProgramming });
|
|
602
622
|
if (json) {
|
|
603
623
|
process.stdout.write(JSON.stringify({ head: cmp.head, counts: cmp.counts, result: cmp.result, onlyProgramming }, null, 2) + '\n');
|
|
604
624
|
} else {
|
|
605
625
|
const lines = [];
|
|
606
626
|
lines.push(`上游 HEAD: ${cmp.head}`);
|
|
607
|
-
const modeHint = onlyProgramming ? '
|
|
627
|
+
const modeHint = onlyProgramming ? '(默认:engineering + 独有所需)' : '(全量)';
|
|
608
628
|
lines.push(`本地非独有: ${cmp.counts.local} 上游: ${cmp.counts.upstream} ${modeHint}`);
|
|
609
629
|
lines.push('');
|
|
610
630
|
const totalDiff = cmp.result.added.length + cmp.result.updated.length + cmp.result.removed.length + cmp.result.renamed.length;
|
|
@@ -695,10 +715,11 @@ async function main() {
|
|
|
695
715
|
}
|
|
696
716
|
if (command === 'check') {
|
|
697
717
|
// check strict: allow --all/--json/--upstream/--ref/--help
|
|
698
|
-
for (
|
|
718
|
+
for (let i = 0; i < rest.length; i++) {
|
|
719
|
+
const a = rest[i];
|
|
699
720
|
if (a === '--all' || a === '--json' || a === '--help' || a === '-h') continue;
|
|
700
|
-
if (a === '--upstream' || a
|
|
701
|
-
if (a
|
|
721
|
+
if (a === '--upstream' || a === '--ref') { i++; continue; }
|
|
722
|
+
if (a.startsWith('--upstream=') || a.startsWith('--ref=')) continue;
|
|
702
723
|
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
724
|
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
725
|
}
|
package/package.json
CHANGED
package/scripts/sync-upstream.js
CHANGED
|
@@ -10,6 +10,7 @@ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
|
10
10
|
const LOCAL_SKILLS_DIR = path.join(ROOT, '.agents', 'skills');
|
|
11
11
|
const PROPRIETARY_PATH = path.join(ROOT, 'config', 'proprietary.json');
|
|
12
12
|
const ENGINEERING_PATH = path.join(ROOT, 'config', 'engineering.json');
|
|
13
|
+
const REQUIRED_PATH = path.join(ROOT, 'config', 'required.json');
|
|
13
14
|
const UPSTREAM_URL = 'https://github.com/mattpocock/skills.git';
|
|
14
15
|
|
|
15
16
|
// 重命名映射:上游已重命名,本地旧名需迁移
|
|
@@ -34,6 +35,14 @@ async function loadEngineering() {
|
|
|
34
35
|
return new Set(['ask-matt','code-review','codebase-design','diagnosing-bugs','domain-modeling','grill-with-docs','implement','improve-codebase-architecture','prototype','research','resolving-merge-conflicts','setup-matt-pocock-skills','tdd','to-spec','to-tickets','triage','wayfinder','wizard']);
|
|
35
36
|
}
|
|
36
37
|
}
|
|
38
|
+
async function loadRequired() {
|
|
39
|
+
try {
|
|
40
|
+
const raw = await readFile(REQUIRED_PATH, 'utf8');
|
|
41
|
+
return new Set(JSON.parse(raw));
|
|
42
|
+
} catch {
|
|
43
|
+
return new Set(['grilling']);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
37
46
|
|
|
38
47
|
async function hashFile(filePath) {
|
|
39
48
|
const buf = await readFile(filePath);
|
|
@@ -120,12 +129,14 @@ export async function compare({ upstreamUrl, tmpDir, ref, onlyProgramming = true
|
|
|
120
129
|
const upstreamRoot = fetched.dest;
|
|
121
130
|
const upstreamMapFull = await collectUpstreamSkills(upstreamRoot);
|
|
122
131
|
const localMapFull = await collectLocalSkills(proprietary);
|
|
123
|
-
//
|
|
132
|
+
// 默认范围:engineering 桶(编程)+ 独有所需(config/required.json,如 grill-to-spec 经 grill-with-docs 所需的 grilling);--all 则含全部 productivity
|
|
133
|
+
const required = await loadRequired();
|
|
124
134
|
const upstreamMap = onlyProgramming
|
|
125
|
-
? new Map([...upstreamMapFull.entries()].filter(([, v]) => v.bucket === 'engineering'))
|
|
135
|
+
? new Map([...upstreamMapFull.entries()].filter(([name, v]) => v.bucket === 'engineering' || required.has(name)))
|
|
126
136
|
: upstreamMapFull;
|
|
127
137
|
const localMap = localMapFull;
|
|
128
138
|
const isEngineering = (name) => engineering.has(name);
|
|
139
|
+
const isDefault = (name) => engineering.has(name) || required.has(name);
|
|
129
140
|
|
|
130
141
|
const added = [];
|
|
131
142
|
const updated = [];
|
|
@@ -166,14 +177,14 @@ export async function compare({ upstreamUrl, tmpDir, ref, onlyProgramming = true
|
|
|
166
177
|
for (const name of localMap.keys()) {
|
|
167
178
|
if (renamedFrom.has(name)) continue;
|
|
168
179
|
if (upstreamMap.has(name) || renamedTo.has(name)) continue;
|
|
169
|
-
//
|
|
170
|
-
if (onlyProgramming && !
|
|
180
|
+
// 默认模式下仅报告默认范围(engineering + 独有所需)内本地技能的删除;其余 productivity/instance-test 等跳过
|
|
181
|
+
if (onlyProgramming && !isDefault(name)) continue;
|
|
171
182
|
// 全量模式下所有本地非独有且不在上游的都视为 removed
|
|
172
183
|
removed.push(name);
|
|
173
184
|
}
|
|
174
185
|
|
|
175
186
|
const localCount = onlyProgramming
|
|
176
|
-
? [...localMap.keys()].filter(
|
|
187
|
+
? [...localMap.keys()].filter((n) => isDefault(n)).length
|
|
177
188
|
: localMap.size;
|
|
178
189
|
return {
|
|
179
190
|
head: fetched.head,
|
|
@@ -184,6 +195,7 @@ export async function compare({ upstreamUrl, tmpDir, ref, onlyProgramming = true
|
|
|
184
195
|
localMapFull,
|
|
185
196
|
proprietary: [...proprietary],
|
|
186
197
|
engineering: [...engineering],
|
|
198
|
+
required: [...required],
|
|
187
199
|
onlyProgramming,
|
|
188
200
|
result: { added: added.sort(), updated: updated.sort(), same: same.sort(), removed: removed.sort(), renamed },
|
|
189
201
|
counts: { upstream: upstreamMap.size, local: localCount, upstreamFull: upstreamMapFull.size, localFull: localMapFull.size },
|
|
@@ -247,7 +259,7 @@ function formatTable(cmp) {
|
|
|
247
259
|
const { result, counts, head, onlyProgramming } = cmp;
|
|
248
260
|
const lines = [];
|
|
249
261
|
lines.push(`上游 HEAD: ${head}`);
|
|
250
|
-
const modeHint = onlyProgramming ? '
|
|
262
|
+
const modeHint = onlyProgramming ? '(默认,engineering + 独有所需)' : '(全量,含 productivity)';
|
|
251
263
|
lines.push(`本地非独有: ${counts.local} 上游: ${counts.upstream} ${modeHint}`);
|
|
252
264
|
lines.push('');
|
|
253
265
|
const totalDiff = result.added.length + result.updated.length + result.removed.length + result.renamed.length;
|
|
@@ -291,7 +303,7 @@ Usage:
|
|
|
291
303
|
Options:
|
|
292
304
|
--check 只对比,不改动文件(默认)
|
|
293
305
|
--apply 应用同步(覆盖 .agents/skills 非独有技能)
|
|
294
|
-
--all
|
|
306
|
+
--all 包含全部非编程技能(productivity);默认同步编程相关(engineering)+ 独有所需(config/required.json)
|
|
295
307
|
--dry-run 演练模式,不写文件
|
|
296
308
|
--json 以 JSON 输出结果
|
|
297
309
|
--upstream 上游仓库 URL(默认 https://github.com/mattpocock/skills.git)
|
|
@@ -16,7 +16,7 @@ description: "TDD seam red-green loop: use when the user provides a spec/ticket
|
|
|
16
16
|
|
|
17
17
|
## 多 issue 编排(按依赖分层并行)
|
|
18
18
|
|
|
19
|
-
触发见 [orchestration.md](references/orchestration.md);`.scratch/<feature>/issues/` 下多文件且部分含 `Blocked by` 时触发,主过程 A0 依赖图 → A1 Kahn 分层 L1入度0→L2→Ln → A2
|
|
19
|
+
触发见 [orchestration.md](references/orchestration.md);`.scratch/<feature>/issues/` 下多文件且部分含 `Blocked by` 时触发,主过程 A0 依赖图 → A1 Kahn 分层 L1入度0→L2→Ln → A2 分层调度(逐个 subagent single 派发、共享 working tree,禁止主会话直做;`N>1` 时串行错峰派发以减同文件竞写,文件冲突由后完成者 rebase 解决) → A3 子代理契约 → A4 全量收敛 → A5 回退与冲突(最小重派:按失败点精确回退、精确定位单 issue 单 seam,全量保留为详规真相源)。必须先编排子代理计划(输出依赖图/DAG 与 Kahn 分层 `L1..Ln` 并确认)后才派发,禁止跳过计划直接派发导致重复调度;编排模式下所有 issue 的 `①→⑦` 必须经子代理执行、主会话仅编排与验收,禁止任何“为省开销/效率”在主会话直做;层收敛 4 项(验收/相关测试/`git status`仅删`[DEBUG-...]`/ `BASE_HEAD`历史校验 `git merge-base --is-ancestor`)与子代理回执卡片(≤30行、缺字段视为不通过)、打回重派、rebase 冲突处理等可执行约束全量见 orchestration.md。
|
|
20
20
|
|
|
21
21
|
## Steps
|
|
22
22
|
|
|
@@ -38,27 +38,23 @@ Ln = 最后一层
|
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
每层内节点互无依赖,可并行;层间有依赖,必须串行。分层结果在编排开始前一次性展示给用户确认(合规交互点),确认后才派发。
|
|
41
|
-
|
|
41
|
+
若两 issue 在阶段②已声明预期改动同一文件,编排器在 A0 后提示建议追加 `Blocked by` 使其串行(轻提示,不强制);主要仍靠 A2 串行错峰自然错峰。
|
|
42
42
|
### A2. 分层调度
|
|
43
43
|
|
|
44
44
|
```
|
|
45
45
|
for each 层 Li in L1..Ln:
|
|
46
|
-
|
|
47
|
-
并行派发:为 Li 中每个 issue 启动一个子代理(single 模式,cwd 为对应 worktree,禁止 parallel tasks 数组)
|
|
46
|
+
并行派发:为 Li 中每个 issue 启动一个子代理(single 模式,共享 working tree,禁止 parallel tasks 数组;N>1 时串行错峰派发)
|
|
48
47
|
等待:阻塞直到 Li 全部子代理返回回执卡片
|
|
49
|
-
验收:编排器按 A3 验收清单逐 issue 验收(只认回执卡片的关键信息 +
|
|
50
|
-
|
|
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 保留至重派时以失败分支为增量基础重建
|
|
48
|
+
验收:编排器按 A3 验收清单逐 issue 验收(只认回执卡片的关键信息 + 抽检验证在最新 HEAD 上执行,不消费全量日志)
|
|
49
|
+
层收敛验证:验收全通过进入全量验证(完成条件 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 重派该 issue
|
|
53
50
|
全部层层收敛通过后进入 A4 全量收敛
|
|
54
51
|
```
|
|
55
52
|
|
|
56
53
|
- **派发纪律**:与阶段⑤双轴审查一致——逐个 `subagent` 派发,禁止 `parallel tasks` 数组(同因:中文报告截断)。
|
|
57
|
-
- **等待语义**:层内任一子代理失败不取消同层其他子代理;待层内全部返回后统一按 A5
|
|
58
|
-
-
|
|
54
|
+
- **等待语义**:层内任一子代理失败不取消同层其他子代理;待层内全部返回后统一按 A5 最小重派处理。
|
|
55
|
+
- **冲突判定(Q1/Q2)**:同文件即冲突,以 `HEAD` 已移动(`git log` 已含先完成者 `#NN`)为准判定慢者前后不一致;不在工作区瞬时覆盖时判定。
|
|
59
56
|
- **回合连续性**:编排器在层间不结束回合——一层收敛后立即派发下一层,直到全部层完成或外部阻塞;预告下一层后立即执行。
|
|
60
|
-
- **Git 历史保护(正向:仅追加;护栏:禁改写)**:编排器在分层调度前记录 `BASE_HEAD=$(git rev-parse HEAD)
|
|
61
|
-
|
|
57
|
+
- **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` 等(需显式确认)。
|
|
62
58
|
### A3. 子代理契约(单 issue 单代理)
|
|
63
59
|
|
|
64
60
|
每个子代理是一个**完整的 tdd-implement 单 issue 执行单元**,输入与产出严格界定:
|
|
@@ -69,8 +65,7 @@ for each 层 Li in L1..Ln:
|
|
|
69
65
|
- `spec.md`(feature 级共享 spec,若无则以该 issue 正文为准)
|
|
70
66
|
- 分配的单个 `NN-<slug>.md`(唯一 issue 输入)
|
|
71
67
|
- `CONTEXT.md` + `docs/adr/`(术语与决策一致性)
|
|
72
|
-
|
|
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。
|
|
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) 为唯一事实源,不在子代理内重写。
|
|
74
69
|
- **产出**:
|
|
75
70
|
- 独立 commit(message 含 issue 编号,如 `feat(<feature>): <issue title> (#NN)`)
|
|
76
71
|
- 该 issue 文件 `Status: resolved` + 底部 `## 实施总结`
|
|
@@ -107,7 +102,7 @@ for each 层 Li in L1..Ln:
|
|
|
107
102
|
|
|
108
103
|
任一项不通过 → 打回重派该子代理(仅该 issue),层内其他已通过不受影响;验收通过才计入层收敛。验收结论随层收敛一并输出。
|
|
109
104
|
|
|
110
|
-
子代理内部的回合连续性、任务分解、Todo 规定、Git 历史保护与单线模式完全一致(见 [stages.md 阶段③ 3e/3f/3h](stages.md#阶段-③tdd-开发循环) 与 Git 安全前置)。子代理同样在入口记录 `BASE_HEAD
|
|
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` 等。
|
|
111
106
|
|
|
112
107
|
### A4. 全量收敛
|
|
113
108
|
|
|
@@ -121,9 +116,9 @@ for each 层 Li in L1..Ln:
|
|
|
121
116
|
### A5. 回退与冲突
|
|
122
117
|
|
|
123
118
|
- **子代理内回退(最小单元)**:按 [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
|
|
119
|
+
- **层收敛失败(最小重派)**:层内任一子代理未达到 `resolved`(含验收 5 项、相关测试、卫生、历史校验任一不过)→ 该 issue 保持原 `Status`,编排器在层等待结束后报告失败清单,不自动进入下一层;待修复后仅重派失败节点,同层其他已通过不受影响。层原子语义保持:`Li` 未全 `resolved` 不派 `L_{i+1}`。
|
|
125
120
|
- **全量收敛失败(精确定位)**:A4 全量测试失败 → 以测试文件路径/报错栈精确定位到单 issue 单 seam,回到其所在层仅重派该 issue 的失败 seam + 相关测试,全量由编排器在重派后再次 A4 统一验证;无法精确定位时退化到层级重派,不重跑无关联 issue。
|
|
126
|
-
-
|
|
121
|
+
- **文件冲突(Q1-Q4)**:同文件即冲突(Q1 慢者因 `HEAD` 已移动致前后不一致);慢者完成当前 seam 的 `红→绿→typecheck` 后再以新 HEAD 为基线 rebase,仅重做该冲突文件关联的 seam(其余已绿复用,Q3/Q4 最小化);后完成者 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` 合并结果(`HEAD` 已移动)为准,编排器不做静态文件监听预判。
|
|
127
122
|
- **环依赖**:A0 检测到环即报错终止,不派发任何子代理。
|
|
128
123
|
|
|
129
124
|
### 出口条件
|
|
@@ -134,8 +129,8 @@ for each 层 Li in L1..Ln:
|
|
|
134
129
|
|
|
135
130
|
### 边界
|
|
136
131
|
|
|
137
|
-
- 单 issue / 单 spec
|
|
138
|
-
- 子代理不跨 issue
|
|
132
|
+
- 单 issue / 单 spec 不走本文件;但一旦进入编排模式(多 issue 且含 Blocked by),无论 N==1 还是 N>1,所有 issue 的 ①→⑦ 必须经子代理 single 派发完成,禁止主会话直做(编排器仅编排、验收、归集、清理)
|
|
133
|
+
- 子代理不跨 issue 改动;编排器不替子代理写实现代码;主会话不直接写业务代码/测试/文档(仅做编排与验收)
|
|
139
134
|
- 汇总总结只在对话输出,不落盘额外汇总文件
|
|
140
135
|
- 必须先编排子代理计划(输出依赖图/DAG/Kahn 分层 `L1..Ln` 并确认)后才派子代理,禁止跳过计划直接派发导致重复调度
|
|
141
136
|
- TDD 语义以 [tdd 技能](.agents/skills/tdd/SKILL.md) 为唯一事实源,不在本文件重写
|
package/template/AGENTS.md
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
1. 读 `CONTEXT.md`(术语)——没有则跳过
|
|
17
17
|
2. 按行为路由表行动;未命中用 ask-matt 或直接澄清
|
|
18
|
-
3.
|
|
18
|
+
3. 探索代码库:直接使用 `codegraph explore`(`codegrafh CLI`);若无 `.codegraph/` 索引先执行 `codegraph init` 初始化,再 `explore`
|
|
19
19
|
|
|
20
20
|
## 执行原则
|
|
21
21
|
|
|
@@ -26,21 +26,21 @@
|
|
|
26
26
|
|
|
27
27
|
以全局 AGENTS.md 安全铁律为准,本仓库无附加差异。
|
|
28
28
|
|
|
29
|
-
## 行为路由(默认
|
|
29
|
+
## 行为路由(默认 23 自动发现,`--all` 展开至 32)
|
|
30
30
|
|
|
31
31
|
命中即行动,回复中简短声明所用技能与原因。
|
|
32
32
|
|
|
33
|
-
- 探索/定位/理解代码库 →
|
|
33
|
+
- 探索/定位/理解代码库 → 直接使用 `codegraph explore`(`codegrafh CLI`);若无 `.codegraph/` 索引先 `codegraph init` 初始化,再 `explore`(Q1 硬判定,Q2 意图文+文件锚点,Q3 仅完整源码免读,Q4 跨仓才传 projectPath,Q5 研调链用 research 另行触发)
|
|
34
34
|
- 后台调研 → research;原型验证 → prototype
|
|
35
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,三项)
|
|
39
39
|
- 分诊 → triage;架构扫描 → improve-codebase-architecture;综合 spec → to-spec;拆票 → to-tickets
|
|
40
|
-
- 可选(需 `--all` 才发现):grill-me /
|
|
40
|
+
- 可选(需 `--all` 才发现):grill-me / handoff / teach / to-questionnaire / wait-what / writing-for-agents / ci-guard / scaffold-functional-test / instance-test
|
|
41
41
|
- 兜底 → ask-matt;模板维护 → README.md
|
|
42
42
|
|
|
43
|
-
显式触发(须用户 `/` 发起,默认
|
|
43
|
+
显式触发(须用户 `/` 发起,默认 23 中仅 grill-to-spec/wayfinder/to-spec/to-tickets/triage/improve-codebase-architecture 为默认;其余 teach/handoff/writing-for-agents 需 `--all`):grill-to-spec、wayfinder、to-spec、to-tickets、triage、improve-codebase-architecture、teach、handoff、writing-for-agents
|
|
44
44
|
|
|
45
45
|
## 分文件
|
|
46
46
|
|
|
@@ -49,8 +49,10 @@
|
|
|
49
49
|
|
|
50
50
|
## CodeGraph
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
理解/定位代码**必须**使用 `codegrafh CLI`,直接优于 grep/find/读文件——一次调用拿到相关符号逐字源码与调用路径:
|
|
53
53
|
|
|
54
|
-
- **CLI**:`codegraph explore "<符号名或问题>"` 一次回答大部分代码问题——相关符号的逐字源码 + 调用路径(含 grep 追不上的动态分派跳转)。在 query
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
- **CLI**:`codegraph explore "<符号名或问题>"` 一次回答大部分代码问题——相关符号的逐字源码 + 调用路径(含 grep 追不上的动态分派跳转)。在 query 中指名文件/符号即可读取其带行号的当前源码,默认 `maxFiles: 12` 覆盖跨 5-8 文件调用链。
|
|
55
|
+
- **初始化**:若根目录无 `.codegraph/`,先执行 `codegraph init` 初始化索引,再 `explore`;已有索引直接 `explore`(硬判定,不回退 explore 子代理)。
|
|
56
|
+
- **已读等价**:返回体含完整源码块的文件视为已 `Read`,不再重复 `read`;仅返回调用路径片段时补一次带行号 `read`。
|
|
57
|
+
- **跨仓/子项目**:仅当探索第二代码库或 monorepo 子项目(根无索引但子目录有)时显式传 `projectPath`。
|
|
58
|
+
与 `research`(后台调研产出 Markdown 文件)分工:`codegraph explore` 为代码定位唯一首选,`research` 仅用于需产出调研文档的后台任务。
|