@heihei0299/matt-skills 3.1.1 → 3.1.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/README.md CHANGED
@@ -66,7 +66,9 @@ npx @heihei0299/matt-skills sync --dry-run --json
66
66
 
67
67
  `init` 对已有 `AGENTS.md` 始终跳过;已有项目使用 `sync`。默认 `sync` 保留已有 `AGENTS.md` 和项目规则,`--all` 只扩大技能范围。需要显式刷新 `AGENTS.md` 时使用 `sync --refresh-agents`;无受管区块时会先备份为 `AGENTS.md.bak`。`sync` 不删除目标项目的额外文件或自定义 skills。
68
68
 
69
- 默认 workflow 范围固定为 7 个入口及依赖 skills,包含 `initialize-project`、`setup-matt-pocock-skills`、`grill-with-docs`、`to-spec`、`to-tickets`、`grilling` 和 `domain-modeling`;其中默认独有 skills 是 `initialize-project`。`tdd` 及其他可分发 skills 仍通过 `--all` 获取。
69
+ 默认 `init` / `sync` 不分发 `.pi` 与 `.opencode`;需要完整 harness 配置时使用 `--all`。
70
+
71
+ 默认 workflow 范围固定为 9 个入口及依赖 skills,包含 `initialize-project`、`setup-matt-pocock-skills`、`grill-with-docs`、`to-spec`、`to-tickets`、`grilling`、`domain-modeling`、`tdd` 和 `diagnosing-bugs`;其中默认独有 skills 是 `initialize-project`。
70
72
 
71
73
  ## CLI
72
74
 
package/bin/cli.js CHANGED
@@ -34,7 +34,7 @@ Usage:
34
34
 
35
35
  Init options:
36
36
  --dest <path> Target directory (default: current directory)
37
- --all Include all distributable skills; default only default workflow skills
37
+ --all Include all distributable skills and .pi/.opencode; default workflow skills only
38
38
  --help, -h Show this help
39
39
  提示:已有 AGENTS.md 时 init 始终跳过;需要更新已有项目请使用 sync。
40
40
 
@@ -47,7 +47,7 @@ Usage:
47
47
  matt-skills sync [--all|--dry-run|--refresh-agents] [--dest <path>]
48
48
 
49
49
  Sync options:
50
- --all 同步全部可分发技能;不改变 AGENTS.md 刷新策略
50
+ --all 同步全部可分发技能和 .pi/.opencode;不改变 AGENTS.md 刷新策略
51
51
  --refresh-agents 显式刷新 AGENTS.md;无受管区块时先备份为 AGENTS.md.bak
52
52
  --dry-run 预演目标项目变化,只比对不写盘
53
53
  --json 仅与 --dry-run 一起使用,输出机器可读结果
