@heihei0299/matt-skills 1.2.0 → 1.3.0
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 +3 -1
- package/.agents/skills/tdd-implement/SKILL.md +26 -121
- package/.agents/skills/tdd-implement/references/orchestration.md +136 -0
- package/.agents/skills/tdd-implement/references/stages.md +105 -104
- package/config/proprietary.json +1 -0
- package/package.json +3 -1
- package/scripts/sync-upstream.js +315 -0
- package/template/.opencode/CONTEXT.md +4 -0
- package/template/.opencode/docs/agents/runtime-discipline.md +3 -2
- package/template/.opencode/docs/agents/skill-design.md +7 -2
- package/template/.opencode/skills/commit-check/SKILL.md +3 -1
- package/template/.opencode/skills/tdd-implement/SKILL.md +26 -121
- package/template/.opencode/skills/tdd-implement/references/orchestration.md +136 -0
- package/template/.opencode/skills/tdd-implement/references/stages.md +105 -104
- package/template/.pi/skills/commit-check/SKILL.md +3 -1
- package/template/.pi/skills/tdd-implement/SKILL.md +26 -121
- package/template/.pi/skills/tdd-implement/references/orchestration.md +136 -0
- package/template/.pi/skills/tdd-implement/references/stages.md +105 -104
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { cp, readdir, readFile, rm, stat, mkdir } from 'node:fs/promises';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
|
|
9
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
10
|
+
const LOCAL_SKILLS_DIR = path.join(ROOT, '.agents', 'skills');
|
|
11
|
+
const PROPRIETARY_PATH = path.join(ROOT, 'config', 'proprietary.json');
|
|
12
|
+
const UPSTREAM_URL = 'https://github.com/mattpocock/skills.git';
|
|
13
|
+
|
|
14
|
+
// 重命名映射:上游已重命名,本地旧名需迁移
|
|
15
|
+
const RENAMES = {
|
|
16
|
+
'writing-great-skills': 'writing-for-agents',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
async function loadProprietary() {
|
|
20
|
+
try {
|
|
21
|
+
const raw = await readFile(PROPRIETARY_PATH, 'utf8');
|
|
22
|
+
return new Set(JSON.parse(raw));
|
|
23
|
+
} catch {
|
|
24
|
+
return new Set(['tdd-implement', 'grill-to-spec', 'diagnose-fix', 'commit-check', 'instance-test']);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function hashFile(filePath) {
|
|
29
|
+
const buf = await readFile(filePath);
|
|
30
|
+
return createHash('sha256').update(buf).digest('hex');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function hashDir(dir) {
|
|
34
|
+
// 对 SKILL.md 做 hash,用于比对;若无 SKILL.md 则对目录内所有文件联合 hash
|
|
35
|
+
const skillMd = path.join(dir, 'SKILL.md');
|
|
36
|
+
try {
|
|
37
|
+
await stat(skillMd);
|
|
38
|
+
return await hashFile(skillMd);
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function listSkills(dir) {
|
|
45
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
46
|
+
const out = [];
|
|
47
|
+
for (const e of entries) {
|
|
48
|
+
if (!e.isDirectory()) continue;
|
|
49
|
+
if (e.name.endsWith('.bak')) continue;
|
|
50
|
+
if (e.name === '.git') continue;
|
|
51
|
+
out.push(e.name);
|
|
52
|
+
}
|
|
53
|
+
return out.sort();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function fetchUpstream({ tmpDir, upstreamUrl = UPSTREAM_URL, ref = 'HEAD' } = {}) {
|
|
57
|
+
const dest = tmpDir || path.join(os.tmpdir(), `matt-skills-upstream-${Date.now()}`);
|
|
58
|
+
await rm(dest, { recursive: true, force: true });
|
|
59
|
+
await mkdir(path.dirname(dest), { recursive: true });
|
|
60
|
+
const clone = spawnSync('git', ['clone', '--depth', '1', upstreamUrl, dest], { encoding: 'utf8' });
|
|
61
|
+
if (clone.status !== 0) {
|
|
62
|
+
throw new Error(`git clone 失败: ${clone.stderr || clone.stdout}`);
|
|
63
|
+
}
|
|
64
|
+
if (ref && ref !== 'HEAD') {
|
|
65
|
+
const co = spawnSync('git', ['-C', dest, 'checkout', ref], { encoding: 'utf8' });
|
|
66
|
+
if (co.status !== 0) throw new Error(`git checkout ${ref} 失败: ${co.stderr}`);
|
|
67
|
+
}
|
|
68
|
+
const rev = spawnSync('git', ['-C', dest, 'rev-parse', 'HEAD'], { encoding: 'utf8' });
|
|
69
|
+
const head = rev.stdout ? rev.stdout.trim() : 'unknown';
|
|
70
|
+
return { dest, head };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function collectUpstreamSkills(upstreamRoot) {
|
|
74
|
+
const map = new Map(); // name -> { dir, hash }
|
|
75
|
+
for (const bucket of ['engineering', 'productivity']) {
|
|
76
|
+
const bucketDir = path.join(upstreamRoot, 'skills', bucket);
|
|
77
|
+
let entries = [];
|
|
78
|
+
try {
|
|
79
|
+
entries = await readdir(bucketDir, { withFileTypes: true });
|
|
80
|
+
} catch {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
for (const e of entries) {
|
|
84
|
+
if (!e.isDirectory()) continue;
|
|
85
|
+
const skillDir = path.join(bucketDir, e.name);
|
|
86
|
+
const h = await hashDir(skillDir);
|
|
87
|
+
if (h) map.set(e.name, { dir: skillDir, hash: h, bucket });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return map;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function collectLocalSkills(proprietary) {
|
|
94
|
+
const map = new Map();
|
|
95
|
+
const names = await listSkills(LOCAL_SKILLS_DIR);
|
|
96
|
+
for (const name of names) {
|
|
97
|
+
if (name === 'skill-creator') continue; // 本地符号链接,不纳入上游比对
|
|
98
|
+
if (proprietary.has(name)) continue;
|
|
99
|
+
const dir = path.join(LOCAL_SKILLS_DIR, name);
|
|
100
|
+
const h = await hashDir(dir);
|
|
101
|
+
map.set(name, { dir, hash: h });
|
|
102
|
+
}
|
|
103
|
+
return map;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function compare({ upstreamUrl, tmpDir, ref } = {}) {
|
|
107
|
+
const proprietary = await loadProprietary();
|
|
108
|
+
const fetched = await fetchUpstream({ tmpDir, upstreamUrl, ref });
|
|
109
|
+
const upstreamRoot = fetched.dest;
|
|
110
|
+
const upstreamMap = await collectUpstreamSkills(upstreamRoot);
|
|
111
|
+
const localMap = await collectLocalSkills(proprietary);
|
|
112
|
+
|
|
113
|
+
const added = [];
|
|
114
|
+
const updated = [];
|
|
115
|
+
const same = [];
|
|
116
|
+
const removed = [];
|
|
117
|
+
const renamed = [];
|
|
118
|
+
|
|
119
|
+
// 检测重命名:本地旧名存在且上游新名存在,且本地旧名不在上游
|
|
120
|
+
for (const [oldName, newName] of Object.entries(RENAMES)) {
|
|
121
|
+
if (localMap.has(oldName) && upstreamMap.has(newName) && !upstreamMap.has(oldName)) {
|
|
122
|
+
renamed.push({ from: oldName, to: newName });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const renamedFrom = new Set(renamed.map((r) => r.from));
|
|
126
|
+
const renamedTo = new Set(renamed.map((r) => r.to));
|
|
127
|
+
|
|
128
|
+
for (const [name, u] of upstreamMap.entries()) {
|
|
129
|
+
if (renamedTo.has(name)) continue; // 重命名的新名单独处理
|
|
130
|
+
const local = localMap.get(name);
|
|
131
|
+
if (!local) {
|
|
132
|
+
added.push(name);
|
|
133
|
+
} else if (local.hash !== u.hash) {
|
|
134
|
+
updated.push(name);
|
|
135
|
+
} else {
|
|
136
|
+
same.push(name);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// 重命名的也算 updated(需迁移)
|
|
140
|
+
for (const r of renamed) {
|
|
141
|
+
const u = upstreamMap.get(r.to);
|
|
142
|
+
const local = localMap.get(r.from);
|
|
143
|
+
if (local && u && local.hash !== u.hash) updated.push(`${r.from}→${r.to}`);
|
|
144
|
+
else if (!local) added.push(r.to);
|
|
145
|
+
else same.push(`${r.from}→${r.to}`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
for (const name of localMap.keys()) {
|
|
149
|
+
if (renamedFrom.has(name)) continue;
|
|
150
|
+
if (!upstreamMap.has(name) && !renamedTo.has(name)) {
|
|
151
|
+
removed.push(name);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
head: fetched.head,
|
|
157
|
+
dest: fetched.dest,
|
|
158
|
+
upstreamMap,
|
|
159
|
+
localMap,
|
|
160
|
+
proprietary: [...proprietary],
|
|
161
|
+
result: { added: added.sort(), updated: updated.sort(), same: same.sort(), removed: removed.sort(), renamed },
|
|
162
|
+
counts: { upstream: upstreamMap.size, local: localMap.size },
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export async function applySync({ upstreamUrl, tmpDir, ref, dryRun = false, force = false } = {}) {
|
|
167
|
+
const proprietary = await loadProprietary();
|
|
168
|
+
const cmp = await compare({ upstreamUrl, tmpDir, ref });
|
|
169
|
+
const { dest, head, upstreamMap, result } = cmp;
|
|
170
|
+
const actions = [];
|
|
171
|
+
|
|
172
|
+
if (dryRun) {
|
|
173
|
+
return { ...cmp, actions, dryRun: true };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 处理重命名:删除旧目录,复制新目录
|
|
177
|
+
for (const r of result.renamed) {
|
|
178
|
+
const src = upstreamMap.get(r.to).dir;
|
|
179
|
+
const dst = path.join(LOCAL_SKILLS_DIR, r.to);
|
|
180
|
+
const oldDst = path.join(LOCAL_SKILLS_DIR, r.from);
|
|
181
|
+
await rm(oldDst, { recursive: true, force: true });
|
|
182
|
+
await rm(dst, { recursive: true, force: true });
|
|
183
|
+
await cp(src, dst, { recursive: true, force: true });
|
|
184
|
+
actions.push(`rename ${r.from} → ${r.to}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 新增
|
|
188
|
+
for (const name of result.added) {
|
|
189
|
+
// 跳过已通过重命名处理的新名
|
|
190
|
+
if (result.renamed.some((r) => r.to === name)) continue;
|
|
191
|
+
const src = upstreamMap.get(name)?.dir;
|
|
192
|
+
if (!src) continue;
|
|
193
|
+
const dst = path.join(LOCAL_SKILLS_DIR, name);
|
|
194
|
+
await cp(src, dst, { recursive: true, force: true });
|
|
195
|
+
actions.push(`add ${name}`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// 更新
|
|
199
|
+
for (const name of result.updated) {
|
|
200
|
+
if (name.includes('→')) continue; // 已处理
|
|
201
|
+
const src = upstreamMap.get(name)?.dir;
|
|
202
|
+
if (!src) continue;
|
|
203
|
+
const dst = path.join(LOCAL_SKILLS_DIR, name);
|
|
204
|
+
await rm(dst, { recursive: true, force: true });
|
|
205
|
+
await cp(src, dst, { recursive: true, force: true });
|
|
206
|
+
actions.push(`update ${name}`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// 删除(上游已删且非独有)
|
|
210
|
+
for (const name of result.removed) {
|
|
211
|
+
const dst = path.join(LOCAL_SKILLS_DIR, name);
|
|
212
|
+
// 仅当非独有且上游确实不存在时删除;默认执行,需 --force 才删?按自动覆盖策略直接删
|
|
213
|
+
await rm(dst, { recursive: true, force: true });
|
|
214
|
+
actions.push(`remove ${name}`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// 清理临时目录
|
|
218
|
+
await rm(dest, { recursive: true, force: true });
|
|
219
|
+
|
|
220
|
+
return { ...cmp, dest: null, actions, head };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function formatTable(cmp) {
|
|
224
|
+
const { result, counts, head } = cmp;
|
|
225
|
+
const lines = [];
|
|
226
|
+
lines.push(`上游 HEAD: ${head}`);
|
|
227
|
+
lines.push(`本地非独有: ${counts.local} 上游: ${counts.upstream}`);
|
|
228
|
+
lines.push('');
|
|
229
|
+
const totalDiff = result.added.length + result.updated.length + result.removed.length + result.renamed.length;
|
|
230
|
+
if (totalDiff === 0) {
|
|
231
|
+
lines.push('✅ 已是最新,无差异');
|
|
232
|
+
} else {
|
|
233
|
+
if (result.added.length) lines.push(`新增 (${result.added.length}): ${result.added.join(', ')}`);
|
|
234
|
+
if (result.updated.length) lines.push(`更新 (${result.updated.length}): ${result.updated.join(', ')}`);
|
|
235
|
+
if (result.renamed.length) lines.push(`重命名 (${result.renamed.length}): ${result.renamed.map((r) => `${r.from}→${r.to}`).join(', ')}`);
|
|
236
|
+
if (result.removed.length) lines.push(`删除 (${result.removed.length}): ${result.removed.join(', ')}`);
|
|
237
|
+
if (result.same.length) lines.push(`一致 (${result.same.length}): ${result.same.join(', ')}`);
|
|
238
|
+
}
|
|
239
|
+
return lines.join('\n');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async function main() {
|
|
243
|
+
const args = process.argv.slice(2);
|
|
244
|
+
const opts = {
|
|
245
|
+
json: args.includes('--json'),
|
|
246
|
+
dryRun: args.includes('--dry-run'),
|
|
247
|
+
force: args.includes('--force'),
|
|
248
|
+
check: args.includes('--check'),
|
|
249
|
+
apply: args.includes('--apply') || args.includes('--update'),
|
|
250
|
+
verbose: args.includes('--verbose') || args.includes('-v'),
|
|
251
|
+
};
|
|
252
|
+
const upstreamIdx = args.indexOf('--upstream');
|
|
253
|
+
const upstreamUrl = upstreamIdx !== -1 ? args[upstreamIdx + 1] : undefined;
|
|
254
|
+
const refIdx = args.indexOf('--ref');
|
|
255
|
+
const ref = refIdx !== -1 ? args[refIdx + 1] : undefined;
|
|
256
|
+
const tmpIdx = args.indexOf('--tmp');
|
|
257
|
+
const tmpDir = tmpIdx !== -1 ? args[tmpIdx + 1] : undefined;
|
|
258
|
+
|
|
259
|
+
if (args.includes('--help') || args.includes('-h') || args.length === 0) {
|
|
260
|
+
process.stdout.write(`sync-upstream — 对比/同步 mattpocock/skills 上游技能
|
|
261
|
+
|
|
262
|
+
Usage:
|
|
263
|
+
node scripts/sync-upstream.js --check [--json] [--upstream <url>] [--ref <ref>]
|
|
264
|
+
node scripts/sync-upstream.js --apply [--dry-run] [--force] [--upstream <url>] [--ref <ref>]
|
|
265
|
+
|
|
266
|
+
Options:
|
|
267
|
+
--check 只对比,不改动文件(默认)
|
|
268
|
+
--apply 应用同步(覆盖 .agents/skills 非独有技能)
|
|
269
|
+
--dry-run 演练模式,不写文件
|
|
270
|
+
--json 以 JSON 输出结果
|
|
271
|
+
--upstream 上游仓库 URL(默认 https://github.com/mattpocock/skills.git)
|
|
272
|
+
--ref 上游 ref(默认 HEAD)
|
|
273
|
+
--tmp 指定临时目录(默认 os.tmpdir() 下随机)
|
|
274
|
+
--force 强制覆盖(apply 时默认即覆盖,此标志保留兼容)
|
|
275
|
+
--verbose 详细输出
|
|
276
|
+
`);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (opts.apply) {
|
|
281
|
+
const res = await applySync({ upstreamUrl, tmpDir, ref, dryRun: opts.dryRun, force: opts.force });
|
|
282
|
+
if (opts.json) {
|
|
283
|
+
process.stdout.write(JSON.stringify({ head: res.head, result: res.result, actions: res.actions, dryRun: opts.dryRun }, null, 2) + '\n');
|
|
284
|
+
} else {
|
|
285
|
+
process.stdout.write(formatTable(res) + '\n');
|
|
286
|
+
if (res.actions.length) {
|
|
287
|
+
process.stdout.write(`\n已执行 ${res.actions.length} 项:\n`);
|
|
288
|
+
for (const a of res.actions) process.stdout.write(` - ${a}\n`);
|
|
289
|
+
}
|
|
290
|
+
if (opts.dryRun) process.stdout.write('\n(dry-run,未写文件)\n');
|
|
291
|
+
}
|
|
292
|
+
const hasDiff = res.result.added.length + res.result.updated.length + res.result.removed.length + res.result.renamed.length > 0;
|
|
293
|
+
if (opts.check && hasDiff) process.exitCode = 1;
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// 默认 --check
|
|
298
|
+
const cmp = await compare({ upstreamUrl, tmpDir, ref });
|
|
299
|
+
if (opts.json) {
|
|
300
|
+
process.stdout.write(JSON.stringify({ head: cmp.head, counts: cmp.counts, result: cmp.result }, null, 2) + '\n');
|
|
301
|
+
} else {
|
|
302
|
+
process.stdout.write(formatTable(cmp) + '\n');
|
|
303
|
+
}
|
|
304
|
+
const hasDiff = cmp.result.added.length + cmp.result.updated.length + cmp.result.removed.length + cmp.result.renamed.length > 0;
|
|
305
|
+
// 清理临时目录
|
|
306
|
+
await rm(cmp.dest, { recursive: true, force: true });
|
|
307
|
+
if (hasDiff) process.exitCode = 1;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (import.meta.url === `file://${process.argv[1]}` || process.argv[1].endsWith('sync-upstream.js')) {
|
|
311
|
+
main().catch((e) => {
|
|
312
|
+
process.stderr.write(`error: ${e.message}\n`);
|
|
313
|
+
process.exitCode = 1;
|
|
314
|
+
});
|
|
315
|
+
}
|
|
@@ -42,6 +42,10 @@ _Avoid_: keep going, don't stop
|
|
|
42
42
|
Splitting a giant turn — one large `write` or a batch of `replace`s — into small steps that are individually verified before continuing, so the turn never hits output caps and gets truncated mid-work.
|
|
43
43
|
_Avoid_: step-by-step, take it slow
|
|
44
44
|
|
|
45
|
+
**Git History Preservation** (Git 历史保护):
|
|
46
|
+
Every git-touching skill must preserve history after `BASE_HEAD=$(git rev-parse HEAD)` recorded at stage entry — history may only be appended. Before any commit or stage exit, verify `git merge-base --is-ancestor $BASE_HEAD HEAD`; if it fails, history was rewritten and must be recovered via `git reflog` before continuing. "Directory clean" (`git status` clean) may only be achieved by deleting the skill's own temporary artifacts (`[DEBUG-...]`, one-off scripts, untracked probe files); destructive git commands `git reset --hard`, `git checkout .`, `git clean -fd`, `git stash push --include-untracked`, `git push --force`, `git rebase -i` are forbidden without explicit user confirmation.
|
|
47
|
+
_Avoid_: force clean, stash all
|
|
48
|
+
|
|
45
49
|
**Long-Horizon Skill** (长程多阶段技能):
|
|
46
50
|
A skill whose run spans multiple stages or seams executed continuously (e.g. tdd-implement, diagnosing-bugs, improve-codebase-architecture, wayfinder, grill-to-spec, to-spec). The class of skill that must carry a Turn Continuity rule.
|
|
47
51
|
_Avoid_: complex skill, big skill
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Runtime Discipline
|
|
2
2
|
|
|
3
|
-
本仓库会话的运行时纪律,执行口径源自
|
|
3
|
+
本仓库会话的运行时纪律,执行口径源自 `docs/agents/skill-design.md` 的四条规则(规范正文)。术语定义见 `CONTEXT.md`。
|
|
4
4
|
|
|
5
5
|
## 回合连续性规则
|
|
6
6
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
- 外部阻塞:权限拒绝、缺失授权、依赖不可用——明确说明所需授权或替代路径,不静默停止
|
|
12
12
|
- 阶段完成:整个阶段的出口条件满足(如 seam 全绿、typecheck 通过、commit 完成)
|
|
13
13
|
|
|
14
|
-
预告下一步后立即执行该步骤,禁止把"分析/预告"当作回合终点。随包示例见 `.
|
|
14
|
+
预告下一步后立即执行该步骤,禁止把"分析/预告"当作回合终点。随包示例见 `.agents/skills/tdd-implement/SKILL.md` 与 `references/stages.md` 阶段③ 3e。
|
|
15
15
|
|
|
16
16
|
## 运行纪律(长程任务)
|
|
17
17
|
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
- **长程声明**:执行长程技能前,确认技能文本自带长程任务声明与回合连续性规则(Turn Continuity)——阶段内连续动作一回合内完成,不依赖 harness `/goal` 防线。tdd-implement 已内嵌(SKILL.md 声明 + references/stages.md 阶段③规则)。
|
|
21
21
|
- **模型选择**:flash 级模型长程任务卡住概率显著更高;关键长任务优先强模型或 `/goal` 模式。
|
|
22
22
|
- **任务分解(Chunking)**:巨型操作拆小步执行——单次 `write` 超过 ~150 行先写骨架再分批补全;批量 `replace` 超过 ~5 处分批执行,每批后立即验证。tdd-implement 已内嵌该规则(随包分发)。
|
|
23
|
+
- **Git 历史保护(Git History Preservation)**:任何触及 git 的操作必须追加历史、不可改写丢弃。阶段入口记录 `BASE_HEAD=$(git rev-parse HEAD)`,阶段出口与 commit 前校验 `git merge-base --is-ancestor $BASE_HEAD HEAD`,失败即经 `git reflog` 恢复后才继续。"目录卫生"仅删本次产生的 `[DEBUG-...]`/一次性脚本等未跟踪临时文件,禁止为达干净而执行 `git reset --hard`、`git checkout .`、`git clean -fd`、`git stash push --include-untracked`、`git push --force`、`git rebase -i` 等(需显式用户确认)。
|
|
23
24
|
|
|
24
25
|
## 执行原则(细则)
|
|
25
26
|
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The design rules every skill in this repo must obey. Apply them when writing a new skill or editing an existing one. Terms are defined once in [`CONTEXT.md`](../../CONTEXT.md) — reference them there, never restate the definition.
|
|
4
4
|
|
|
5
|
-
This spec exists because of a real incident: a long-horizon skill run on a flash-class model stopped its turn at "announce the next step" points four times in one session (see `DIAGNOSIS-tdd-implement-stuck.md`). The
|
|
6
|
-
|
|
5
|
+
This spec exists because of a real incident: a long-horizon skill run on a flash-class model stopped its turn at "announce the next step" points four times in one session (see `DIAGNOSIS-tdd-implement-stuck.md`). The four rules below are the preventive measures that came out of that diagnosis and subsequent git-history incidents (see `docs/adr/0003-git-history-preservation.md`). They are repo rules, not advice.
|
|
7
6
|
## Rule 1 — Turn Continuity
|
|
8
7
|
|
|
9
8
|
Every **Long-Horizon Skill** must carry a positive **Turn Continuity** rule of its own: the consecutive actions of a stage (red → green → typecheck → next seam) are executed serially **within one turn**, until the stage's exit condition is met. Do not end the turn at "announce the next step" points, and do not wait for the user to say "continue".
|
|
@@ -26,7 +25,13 @@ Giant turns — a single `write` of a large file, or a batch `replace` of a hund
|
|
|
26
25
|
- A batch of more than ~5 `replace`s: split into batches and verify after each batch.
|
|
27
26
|
|
|
28
27
|
These thresholds are experience defaults; adjust them as practice shows better values.
|
|
28
|
+
## Rule 4 — Git History Preservation
|
|
29
|
+
|
|
30
|
+
Every skill that touches git must preserve history after `BASE_HEAD`: history may only be appended, never rewritten or dropped. The skill must record `BASE_HEAD=$(git rev-parse HEAD)` at stage entry, and verify `git merge-base --is-ancestor $BASE_HEAD HEAD` at every stage exit and before any commit — failure means history was rewritten and the skill must recover via `git reflog` before continuing.
|
|
31
|
+
|
|
32
|
+
To achieve "directory clean" (`git status` clean) the skill may only delete its own temporary artifacts (`[DEBUG-...]`, one-off scripts, untracked probe files) — it must never use git-level destructive commands to reach a clean state. The following are forbidden without explicit user confirmation: `git reset --hard`, `git checkout .`, `git clean -fd`, `git stash push --include-untracked` (use `--keep-index` instead and `pop` with verification), `git push --force`, `git rebase -i` and any `reset`/`checkout` that moves `HEAD` backward.
|
|
29
33
|
|
|
34
|
+
Canonical enforcement: [`tdd-implement/references/stages.md`](../../skills/tdd-implement/references/stages.md) stage ③/⑥/⑦/A2-A4 Git 安全红线 and [`commit-check/SKILL.md`](../../skills/commit-check/SKILL.md) ③ 目录卫生.
|
|
30
35
|
## Long-horizon skills inventory
|
|
31
36
|
|
|
32
37
|
Skills currently classified as Long-Horizon, to be evolved against these rules as they are touched: `tdd-implement` (fixed), `diagnose-fix` (fixed — new orchestration skill for diagnosis + TDD fix, carries its own Turn Continuity rule), `diagnosing-bugs`, `improve-codebase-architecture`, `wayfinder`, `grill-to-spec`, `to-spec`. Backfilling existing skill texts is out of scope for now — these rules bind new and edited skills going forward.
|
|
@@ -21,11 +21,13 @@ description: "Run the pre-commit gate before any commit: verify docs match the i
|
|
|
21
21
|
- 改动涉及项目结构、分发文件、技能/命令清单时,检查 README 中对应的结构说明、映射表、清单是否同步
|
|
22
22
|
- 改动涉及用法/CLI/配置/示例时,检查 README 对应描述与实际一致
|
|
23
23
|
- 存在模板镜像/分发副本时,确认源文件与副本同步(如有守护测试,跑一遍确认)
|
|
24
|
+
- **特例:skill 与 AGENTS.md 的模板同步增量**:若 `git diff HEAD` 仅涉及 `AGENTS.md` 的 `tdd-implement ↔ implement` 路由行 + `.agents/skills/`/`.pi/skills/`/`.opencode/skills/` 的技能文件 + `.gitignore` 的 `.pi/` 忽略,且同目录存在 `AGENTS.md.bak`(模板同步 `sync`/`init --force` 的备份),视为模板同步的预期增量,禁止自动 `checkout -- AGENTS.md` 回滚;存在性按文件系统判(`ls .agents/skills/<name> .pi/skills/<name> .opencode/skills/<name>` 任一存在即算存在,不以 `git ls-files` 为准);缺技能则正向补齐而非回滚文档
|
|
24
25
|
|
|
25
26
|
### ③ 保持目录卫生
|
|
26
27
|
|
|
27
28
|
- `git status` 确认工作区只含预期改动:无残留未跟踪文件、无临时产物(调试脚本、日志、备份文件、`[DEBUG-...]` 残留)
|
|
28
|
-
-
|
|
29
|
+
- 清理本次改动产生的临时文件(一次性脚本、转储、探针)——仅删本次产生的未跟踪临时产物,禁止为达干净而执行 `git reset --hard`、`git checkout .`、`git clean -fd`、`git stash push --include-untracked`、`git push --force`、`git rebase -i` 等(需显式用户确认;`stash` 如需使用改用 `--keep-index` 并在 `pop` 后校验 `git merge-base --is-ancestor $BASE_HEAD HEAD`)。详见 `CONTEXT.md` Git History Preservation 与 `docs/agents/skill-design.md` Rule 4
|
|
30
|
+
- 若本次会话记录了 `BASE_HEAD`,commit 前校验 `git merge-base --is-ancestor $BASE_HEAD HEAD`,失败即经 `git reflog` 恢复后才提交
|
|
29
31
|
- 确认没有敏感信息进入改动(密钥、token、`.env`、私钥)——跑 `scripts/scan-sensitive.sh`,不用手写扫描
|
|
30
32
|
- 提交后工作区应为干净状态(`git status` 无输出)
|
|
31
33
|
|
|
@@ -1,143 +1,48 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: tdd-implement
|
|
3
|
-
description: "
|
|
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."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# TDD Implement
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
`seam` + `red-green` 为领衔词的完整实现编排:每个 seam 一个红-绿循环,直到 commit。TDD 语义(红-绿循环、seam 定义、好测试标准)以 [tdd 技能](.agents/skills/tdd/SKILL.md) 为唯一事实源——测试标准见 [tdd/tests.md](.agents/skills/tdd/tests.md),Mock 边界见 [tdd/mocking.md](.agents/skills/tdd/mocking.md);本技能只编排阶段与运行时规则。
|
|
9
9
|
|
|
10
|
-
本技能是**长程任务**(Long-Horizon Skill):多阶段串行执行,自带**回合连续性**(Turn Continuity)与**任务分解**(Chunking
|
|
10
|
+
本技能是**长程任务**(Long-Horizon Skill):多阶段串行执行,自带**回合连续性**(Turn Continuity)与**任务分解**(Chunking)规则。术语定义见 `CONTEXT.md`,技能设计规则见 `docs/agents/skill-design.md`。
|
|
11
11
|
|
|
12
|
-
##
|
|
12
|
+
## 分支
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
```
|
|
14
|
+
- **单线**:单 spec / 单 issue,走下节 Steps ①→⑦(详规见 [stages.md](references/stages.md))。
|
|
15
|
+
- **多 issue 编排**:`.scratch/<feature>/issues/` 下多文件且含 `Blocked by` 时走编排模式——编排器按依赖分层并行调度,子代理各自治完成 ①→⑦。详见 [orchestration.md](references/orchestration.md)。
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
## Steps
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
- ⑤ Code Review:按 [code-review](.agents/skills/code-review/SKILL.md) **双轴审查**——Standards 轴(编码标准符合度)与 Spec 轴(spec/issue 实现忠实度),两轴独立报告互不掩盖;审查结果只在对话输出,不生成书面审查报告(不落盘 `review-*.md` 类文件)。**派发纪律:两轴必须用 subagent single 模式或 `subagent_consult` 逐个派发,禁止 parallel `tasks` 数组**(parallel 结果仅保留 160 字节摘要,中文报告必截断;详见 stages.md 阶段⑤)
|
|
22
|
-
- ⑥ Commit:commit 前运行 [commit-check](.agents/skills/commit-check/SKILL.md) 门禁(①审查文档 ②对齐 README ③目录卫生 ④commit message),四项全过才提交
|
|
23
|
-
- ⑦ 收尾:先对齐文档——检查 README 与 docs/ 中涉及本次实现的描述与实现是否一致,不一致则更新并 commit;再更新 issue 状态(有关联 issue 时,其验收标准逐条转写为 checkbox 清单并打勾——全部 `- [x]` 才允许标 `resolved`);最后保持目录卫生(清理 `[DEBUG-...]` 调试残留与临时产物,`git status` 确认工作区干净)
|
|
19
|
+
按序执行,每步达到完成条件才进入下一步;进入任一步前先读取其在 [stages.md](references/stages.md) 的定义。
|
|
24
20
|
|
|
25
|
-
|
|
21
|
+
| Step | 做什么 | 完成条件(可验证) | 详规 |
|
|
22
|
+
|------|--------|-------------------|------|
|
|
23
|
+
| ① 理解需求 | 读取 spec/ticket + `CONTEXT.md`/`docs/adr/`,澄清歧义 | 能复述需求且无未澄清歧义 | [stages.md#阶段-①](references/stages.md#阶段-①理解需求) |
|
|
24
|
+
| ② 确认 Seams | 列出待测公共接口 seams(名称+输入+预期输出),向用户确认并生成 Todo | 用户明确同意 seams 清单;Todo 已生成 | [stages.md#阶段-②](references/stages.md#阶段-②确认-seams测试接缝) |
|
|
25
|
+
| ③ TDD 开发循环 | 逐 seam 红-绿循环,串行推进至全绿 | 所有 seams 红-绿完成 + typecheck 通过 | [stages.md#阶段-③](references/stages.md#阶段-③tdd-开发循环) |
|
|
26
|
+
| ④ 完整测试套件 | 跑全量测试 | 全部测试通过(失败回 ③) | [stages.md#阶段-④](references/stages.md#阶段-④完整测试套件) |
|
|
27
|
+
| ⑤ Code Review | 按 [code-review](.agents/skills/code-review/SKILL.md) 双轴审查(Standards + Spec) | 双轴均通过 | [stages.md#阶段-⑤](references/stages.md#阶段-⑤code-review) |
|
|
28
|
+
| ⑥ Commit | 跑 [commit-check](.agents/skills/commit-check/SKILL.md) 门禁四项后提交 | commit 完成且历史校验通过 | [stages.md#阶段-⑥](references/stages.md#阶段-⑥commit) |
|
|
29
|
+
| ⑦ 收尾 | 文档对齐 → issue 状态与实施总结 → 目录卫生 | 文档已对齐、issue 已 `resolved`+总结落盘、工作区干净 | [stages.md#阶段-⑦](references/stages.md#阶段-⑦收尾文档对齐--issue-状态--实施总结) |
|
|
26
30
|
|
|
27
|
-
|
|
31
|
+
编排模式流转见 [orchestration.md](references/orchestration.md);子代理内部仍走上表 ①→⑦(其中 ④ 为相关测试口径,全量由编排器收敛)。
|
|
28
32
|
|
|
29
|
-
|
|
30
|
-
- **编排器职责**:解析 `Blocked by` 依赖图 → 拓扑分层 → 按层调度子代理 → 逐 issue 验收(见下)→ 层间收敛验证(全量测试 + `git status` 干净)→ 汇总实施总结。
|
|
31
|
-
- **子代理契约(单 issue 单代理)**:输入 `spec.md + 单个 issue.md + CONTEXT.md/ADRs`,严格走 tdd-implement ①→⑦(含 seams 确认、红-绿循环、typecheck、双轴 review、commit-check 门禁、issue `resolved` + 实施总结);产出独立 commit;禁止跨 issue 改动。
|
|
32
|
-
- **子代理输出约束**:只返回**回执卡片**(结构化关键信息),不透传全量过程日志。回执字段:issue 编号与标题 / commit hash / seams 清单 / 测试结果(数量与是否全绿)/ typecheck 结论 / 双轴 review 结论 / 验收 checkbox 结果 / 文档对齐清单 / 遗留与风险。红-绿细节、typecheck 原始输出、review 全文等过程日志留在子代理内部,不向主代理透传。
|
|
33
|
-
- **主代理验收**:编排器不盲信回执,逐 issue 验收后才算该 issue 完成。验收项:① commit 存在且 message 含 issue 编号 ② issue 文件 `Status: resolved` + `## 实施总结` 已落盘 ③ 抽检验证(抽跑相关测试或 `tsc --noEmit` 抽检,不重跑全量)④ 无跨 issue 改动(`git diff --name-only` 核对)⑤ 工作区干净。任一项不通过则打回重派该子代理,层内其他已通过不受影响;验收通过才计入层收敛。
|
|
34
|
-
- **分层并行**:同层无依赖的 issue 并行派发子代理,层内全部验收通过后才进入下一层;层间串行,层内并行。
|
|
35
|
-
- **冲突处理**:同层子代理若触及同一文件,后完成者 rebase 解决冲突后重跑 typecheck + 相关测试;跨层天然串行无冲突。
|
|
36
|
-
- **收敛**:全部层验收通过后编排器跑全量测试套件 + 目录卫生检查,任一失败按回退路由回到对应层重派。
|
|
33
|
+
### 阶段间流转
|
|
37
34
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
回合终点仅为三类之一:
|
|
43
|
-
- **合规交互点**:技能要求的用户确认(如阶段② seams 清单确认)——此时提问并等待
|
|
44
|
-
- **外部阻塞**:权限拒绝、缺失授权、依赖不可用——此时明确说明需要什么授权或替代路径,不静默停止
|
|
45
|
-
- **阶段完成**:整个阶段的出口条件满足(如阶段③的所有 seams 红-绿完成 + typecheck 通过、commit 完成)——单个 seam 全绿只是阶段③的内部步骤,不是回合终点
|
|
46
|
-
|
|
47
|
-
输出进度/预告本身不结束回合——输出后继续执行,直到三类终点之一达成;预告下一步后立即执行该步骤。逐 seam 的执行细则见 [stages.md 3e](references/stages.md#阶段-③tdd-开发循环)。
|
|
48
|
-
|
|
49
|
-
编排模式下回合连续性延伸至**层**:一层内全部子代理派发后,编排器等待该层全 `resolved` 再进入下一层,不在层间停顿等待用户"继续";子代理内部仍遵守单 issue 的回合连续性。
|
|
50
|
-
|
|
51
|
-
## 不做什么
|
|
52
|
-
|
|
53
|
-
- 不重写 TDD 语义:红-绿循环、seam 定义、好测试标准、mocking 边界一律查 [tdd 技能](.agents/skills/tdd/SKILL.md),本技能只编排阶段与运行时规则
|
|
54
|
-
- 不把重构塞进红-绿循环:重构归阶段⑤ Code Review
|
|
55
|
-
- 不在阶段间停顿:单 seam 全绿、单次 typecheck 通过都不是回合终点(见回合连续性规则)
|
|
56
|
-
- 不生成书面审查报告:阶段⑤审查结果只在对话输出,不落盘 `review-*.md` 类文件
|
|
57
|
-
- 不手写超大改动:巨型 write/批量 replace 会撞输出上限、中途截断,因此单次 `write` 超 ~150 行先写骨架再分批补全;批量 `replace` 超 ~5 处先拆分再分批执行(见 [stages.md](references/stages.md) 3f)
|
|
58
|
-
- 不跳步:阶段出口未达成不进入下一阶段(见路由规则)
|
|
59
|
-
- 不为单 issue 引入编排:单 issue / 单 spec 不走多 issue 编排分支
|
|
60
|
-
|
|
61
|
-
## 任务拆分与 Todo 规定
|
|
62
|
-
|
|
63
|
-
### 拆分层级(大小任务层次)
|
|
64
|
-
|
|
65
|
-
1. **大任务**:Goal/Ticket——整个实现单元,对应一次完整的 tdd-implement 流程
|
|
66
|
-
2. **中任务**:Seam(阶段②确认)——一个红-绿循环单元,每 seam 一个 Todo
|
|
67
|
-
3. **小任务**:Todo——seam 内可独立验证、可勾选的执行单元(T1/T2/T3…)
|
|
68
|
-
4. **执行步**:Subtodo——Todo 内的串行步骤(红 → 绿 → typecheck),回合内逐步勾选推进
|
|
69
|
-
|
|
70
|
-
编排模式下新增一层:
|
|
71
|
-
|
|
72
|
-
5. **编排层**:Feature——`.scratch/<feature>/` 下全部 issues,按 `Blocked by` 分层;每层一组并行子代理,每子代理一个 issue 的完整 ①→⑦。
|
|
73
|
-
|
|
74
|
-
### Todo 清单格式
|
|
75
|
-
阶段② seams 确认后立即生成 todo 清单,每个 seam 一个 todo:
|
|
76
|
-
- 编号:`T1`、`T2`、`T3`…
|
|
77
|
-
- 描述:seam 名称 + 输入 + 预期输出
|
|
78
|
-
- 状态:`pending` / `in-progress` / `done` / `blocked`
|
|
79
|
-
- 完成标准(DoD):该 seam 测试全绿 + typecheck 通过 + 既有测试不受影响
|
|
80
|
-
- 执行步(Subtodo):`T1-R` 红(写失败测试)→ `T1-G` 绿(最小实现)→ `T1-T` typecheck
|
|
81
|
-
|
|
82
|
-
编排模式下 Todo 清单为**分层清单**:`L1: [01, 02] → L2: [03, 04] → L3: [05]`,每层内 issue 并行,层间串行;每 issue 的 DoD 为 `Status: resolved` + 独立 commit + 实施总结已落盘。
|
|
83
|
-
|
|
84
|
-
### Todo 状态机
|
|
85
|
-
```
|
|
86
|
-
pending → in-progress → done
|
|
87
|
-
↘ blocked(外部阻塞)→(授权/替代路径)→ in-progress
|
|
88
|
-
```
|
|
89
|
-
- Subtodo 不单独设 `blocked`——阻塞状态归父 Todo,Subtodo 跟随父状态
|
|
90
|
-
|
|
91
|
-
编排模式下 issue 粒度状态机:`pending → in-progress(子代理已派发) → done(Status: resolved)`;`blocked` 表示 `Blocked by` 依赖未满足,待前层全 `resolved` 后自动解阻。
|
|
92
|
-
|
|
93
|
-
### 粒度与回合归属
|
|
94
|
-
- 一个 todo = 一个 seam 的红-绿 cycle + typecheck,不可再拆
|
|
95
|
-
- 一个 todo 必须在一个回合内完成(红→绿→typecheck→全绿)
|
|
96
|
-
- Subtodo 是 todo 内的执行步:每完成一步立即进入下一步(`T1-R` → `T1-G` → `T1-T`),禁止停在步间预告
|
|
97
|
-
- 每完成一个 todo 立即更新其状态,再进入下一个
|
|
98
|
-
- todo 状态只按实际推进更新(pending → in-progress → done),不基于旧快照重写整个清单;已完成项(done)永不回退
|
|
99
|
-
- 全部 todo 为 done 才进入阶段④
|
|
100
|
-
|
|
101
|
-
编排模式下:每层全部 issue `done` 才进入下一层;全部层 `done` 后编排器做全量收敛验证。
|
|
102
|
-
|
|
103
|
-
### 阻塞处理
|
|
104
|
-
- 外部阻塞(权限拒绝、缺失授权、依赖不可用)→ 标记 `blocked`,记录所需授权或替代路径
|
|
105
|
-
- 不静默停止;恢复后回到 `in-progress` 继续
|
|
106
|
-
|
|
107
|
-
编排模式下:`Blocked by` 依赖阻塞由编排器自动管理——前层未全 `resolved` 时后层 `blocked`,前层收敛后自动解阻派发;不需人工确认依赖满足。
|
|
108
|
-
|
|
109
|
-
## 路由规则
|
|
110
|
-
|
|
111
|
-
### 正常流转
|
|
112
|
-
|
|
113
|
-
| 当前阶段 | 出口条件 | 下一阶段 |
|
|
114
|
-
|----------|----------|----------|
|
|
115
|
-
| ① 理解需求 | 需求已澄清,无歧义 | → ② 确认 Seams |
|
|
116
|
-
| ② 确认 Seams | 用户确认 seams 清单 | → ③ TDD 开发 |
|
|
117
|
-
| ③ TDD 开发 | 所有 seams 红-绿完成,typecheck 通过 | → ④ 完整测试套件 |
|
|
118
|
-
| ④ 完整测试套件 | 全部测试通过 | → ⑤ Code Review |
|
|
119
|
-
| ⑤ Code Review | 审查通过 | → ⑥ Commit |
|
|
120
|
-
| ⑥ Commit | commit 完成 | → ⑦ 收尾 |
|
|
121
|
-
| ⑦ 收尾 | issue 状态已更新 + 实施总结已写 | ✅ 结束 |
|
|
122
|
-
|
|
123
|
-
编排模式流转:`编排器:依赖图 → 分层 → [层内并行子代理(①→⑦) → 层收敛]×N → 全量收敛 → 汇总总结 ✅`;子代理内部仍走上表单 issue 流转。
|
|
124
|
-
|
|
125
|
-
### 回退路由
|
|
126
|
-
|
|
127
|
-
| 当前阶段 | 回退条件 | 回退目标 |
|
|
128
|
-
|----------|----------|----------|
|
|
129
|
-
| ③ TDD 开发 | typecheck 失败 | → ③ 修复类型错误 |
|
|
130
|
-
| ④ 完整测试套件 | 测试失败 | → ③ 修复失败测试 |
|
|
131
|
-
| ⑤ Code Review | 实现错误 | → ③ 修复实现 |
|
|
132
|
-
| ⑤ Code Review | seams 遗漏 | → ② 补充 seams |
|
|
133
|
-
| ⑤ Code Review | 需求偏差 | → ① 澄清需求 |
|
|
134
|
-
|
|
135
|
-
编排模式回退:子代理内回退按上表在子代理内闭环;编排器层收敛失败(全量测试失败 / 目录不干净)→ 定位到失败 issue 所在层重派对应子代理。
|
|
35
|
+
- 正常流转:出口条件满足即进入下一阶段,不在阶段间停顿。
|
|
36
|
+
- 回退路由:见 [stages.md#回退路由](references/stages.md#回退路由);编排模式回退见 [orchestration.md#A5](references/orchestration.md#a5-回退与冲突)。
|
|
37
|
+
- 回合连续性:阶段内连续动作(红→绿→typecheck→下一 seam)在**一个回合内串行完成**,直至阶段出口;预告下一步后立即执行。详规见 [stages.md ③-3e](references/stages.md#3e-回合连续性) 与 [orchestration.md#A2](references/orchestration.md#a2-分层调度)。
|
|
38
|
+
- 任务分解:巨型写入拆小步——`write` 超 ~150 行先写骨架再分批补全,`replace` 超 ~5 处分批执行并验证。详见 [stages.md ③-3f](references/stages.md#3f-任务分解chunking)。
|
|
136
39
|
|
|
137
40
|
## 引用
|
|
138
41
|
|
|
139
42
|
- TDD 核心规则:[tdd 技能](.agents/skills/tdd/SKILL.md)
|
|
140
43
|
- 测试标准:[tdd/tests.md](.agents/skills/tdd/tests.md)
|
|
141
44
|
- Mock 指南:[tdd/mocking.md](.agents/skills/tdd/mocking.md)
|
|
142
|
-
- Commit 门禁:[commit-check
|
|
45
|
+
- Commit 门禁:[commit-check](.agents/skills/commit-check/SKILL.md)
|
|
46
|
+
- 单线详规:[stages.md](references/stages.md)
|
|
47
|
+
- 多 issue 编排:[orchestration.md](references/orchestration.md)
|
|
143
48
|
- Issue tracker 约定:[issue-tracker.md](../../docs/agents/issue-tracker.md)
|