@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 +3 -1
- package/bin/cli.js +2 -2
- package/bin/commands/init.js +1 -1
- package/bin/commands/sync.js +1 -1
- package/bin/project/template.js +11 -8
- package/config/default.json +2 -0
- package/package.json +1 -1
- package/scripts/sync-upstream.js +160 -71
- package/template/AGENTS.md +25 -28
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
|
-
默认
|
|
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
|
|
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
|
|
50
|
+
--all 同步全部可分发技能和 .pi/.opencode;不改变 AGENTS.md 刷新策略
|
|
51
51
|
--refresh-agents 显式刷新 AGENTS.md;无受管区块时先备份为 AGENTS.md.bak
|
|
52
52
|
--dry-run 预演目标项目变化,只比对不写盘
|
|
53
53
|
--json 仅与 --dry-run 一起使用,输出机器可读结果
|
package/bin/commands/init.js
CHANGED
|
@@ -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`);
|
package/bin/commands/sync.js
CHANGED
|
@@ -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`);
|
package/bin/project/template.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
99
|
-
|
|
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
|
}
|
package/config/default.json
CHANGED
package/package.json
CHANGED
package/scripts/sync-upstream.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { cp, readdir, readFile, rm, stat
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
await
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
192
|
-
const
|
|
193
|
-
const
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
package/template/AGENTS.md
CHANGED
|
@@ -1,33 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
27
|
+
|
|
28
|
+
完成时简要说明结果、关键修改和验证。
|
|
29
|
+
存在重要未验证项、已知限制、既有失败或剩余风险时明确指出。
|
|
30
|
+
有 commit 时报告 commit hash。
|