@@ -31,7 +31,7 @@ export async function runInit(args) {
31
31
  if (await pathExists(marker)) {
32
32
  process.stdout.write('模板已存在(AGENTS.md),跳过\n');
33
33
  } else {
34
- await copyTemplate(target);
34
+ await copyTemplate(target, { includeHarness: all });
35
35
  const selectedSkills = await listSkillNames({ onlyProgramming });
36
36
  await distributeProjectSkills({ target, sourceDir: SKILLS_DIR, skillNames: selectedSkills });
37
37
  process.stdout.write(`模板:已复制(AGENTS.md、skills:${PROJECT_SKILLS_DIR})\n`);
@@ -55,7 +55,7 @@ async function syncCommand({ dest, all, dryRun, json, refreshAgents, quiet = fal
55
55
  }
56
56
 
57
57
  const target = dest ? path.resolve(process.cwd(), dest) : process.cwd();
58
- const template = await syncTemplate({ target, refreshAgents });
58
+ const template = await syncTemplate({ target, refreshAgents, includeHarness: all });
59
59
  if (template.initialized) {
60
60
  output('未检测到现有项目(AGENTS.md 不存在),将执行全新初始化\n');
61
61
  output(`模板:已复制(AGENTS.md、skills:${PROJECT_SKILLS_DIR})\n`);
@@ -8,17 +8,18 @@ const ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), '../..');
8
8
  export const TEMPLATE_DIR = path.join(ROOT, 'template');
9
9
  const DRY_RUN_PATHS = ['AGENTS.md', 'AGENTS.md.bak', '.opencode', '.pi', '.agents/skills', '.claude/skills'];
10
10
 
11
- function shouldCopyTemplatePath(src) {
11
+ function shouldCopyTemplatePath(src, includeHarness) {
12
12
  const relative = path.relative(TEMPLATE_DIR, src);
13
13
  const parts = relative.split(path.sep);
14
- return !(parts[0] === '.agents' && parts[1] === 'skills');
14
+ if (parts[0] === '.agents' && parts[1] === 'skills') return false;
15
+ return includeHarness || (parts[0] !== '.pi' && parts[0] !== '.opencode');
15
16
  }
16
17
 
17
- export async function copyTemplate(target) {
18
+ export async function copyTemplate(target, { includeHarness = true } = {}) {
18
19
  await cp(TEMPLATE_DIR, target, {
19
20
  recursive: true,
20
21
  force: true,
21
- filter: shouldCopyTemplatePath,
22
+ filter: (src) => shouldCopyTemplatePath(src, includeHarness),
22
23
  });
23
24
  }
24
25
 
@@ -77,10 +78,10 @@ export async function refreshAgentsFile(targetFile) {
77
78
  return 'full';
78
79
  }
79
80
 
80
- export async function syncTemplate({ target, refreshAgents }) {
81
+ export async function syncTemplate({ target, refreshAgents, includeHarness = true }) {
81
82
  const marker = path.join(target, 'AGENTS.md');
82
83
  if (!(await pathExists(marker))) {
83
- await copyTemplate(target);
84
+ await copyTemplate(target, { includeHarness });
84
85
  return { initialized: true, agentsManaged: false, agentsRefreshed: false, agentsRefreshMode: null };
85
86
  }
86
87
 
@@ -95,7 +96,9 @@ export async function syncTemplate({ target, refreshAgents }) {
95
96
  agentsManaged = await syncManagedAgents(marker, path.join(TEMPLATE_DIR, 'AGENTS.md'));
96
97
  } catch {}
97
98
  }
98
- await cp(path.join(TEMPLATE_DIR, '.opencode'), path.join(target, '.opencode'), { recursive: true, force: true });
99
- await cp(path.join(TEMPLATE_DIR, '.pi'), path.join(target, '.pi'), { recursive: true, force: true });
99
+ if (includeHarness) {
100
+ await cp(path.join(TEMPLATE_DIR, '.opencode'), path.join(target, '.opencode'), { recursive: true, force: true });
101
+ await cp(path.join(TEMPLATE_DIR, '.pi'), path.join(target, '.pi'), { recursive: true, force: true });
102
+ }
100
103
  return { initialized: false, agentsManaged, agentsRefreshed, agentsRefreshMode };
101
104
  }
@@ -1,9 +1,11 @@
1
1
  [
2
+ "diagnosing-bugs",
2
3
  "domain-modeling",
3
4
  "grill-with-docs",
4
5
  "grilling",
5
6
  "initialize-project",
6
7
  "setup-matt-pocock-skills",
8
+ "tdd",
7
9
  "to-spec",
8
10
  "to-tickets"
9
11
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heihei0299/matt-skills",
3
- "version": "3.1.1",
3
+ "version": "3.1.3",
4
4
  "description": "Agent skills + 项目配置模板:一条命令初始化 opencode / pi-agent 项目(含 mattpocock/skills 上游技能)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { cp, readdir, readFile, rm, stat, mkdir } from 'node:fs/promises';
2
+ import { cp, mkdir, mkdtemp, readdir, readFile, readlink, rename, rm, stat } from 'node:fs/promises';
3
3
  import { createHash } from 'node:crypto';
4
4
  import { spawnSync } from 'node:child_process';
5
5
  import path from 'node:path';
@@ -30,20 +30,31 @@ async function loadRequired() {
30
30
  return loadSkillSet(REQUIRED_PATH, 'required');
31
31
  }
32
32
 
33
- async function hashFile(filePath) {
34
- const buf = await readFile(filePath);
35
- return createHash('sha256').update(buf).digest('hex');
36
- }
37
-
38
33
  async function hashDir(dir) {
39
- // 对 SKILL.md 做 hash,用于比对;若无 SKILL.md 则对目录内所有文件联合 hash
40
- const skillMd = path.join(dir, 'SKILL.md');
41
- try {
42
- await stat(skillMd);
43
- return await hashFile(skillMd);
44
- } catch {
45
- return null;
34
+ const files = [];
35
+
36
+ async function collect(current, relative = '') {
37
+ const entries = await readdir(current, { withFileTypes: true });
38
+ entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
39
+ for (const entry of entries) {
40
+ const entryPath = path.join(current, entry.name);
41
+ const entryRelative = path.posix.join(relative, entry.name);
42
+ if (entry.isDirectory()) await collect(entryPath, entryRelative);
43
+ else if (entry.isFile()) files.push({ path: entryPath, relative: entryRelative, type: 'file' });
44
+ else if (entry.isSymbolicLink()) files.push({ path: entryPath, relative: entryRelative, type: 'symlink' });
45
+ }
46
+ }
47
+
48
+ await collect(dir);
49
+ files.sort((a, b) => (a.relative < b.relative ? -1 : a.relative > b.relative ? 1 : 0));
50
+
51
+ const hash = createHash('sha256');
52
+ for (const file of files) {
53
+ hash.update(`${file.type}\0${file.relative}\0`);
54
+ hash.update(file.type === 'symlink' ? await readlink(file.path) : await readFile(file.path));
55
+ hash.update('\0');
46
56
  }
57
+ return hash.digest('hex');
47
58
  }
48
59
 
49
60
  async function listSkills(dir) {
@@ -62,17 +73,30 @@ async function fetchUpstream({ tmpDir, upstreamUrl = UPSTREAM_URL, ref = 'HEAD'
62
73
  const dest = tmpDir || path.join(os.tmpdir(), `matt-skills-upstream-${Date.now()}`);
63
74
  await rm(dest, { recursive: true, force: true });
64
75
  await mkdir(path.dirname(dest), { recursive: true });
65
- const clone = spawnSync('git', ['clone', '--depth', '1', upstreamUrl, dest], { encoding: 'utf8' });
66
- if (clone.status !== 0) {
67
- throw new Error(`git clone 失败: ${clone.stderr || clone.stdout}`);
68
- }
69
- if (ref && ref !== 'HEAD') {
70
- const co = spawnSync('git', ['-C', dest, 'checkout', ref], { encoding: 'utf8' });
71
- if (co.status !== 0) throw new Error(`git checkout ${ref} 失败: ${co.stderr}`);
76
+ try {
77
+ const clone = spawnSync('git', ['clone', '--depth', '1', upstreamUrl, dest], { encoding: 'utf8' });
78
+ if (clone.status !== 0) {
79
+ throw new Error(`git clone 失败: ${clone.stderr || clone.stdout || clone.error?.message || 'unknown error'}`);
80
+ }
81
+ if (ref && ref !== 'HEAD') {
82
+ const fetched = spawnSync('git', ['-C', dest, 'fetch', '--depth', '1', 'origin', ref], { encoding: 'utf8' });
83
+ if (fetched.status !== 0) {
84
+ throw new Error(`git fetch ${ref} 失败: ${fetched.stderr || fetched.stdout || fetched.error?.message || 'unknown error'}`);
85
+ }
86
+ const co = spawnSync('git', ['-C', dest, 'checkout', '--detach', 'FETCH_HEAD'], { encoding: 'utf8' });
87
+ if (co.status !== 0) {
88
+ throw new Error(`git checkout ${ref} 失败: ${co.stderr || co.stdout || co.error?.message || 'unknown error'}`);
89
+ }
90
+ }
91
+ const rev = spawnSync('git', ['-C', dest, 'rev-parse', 'HEAD'], { encoding: 'utf8' });
92
+ if (rev.status !== 0) {
93
+ throw new Error(`git rev-parse 失败: ${rev.stderr || rev.stdout || rev.error?.message || 'unknown error'}`);
94
+ }
95
+ return { dest, head: rev.stdout.trim() || 'unknown' };
96
+ } catch (error) {
97
+ await rm(dest, { recursive: true, force: true }).catch(() => {});
98
+ throw error;
72
99
  }
73
- const rev = spawnSync('git', ['-C', dest, 'rev-parse', 'HEAD'], { encoding: 'utf8' });
74
- const head = rev.stdout ? rev.stdout.trim() : 'unknown';
75
- return { dest, head };
76
100
  }
77
101
 
78
102
  async function collectUpstreamSkills(upstreamRoot) {
@@ -95,27 +119,27 @@ async function collectUpstreamSkills(upstreamRoot) {
95
119
  return map;
96
120
  }
97
121
 
98
- async function collectLocalSkills(proprietary) {
122
+ async function collectLocalSkills(proprietary, localSkillsDir = LOCAL_SKILLS_DIR) {
99
123
  const map = new Map();
100
- const names = await listSkills(LOCAL_SKILLS_DIR);
124
+ const names = await listSkills(localSkillsDir);
101
125
  for (const name of names) {
102
126
  if (name === 'skill-creator') continue; // 本地符号链接,不纳入上游比对
103
127
  if (proprietary.has(name)) continue;
104
- const dir = path.join(LOCAL_SKILLS_DIR, name);
128
+ const dir = path.join(localSkillsDir, name);
105
129
  const h = await hashDir(dir);
106
130
  map.set(name, { dir, hash: h });
107
131
  }
108
132
  return map;
109
133
  }
110
134
 
111
- export async function compare({ upstreamUrl, tmpDir, ref, onlyProgramming = true } = {}) {
135
+ export async function compare({ upstreamUrl, tmpDir, ref, onlyProgramming = true, localSkillsDir = LOCAL_SKILLS_DIR } = {}) {
112
136
  const proprietary = await loadProprietary();
113
137
  const engineering = await loadEngineering();
114
138
  const required = await loadRequired();
115
139
  const fetched = await fetchUpstream({ tmpDir, upstreamUrl, ref });
116
140
  const upstreamRoot = fetched.dest;
117
141
  const upstreamMapFull = await collectUpstreamSkills(upstreamRoot);
118
- const localMapFull = await collectLocalSkills(proprietary);
142
+ const localMapFull = await collectLocalSkills(proprietary, localSkillsDir);
119
143
  // 默认范围:engineering 桶(编程)+ 独有所需(config/required.json,如 grill-to-spec 经 grill-with-docs 所需的 grilling);--all 则含全部 productivity
120
144
  const upstreamMap = onlyProgramming
121
145
  ? new Map([...upstreamMapFull.entries()].filter(([name, v]) => v.bucket === 'engineering' || required.has(name)))
@@ -188,59 +212,124 @@ export async function compare({ upstreamUrl, tmpDir, ref, onlyProgramming = true
188
212
  };
189
213
  }
190
214
 
191
- export async function applySync({ upstreamUrl, tmpDir, ref, dryRun = false, force = false, onlyProgramming = true } = {}) {
192
- const proprietary = await loadProprietary();
193
- const cmp = await compare({ upstreamUrl, tmpDir, ref, onlyProgramming });
215
+ async function verifyStagedSkills({ stageDir, effectiveMap, upstreamMapFull, result }) {
216
+ const sourceFor = (name) => effectiveMap.get(name) || upstreamMapFull.get(name);
217
+ const expected = new Set([
218
+ ...result.renamed.map((r) => r.to),
219
+ ...result.added.filter((name) => !result.renamed.some((r) => r.to === name)),
220
+ ...result.updated.filter((name) => !name.includes('→')),
221
+ ]);
222
+
223
+ for (const name of expected) {
224
+ const source = sourceFor(name);
225
+ const staged = path.join(stageDir, name);
226
+ if (!source || await hashDir(staged) !== source.hash) {
227
+ throw new Error(`同步暂存校验失败: ${name}`);
228
+ }
229
+ }
230
+ for (const name of [...result.removed, ...result.renamed.map((r) => r.from)]) {
231
+ try {
232
+ await stat(path.join(stageDir, name));
233
+ throw new Error(`同步暂存校验失败: ${name} 应被删除`);
234
+ } catch (error) {
235
+ if (error.code !== 'ENOENT') throw error;
236
+ }
237
+ }
238
+ }
239
+
240
+ async function commitStagedSkills(localSkillsDir, stageDir) {
241
+ const parent = path.dirname(localSkillsDir);
242
+ const backupDir = await mkdtemp(path.join(parent, '.matt-skills-sync-backup-'));
243
+ await rm(backupDir, { recursive: true, force: true });
244
+ let originalMoved = false;
245
+ let stageMoved = false;
246
+
247
+ try {
248
+ await rename(localSkillsDir, backupDir);
249
+ originalMoved = true;
250
+ await rename(stageDir, localSkillsDir);
251
+ stageMoved = true;
252
+ } catch (error) {
253
+ try {
254
+ if (stageMoved) await rename(localSkillsDir, stageDir);
255
+ if (originalMoved) await rename(backupDir, localSkillsDir);
256
+ } catch (rollbackError) {
257
+ throw new Error(`同步失败且回滚失败: ${rollbackError.message}; 原始错误: ${error.message}`);
258
+ }
259
+ await rm(backupDir, { recursive: true, force: true }).catch(() => {});
260
+ throw error;
261
+ }
262
+
263
+ await rm(backupDir, { recursive: true, force: true });
264
+ }
265
+
266
+ export async function applySync({ upstreamUrl, tmpDir, ref, dryRun = false, force = false, onlyProgramming = true, localSkillsDir = LOCAL_SKILLS_DIR, failAfter = null } = {}) {
267
+ const cmp = await compare({ upstreamUrl, tmpDir, ref, onlyProgramming, localSkillsDir });
194
268
  const { dest, head, upstreamMap, upstreamMapFull, result } = cmp;
195
269
  const effectiveMap = onlyProgramming ? upstreamMap : upstreamMapFull;
196
270
  const actions = [];
271
+ let stageDir = null;
197
272
 
198
- if (dryRun) {
199
- await rm(dest, { recursive: true, force: true });
200
- return { ...cmp, dest: null, actions, dryRun: true };
201
- }
273
+ try {
274
+ if (dryRun) return { ...cmp, dest: null, actions, dryRun: true };
275
+ const hasDiff = result.added.length + result.updated.length + result.removed.length + result.renamed.length > 0;
276
+ if (!hasDiff) return { ...cmp, dest: null, actions, head };
202
277
 
203
- // 处理重命名
204
- for (const r of result.renamed) {
205
- const src = effectiveMap.get(r.to)?.dir || upstreamMapFull.get(r.to)?.dir;
206
- if (!src) continue;
207
- const dst = path.join(LOCAL_SKILLS_DIR, r.to);
208
- const oldDst = path.join(LOCAL_SKILLS_DIR, r.from);
209
- await rm(oldDst, { recursive: true, force: true });
210
- await rm(dst, { recursive: true, force: true });
211
- await cp(src, dst, { recursive: true, force: true });
212
- actions.push(`rename ${r.from} → ${r.to}`);
213
- }
278
+ const stagePrefix = path.join(path.dirname(localSkillsDir), '.matt-skills-sync-stage-');
279
+ stageDir = await mkdtemp(stagePrefix);
280
+ await rm(stageDir, { recursive: true, force: true });
281
+ await cp(localSkillsDir, stageDir, { recursive: true, force: true });
282
+ let successfulOperations = 0;
283
+ const stageOperation = async (operation) => {
284
+ if (Number.isInteger(failAfter) && failAfter >= 0 && successfulOperations >= failAfter) {
285
+ throw new Error(`注入同步失败(已完成 ${successfulOperations} 个暂存操作)`);
286
+ }
287
+ const value = await operation();
288
+ successfulOperations++;
289
+ return value;
290
+ };
291
+ const sourceFor = (name) => effectiveMap.get(name)?.dir || upstreamMapFull.get(name)?.dir;
214
292
 
215
- for (const name of result.added) {
216
- if (result.renamed.some((r) => r.to === name)) continue;
217
- const src = effectiveMap.get(name)?.dir || upstreamMapFull.get(name)?.dir;
218
- if (!src) continue;
219
- const dst = path.join(LOCAL_SKILLS_DIR, name);
220
- await cp(src, dst, { recursive: true, force: true });
221
- actions.push(`add ${name}`);
222
- }
293
+ // 处理重命名
294
+ for (const r of result.renamed) {
295
+ const src = sourceFor(r.to);
296
+ if (!src) continue;
297
+ await stageOperation(() => rm(path.join(stageDir, r.from), { recursive: true, force: true }));
298
+ await stageOperation(() => rm(path.join(stageDir, r.to), { recursive: true, force: true }));
299
+ await stageOperation(() => cp(src, path.join(stageDir, r.to), { recursive: true, force: true }));
300
+ actions.push(`rename ${r.from} → ${r.to}`);
301
+ }
223
302
 
224
- for (const name of result.updated) {
225
- if (name.includes('→')) continue;
226
- const src = effectiveMap.get(name)?.dir || upstreamMapFull.get(name)?.dir;
227
- if (!src) continue;
228
- const dst = path.join(LOCAL_SKILLS_DIR, name);
229
- await rm(dst, { recursive: true, force: true });
230
- await cp(src, dst, { recursive: true, force: true });
231
- actions.push(`update ${name}`);
232
- }
303
+ for (const name of result.added) {
304
+ if (result.renamed.some((r) => r.to === name)) continue;
305
+ const src = sourceFor(name);
306
+ if (!src) continue;
307
+ await stageOperation(() => cp(src, path.join(stageDir, name), { recursive: true, force: true }));
308
+ actions.push(`add ${name}`);
309
+ }
233
310
 
234
- for (const name of result.removed) {
235
- const dst = path.join(LOCAL_SKILLS_DIR, name);
236
- await rm(dst, { recursive: true, force: true });
237
- actions.push(`remove ${name}`);
238
- }
311
+ for (const name of result.updated) {
312
+ if (name.includes('→')) continue;
313
+ const src = sourceFor(name);
314
+ if (!src) continue;
315
+ await stageOperation(() => rm(path.join(stageDir, name), { recursive: true, force: true }));
316
+ await stageOperation(() => cp(src, path.join(stageDir, name), { recursive: true, force: true }));
317
+ actions.push(`update ${name}`);
318
+ }
239
319
 
240
- // 清理临时目录
241
- await rm(dest, { recursive: true, force: true });
320
+ for (const name of result.removed) {
321
+ await stageOperation(() => rm(path.join(stageDir, name), { recursive: true, force: true }));
322
+ actions.push(`remove ${name}`);
323
+ }
242
324
 
243
- return { ...cmp, dest: null, actions, head };
325
+ await verifyStagedSkills({ stageDir, effectiveMap, upstreamMapFull, result });
326
+ await commitStagedSkills(localSkillsDir, stageDir);
327
+ stageDir = null;
328
+ return { ...cmp, dest: null, actions, head };
329
+ } finally {
330
+ await rm(dest, { recursive: true, force: true }).catch(() => {});
331
+ if (stageDir) await rm(stageDir, { recursive: true, force: true }).catch(() => {});
332
+ }
244
333
  }
245
334
  export function formatComparison(cmp) {
246
335
  const { result, counts, head, onlyProgramming } = cmp;
@@ -1,33 +1,30 @@
1
- ## Workflow
2
- 按任务选择最匹配的 skill / 工具:
3
- * 需要新增证据的代码理解 / 定位 / 调用链 / 依赖关系 / 数据流 → `codegraph explore`
4
- * 目标项目首次初始化或刷新项目上下文 → `initialize-project`
5
- * 首次使用 spec / tickets 流程,需要配置 issue tracker 和领域文档 → `setup-matt-pocock-skills`
6
- * 模糊需求、设计讨论并同步 ADR / glossary → `grill-with-docs`
7
- * 已有共识,需要发布 spec → `to-spec`
8
- * 已有 spec / 计划,需要拆分可执行 tickets → `to-tickets`
9
- 未命中 skill 时直接执行。明确不改变行为的文案、注释、格式和机械修改直接处理。
10
- 仅当关键歧义无法从仓库事实解决,且会改变实现、范围、风险或验收结果时询问用户。
1
+ 本仓库遵循全局 `AGENTS.md`;以下规则仅用于具体化本项目工作流,不放宽全局安全、授权或运行时权限边界。
2
+
11
3
  ## Context / CodeGraph
12
- 以当前代码、配置、测试和版本化文档为事实来源;更具体的项目指令优先。
13
- - 有 issue/spec 时先读当前 issue;否则从用户问题和最相关 symbol/path 开始。
14
- - 只按当前未决问题逐步扩展上下文;README/package/tests/docs 按需读取。
15
- - 当前上下文已有充分且未过时的证据时,不做等价重复读取。
16
- - 当当前上下文不足、需要新增代码理解证据时,优先使用 `codegraph explore`;结果充分后不再 broad grep/read。
4
+
5
+ 以当前代码、配置、测试和版本化文档为项目事实来源;更具体的目录级规则优先。
6
+ 有当前 issue / spec 时先读它;否则从用户问题和最相关的 symbol / path 开始。
7
+ 只围绕当前未决问题扩展上下文;README、package、tests、docs 按需读取。
8
+ 当前上下文已有充分且未过时的证据时,不做等价重复读取。
9
+ 当前上下文不足且需要新增代码理解证据时,优先使用 `codegraph explore`;结果充分后不再执行等价的 broad grep / read。
17
10
  实现时复用现有抽象、接口和依赖方向,不创建平行实现或无关扩展。
11
+
18
12
  ## Validation
19
- 验证应足以证明修改正确且未破坏直接受影响行为。
20
- 优先验证原问题、相关测试和直接受影响模块;不重复已有有效证据。
21
- 公共 API、共享抽象、跨模块调用链或局部验证不足时扩大验证范围;普通修改不自动运行全量测试。
13
+
14
+ 优先验证原问题、相关测试和直接受影响模块。
15
+ 公共 API、共享抽象、跨模块调用链或局部验证不足时扩大验证范围。
16
+ 项目已有针对当前改动的验证入口时优先使用,不自行创建等价验证流程。
17
+
22
18
  ## Git
23
- - 每个独立 issue/spec 对应一个 commit;不得按实现、测试、review 等阶段拆分。
24
- - 仅在本次改动完成并通过相关验证后提交。checkpoint 例外,但须明确标注未完成或未通过的验证。
25
- - commit 前检查 `git diff` 和 `git status`;只 stage 本次任务改动,不覆盖、回滚或提交用户及其他任务的既有修改。
26
- - `commit` 不授权 `push` 或发布;远程写入须单独授权。
27
- - commit 后报告 hash、验证命令及结果。
28
- ## Security
29
- * 不读取、输出或提交未经授权的真实 secrets。
30
- * 按现有 lockfile 恢复依赖可直接执行。
31
- * 新增/升级依赖、部署、发布、`git push`、远程写入和破坏性操作必须明确授权。
19
+
20
+ 每个独立 issue / spec 对应一个交付 commit,不按实现、测试、review 等阶段拆分。
21
+ 仅在本次任务完成并通过相关验证后 commit;明确要求 checkpoint 时可提交未完成状态,并注明未完成或未通过的验证。
22
+ commit 前检查 `git diff` 和 `git status`;只 stage 当前任务改动,不混入其他任务或用户已有修改。
23
+ commit 不授权 push、发布或其他远程写入。
24
+ commit 后报告 hash,以及本次执行的主要验证及结果。
25
+
32
26
  ## Completion
33
- 完成时简要说明结果和验证;有重要未验证项、限制或风险时说明;有 commit 时报告 hash。
27
+
28
+ 完成时简要说明结果、关键修改和验证。
29
+ 存在重要未验证项、已知限制、既有失败或剩余风险时明确指出。
30
+ 有 commit 时报告 commit hash。