@heihei0299/matt-skills 2.0.2 → 2.1.2
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/ci-guard/SKILL.md +6 -2
- package/.agents/skills/commit-check/scripts/scan-sensitive.sh +14 -6
- package/.agents/skills/diagnose-fix/SKILL.md +16 -15
- package/.agents/skills/diagnose-fix/references/anti-patterns.md +5 -4
- package/.agents/skills/grill-to-spec/SKILL.md +4 -0
- package/.agents/skills/grill-to-spec/references/rules.md +19 -24
- package/.agents/skills/scaffold-functional-test/SKILL.md +9 -6
- package/.agents/skills/scaffold-functional-test/references/schema.md +56 -11
- package/.agents/skills/tdd-implement/SKILL.md +25 -14
- package/.agents/skills/tdd-implement/references/contract.md +21 -0
- package/.agents/skills/tdd-implement/references/finalize.md +27 -0
- package/.agents/skills/tdd-implement/references/orchestration.md +21 -16
- package/.agents/skills/tdd-implement/references/red-green.md +25 -0
- package/.agents/skills/tdd-implement/references/stages.md +25 -39
- package/.agents/skills/tdd-implement/references/verify.md +24 -0
- package/README.md +48 -27
- package/bin/cli.js +85 -24
- package/bin/skill-boundaries.js +61 -0
- package/config/proprietary.json +28 -9
- package/package.json +1 -1
- package/scripts/sync-upstream.js +4 -8
- package/template/.agents/skills/diagnose-fix/SKILL.md +16 -15
- package/template/.agents/skills/diagnose-fix/references/anti-patterns.md +5 -4
- package/template/.agents/skills/grill-to-spec/SKILL.md +4 -0
- package/template/.agents/skills/grill-to-spec/references/rules.md +19 -24
- package/template/.agents/skills/scaffold-functional-test/SKILL.md +9 -6
- package/template/.agents/skills/scaffold-functional-test/references/schema.md +56 -11
- package/template/.agents/skills/tdd-implement/SKILL.md +25 -14
- package/template/.agents/skills/tdd-implement/references/contract.md +21 -0
- package/template/.agents/skills/tdd-implement/references/finalize.md +27 -0
- package/template/.agents/skills/tdd-implement/references/orchestration.md +21 -16
- package/template/.agents/skills/tdd-implement/references/red-green.md +25 -0
- package/template/.agents/skills/tdd-implement/references/stages.md +25 -39
- package/template/.agents/skills/tdd-implement/references/verify.md +24 -0
- package/template/.opencode/CONTEXT.md +7 -7
- package/template/.opencode/docs/agents/skill-design.md +3 -3
- package/template/.opencode/skills/README.md +1 -1
- package/template/.pi/CONTEXT.md +7 -7
- package/template/.pi/docs/agents/skill-design.md +3 -3
- package/template/.pi/skills/README.md +1 -1
- package/template/AGENTS.md +49 -30
- package/template/.agents/skills/ci-guard/SKILL.md +0 -50
- package/template/.agents/skills/ci-guard/agents/openai.yaml +0 -5
- package/template/.agents/skills/commit-check/SKILL.md +0 -95
- package/template/.agents/skills/commit-check/agents/openai.yaml +0 -5
- package/template/.agents/skills/commit-check/scripts/scan-sensitive.sh +0 -29
- package/template/.opencode/commands/commit-check.md +0 -9
package/bin/cli.js
CHANGED
|
@@ -4,11 +4,17 @@ import os from 'node:os';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import prompts from 'prompts';
|
|
7
|
+
import {
|
|
8
|
+
PROPRIETARY_SKILLS,
|
|
9
|
+
isDefaultProgrammingSkill,
|
|
10
|
+
isDistributableProprietarySkill,
|
|
11
|
+
isDistributableSkill,
|
|
12
|
+
isRepoLocalSkill,
|
|
13
|
+
REPO_LOCAL_SKILLS,
|
|
14
|
+
} from './skill-boundaries.js';
|
|
7
15
|
|
|
8
16
|
const SKILLS_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', '.agents', 'skills');
|
|
9
17
|
const TEMPLATE_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'template');
|
|
10
|
-
const PROPRIETARY_SKILLS = new Set(['ci-guard', 'tdd-implement', 'grill-to-spec', 'diagnose-fix', 'commit-check', 'scaffold-functional-test', 'show-me']);
|
|
11
|
-
const PROPRIETARY_DEFAULT = new Set(['tdd-implement', 'diagnose-fix', 'commit-check', 'grill-to-spec', 'show-me']); // 默认仅装核心 4 + show-me,--all 才装全部 7
|
|
12
18
|
const ENGINEERING_PATH = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'config', 'engineering.json');
|
|
13
19
|
const REQUIRED_PATH = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'config', 'required.json');
|
|
14
20
|
let ENGINEERING_SKILLS = null;
|
|
@@ -34,10 +40,10 @@ async function loadRequiredSkills() {
|
|
|
34
40
|
return REQUIRED_SKILLS;
|
|
35
41
|
}
|
|
36
42
|
function isProgrammingSkill(name, engineering, required) {
|
|
37
|
-
return
|
|
43
|
+
return isDefaultProgrammingSkill(name, engineering, required);
|
|
38
44
|
}
|
|
39
45
|
function isProgrammingAll(name, engineering) {
|
|
40
|
-
return
|
|
46
|
+
return isDistributableProprietarySkill(name) || engineering.has(name);
|
|
41
47
|
}
|
|
42
48
|
process.stdout.on('error', (err) => {
|
|
43
49
|
if (err.code === 'EPIPE') process.exit(0);
|
|
@@ -64,9 +70,9 @@ Usage:
|
|
|
64
70
|
|
|
65
71
|
Init options:
|
|
66
72
|
--dest <path> Target directory (default: current directory)
|
|
67
|
-
--all Include
|
|
73
|
+
--all Include all distributable skills; default only default programming skills
|
|
68
74
|
--help, -h Show this help
|
|
69
|
-
提示:已有 AGENTS.md 时普通 init 跳过;显式 init --all
|
|
75
|
+
提示:已有 AGENTS.md 时普通 init 跳过;显式 init --all 刷新模板并覆盖全量可分发 skills。
|
|
70
76
|
|
|
71
77
|
提示:matt-skills --help 查看全量
|
|
72
78
|
`;
|
|
@@ -82,7 +88,7 @@ Sync options:
|
|
|
82
88
|
--dest <path> Target directory (default: current directory)
|
|
83
89
|
--help, -h Show this help
|
|
84
90
|
|
|
85
|
-
说明:默认不带 --all
|
|
91
|
+
说明:默认不带 --all 仅增量同步默认 programming skill 且 AGENTS.md 有定制则跳过;--all 时同步全部可分发 skill 并强制更新 AGENTS.md。
|
|
86
92
|
|
|
87
93
|
提示:matt-skills --help 查看全量
|
|
88
94
|
`;
|
|
@@ -93,7 +99,7 @@ Usage:
|
|
|
93
99
|
matt-skills list [--all] [--json]
|
|
94
100
|
|
|
95
101
|
List options:
|
|
96
|
-
--all List all skills (default only
|
|
102
|
+
--all List all distributable skills (default only default programming skills)
|
|
97
103
|
--json Output as JSON
|
|
98
104
|
--help, -h Show this help
|
|
99
105
|
|
|
@@ -106,7 +112,7 @@ Usage:
|
|
|
106
112
|
matt-skills check [--all] [--json] [--upstream <url>] [--ref <ref>]
|
|
107
113
|
|
|
108
114
|
Check options:
|
|
109
|
-
--all Include
|
|
115
|
+
--all Include the full upstream comparison scope; default engineering + required upstream skills (proprietary excluded)
|
|
110
116
|
--json Output as JSON
|
|
111
117
|
--upstream <url> Upstream repo URL (default: https://github.com/mattpocock/skills.git)
|
|
112
118
|
--ref <ref> Upstream ref (default: HEAD)
|
|
@@ -122,7 +128,7 @@ Usage:
|
|
|
122
128
|
|
|
123
129
|
Install options:
|
|
124
130
|
--tools <a,b> Install for the given tools (codex, pi, opencode, claude); skips tool selection — 共享技能统一指向 .agents/skills,.pi/skills/.opencode/skills 仅用于项目自定义
|
|
125
|
-
--all Install all skills (default only
|
|
131
|
+
--all Install all distributable skills (default only default programming); skips skill selection
|
|
126
132
|
--force Overwrite existing skills
|
|
127
133
|
--global Install to the user's global skill directories
|
|
128
134
|
--project Install to project skill directories (default)
|
|
@@ -161,6 +167,7 @@ async function listSkills({ onlyProgramming = false } = {}) {
|
|
|
161
167
|
if (!entry.isDirectory()) continue;
|
|
162
168
|
if (entry.name.endsWith('.bak')) continue;
|
|
163
169
|
if (entry.name === 'skill-creator') continue;
|
|
170
|
+
if (isRepoLocalSkill(entry.name)) continue;
|
|
164
171
|
if (onlyProgramming && !isProgrammingSkill(entry.name, engineering, required)) continue;
|
|
165
172
|
let content;
|
|
166
173
|
try {
|
|
@@ -182,6 +189,13 @@ async function pathExists(p) {
|
|
|
182
189
|
return false;
|
|
183
190
|
}
|
|
184
191
|
}
|
|
192
|
+
|
|
193
|
+
function shouldCopyTemplatePath(src) {
|
|
194
|
+
const relative = path.relative(TEMPLATE_DIR, src);
|
|
195
|
+
const parts = relative.split(path.sep);
|
|
196
|
+
return !(parts[0] === '.agents' && parts[1] === 'skills' && isRepoLocalSkill(parts[2]));
|
|
197
|
+
}
|
|
198
|
+
|
|
185
199
|
const TOOLS = ['codex', 'pi', 'opencode', 'claude'];
|
|
186
200
|
|
|
187
201
|
// 统一源:共享技能全部在 .agents/skills,harness 的 .pi/skills/.opencode/skills 仅用于项目自定义
|
|
@@ -262,6 +276,12 @@ async function installCommand({ dest, all, force, tools, global }) {
|
|
|
262
276
|
process.stdout.write('未选择任何技能,未安装任何技能\n');
|
|
263
277
|
return;
|
|
264
278
|
}
|
|
279
|
+
const knownDistributable = new Set(skills.map((skill) => skill.name));
|
|
280
|
+
for (const name of selected) {
|
|
281
|
+
if (!isDistributableSkill(name, knownDistributable)) {
|
|
282
|
+
throw new Error(`${name} is repository-local or unavailable and cannot be distributed`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
265
285
|
for (const { tool, dir } of targets) {
|
|
266
286
|
let installed = 0;
|
|
267
287
|
let skipped = 0;
|
|
@@ -287,7 +307,11 @@ async function initCommand({ dest, all }) {
|
|
|
287
307
|
if (await pathExists(marker) && !all) {
|
|
288
308
|
process.stdout.write('模板已存在(AGENTS.md),跳过\n');
|
|
289
309
|
} else {
|
|
290
|
-
await cp(TEMPLATE_DIR, target, {
|
|
310
|
+
await cp(TEMPLATE_DIR, target, {
|
|
311
|
+
recursive: true,
|
|
312
|
+
force: true,
|
|
313
|
+
filter: shouldCopyTemplatePath,
|
|
314
|
+
});
|
|
291
315
|
process.stdout.write('模板:已复制(AGENTS.md、.agents/skills、.opencode/、.pi/)\n');
|
|
292
316
|
// 默认范围(engineering + 独有所需 + 默认独有),--all 才保留其余 productivity
|
|
293
317
|
if (onlyProgramming && path.resolve(target) !== path.resolve(path.join(path.dirname(fileURLToPath(import.meta.url)), '..'))) {
|
|
@@ -311,7 +335,7 @@ async function initCommand({ dest, all }) {
|
|
|
311
335
|
let installed = 0;
|
|
312
336
|
try {
|
|
313
337
|
const entries = await readdir(skillsDir, { withFileTypes: true });
|
|
314
|
-
installed = entries.filter((e) => e.isDirectory() && !e.name.endsWith('.bak') && e.name !== '.git' && e.name !== 'skill-creator').length;
|
|
338
|
+
installed = entries.filter((e) => e.isDirectory() && !e.name.endsWith('.bak') && e.name !== '.git' && e.name !== 'skill-creator' && !isRepoLocalSkill(e.name)).length;
|
|
315
339
|
} catch {}
|
|
316
340
|
const allSkillsFull = await listSkills({ onlyProgramming: false });
|
|
317
341
|
const engineeringForStats = await loadEngineeringSkills();
|
|
@@ -322,12 +346,12 @@ async function initCommand({ dest, all }) {
|
|
|
322
346
|
const displayTotal = onlyProgramming ? programmingCount : allSkillsFull.length;
|
|
323
347
|
const displayUpstream = onlyProgramming ? upstreamProg : upstreamFull;
|
|
324
348
|
if (path.resolve(skillsDir) === path.resolve(SKILLS_DIR)) {
|
|
325
|
-
process.stdout.write(`技能:已装 ${installed}、跳过 0
|
|
349
|
+
process.stdout.write(`技能:已装 ${installed}、跳过 0(可分发 ${allSkillsFull.length},含上游 ${upstreamFull};默认编程 ${programmingCount},含上游 ${upstreamProg})\n`);
|
|
326
350
|
} else {
|
|
327
351
|
if (onlyProgramming) {
|
|
328
|
-
process.stdout.write(`技能:已装 ${installed}
|
|
352
|
+
process.stdout.write(`技能:已装 ${installed}(默认编程 ${displayTotal},含上游 ${displayUpstream};可分发 ${allSkillsFull.length},含上游 ${upstreamFull})\n`);
|
|
329
353
|
} else {
|
|
330
|
-
process.stdout.write(`技能:已装 ${installed}
|
|
354
|
+
process.stdout.write(`技能:已装 ${installed}(可分发 ${displayTotal},含上游 ${displayUpstream})\n`);
|
|
331
355
|
}
|
|
332
356
|
}
|
|
333
357
|
process.stdout.write(`目标路径:${target}\n`);
|
|
@@ -340,7 +364,7 @@ async function syncCommand({ dest, all, dryRun, json, upstreamUrl, ref }) {
|
|
|
340
364
|
if (json) {
|
|
341
365
|
process.stdout.write(JSON.stringify({ head: cmp.head, counts: cmp.counts, result: cmp.result, onlyProgramming }, null, 2) + '\n');
|
|
342
366
|
} else {
|
|
343
|
-
const modeHint = onlyProgramming ? '(默认:engineering + 独有所需)' : '
|
|
367
|
+
const modeHint = onlyProgramming ? '(默认:engineering + 独有所需)' : '(全量上游)';
|
|
344
368
|
const lines = [];
|
|
345
369
|
lines.push(`上游 HEAD: ${cmp.head}`);
|
|
346
370
|
lines.push(`本地非独有: ${cmp.counts.local} 上游: ${cmp.counts.upstream} ${modeHint}`);
|
|
@@ -368,7 +392,11 @@ async function syncCommand({ dest, all, dryRun, json, upstreamUrl, ref }) {
|
|
|
368
392
|
// 模板同步:默认模式下过滤 skills,仅同步默认子集
|
|
369
393
|
async function copyTemplateFiltered() {
|
|
370
394
|
if (!onlyProgramming) {
|
|
371
|
-
await cp(TEMPLATE_DIR, target, {
|
|
395
|
+
await cp(TEMPLATE_DIR, target, {
|
|
396
|
+
recursive: true,
|
|
397
|
+
force: true,
|
|
398
|
+
filter: shouldCopyTemplatePath,
|
|
399
|
+
});
|
|
372
400
|
return;
|
|
373
401
|
}
|
|
374
402
|
// 默认范围:分别复制非 skills 部分,skills 由后续 allSkills 循环处理
|
|
@@ -391,7 +419,11 @@ async function syncCommand({ dest, all, dryRun, json, upstreamUrl, ref }) {
|
|
|
391
419
|
if (!(await pathExists(marker))) {
|
|
392
420
|
process.stdout.write('未检测到现有项目(AGENTS.md 不存在),将执行全新初始化\n');
|
|
393
421
|
if (onlyProgramming) await copyTemplateFiltered();
|
|
394
|
-
else await cp(TEMPLATE_DIR, target, {
|
|
422
|
+
else await cp(TEMPLATE_DIR, target, {
|
|
423
|
+
recursive: true,
|
|
424
|
+
force: true,
|
|
425
|
+
filter: shouldCopyTemplatePath,
|
|
426
|
+
});
|
|
395
427
|
process.stdout.write('模板:已复制(AGENTS.md、.agents/skills、.opencode/、.pi/)\n');
|
|
396
428
|
} else {
|
|
397
429
|
process.stdout.write('同步:检测到现有项目,将增量更新\n');
|
|
@@ -416,21 +448,30 @@ async function syncCommand({ dest, all, dryRun, json, upstreamUrl, ref }) {
|
|
|
416
448
|
}
|
|
417
449
|
} catch {}
|
|
418
450
|
} else {
|
|
419
|
-
await cp(path.join(TEMPLATE_DIR, '.agents'), path.join(target, '.agents'), {
|
|
451
|
+
await cp(path.join(TEMPLATE_DIR, '.agents'), path.join(target, '.agents'), {
|
|
452
|
+
recursive: true,
|
|
453
|
+
force: true,
|
|
454
|
+
filter: shouldCopyTemplatePath,
|
|
455
|
+
});
|
|
420
456
|
await cp(path.join(TEMPLATE_DIR, '.opencode'), path.join(target, '.opencode'), { recursive: true, force: true });
|
|
421
457
|
await cp(path.join(TEMPLATE_DIR, '.pi'), path.join(target, '.pi'), { recursive: true, force: true });
|
|
422
458
|
}
|
|
423
459
|
process.stdout.write('模板:已同步(AGENTS.md 跳过,已含定制)\n');
|
|
424
460
|
} else {
|
|
425
461
|
if (onlyProgramming) await copyTemplateFiltered();
|
|
426
|
-
else await cp(TEMPLATE_DIR, target, {
|
|
462
|
+
else await cp(TEMPLATE_DIR, target, {
|
|
463
|
+
recursive: true,
|
|
464
|
+
force: true,
|
|
465
|
+
filter: shouldCopyTemplatePath,
|
|
466
|
+
});
|
|
427
467
|
process.stdout.write('模板:已同步(AGENTS.md、.agents/skills、.opencode/、.pi/)\n');
|
|
428
468
|
}
|
|
429
469
|
}
|
|
430
|
-
// 技能同步:--all
|
|
470
|
+
// 技能同步:--all 仅更新同名可分发技能内容,存在则覆盖,不存在则新增,并更新 AGENTS.md(由上一步已处理);默认范围为默认 programming,不删多余
|
|
431
471
|
const entries = await readdir(SKILLS_DIR, { withFileTypes: true });
|
|
432
472
|
const allNames = entries.filter((e) => e.isDirectory() && !e.name.endsWith('.bak') && e.name !== 'skill-creator' && e.name !== '.git').map((e) => e.name);
|
|
433
473
|
let allSkills = allNames.sort();
|
|
474
|
+
allSkills = allSkills.filter((name) => !isRepoLocalSkill(name));
|
|
434
475
|
if (onlyProgramming) {
|
|
435
476
|
const engineering = await loadEngineeringSkills();
|
|
436
477
|
const required = await loadRequiredSkills();
|
|
@@ -438,6 +479,23 @@ async function syncCommand({ dest, all, dryRun, json, upstreamUrl, ref }) {
|
|
|
438
479
|
}
|
|
439
480
|
const skillsDir = path.join(target, '.agents', 'skills');
|
|
440
481
|
await mkdir(skillsDir, { recursive: true });
|
|
482
|
+
const preservedRepoLocal = [];
|
|
483
|
+
const preserveLocations = [
|
|
484
|
+
{
|
|
485
|
+
dir: skillsDir,
|
|
486
|
+
label: '.agents/skills',
|
|
487
|
+
isWorkspaceSource: path.resolve(skillsDir) === path.resolve(SKILLS_DIR),
|
|
488
|
+
},
|
|
489
|
+
{ dir: path.join(target, '.pi', 'skills'), label: '.pi/skills', isWorkspaceSource: false },
|
|
490
|
+
{ dir: path.join(target, '.opencode', 'skills'), label: '.opencode/skills', isWorkspaceSource: false },
|
|
491
|
+
];
|
|
492
|
+
for (const name of REPO_LOCAL_SKILLS) {
|
|
493
|
+
for (const location of preserveLocations) {
|
|
494
|
+
if (!location.isWorkspaceSource && await pathExists(path.join(location.dir, name))) {
|
|
495
|
+
preservedRepoLocal.push(`${location.label}/${name}`);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
441
499
|
let installed = 0;
|
|
442
500
|
let updated = 0;
|
|
443
501
|
for (const name of allSkills) {
|
|
@@ -468,7 +526,7 @@ async function syncCommand({ dest, all, dryRun, json, upstreamUrl, ref }) {
|
|
|
468
526
|
if (!e.isDirectory()) continue;
|
|
469
527
|
if (e.name === '.git' || e.name.endsWith('.bak')) continue;
|
|
470
528
|
if (e.name === '.gitkeep' || e.name === 'README.md') continue;
|
|
471
|
-
if (allSkills.includes(e.name)
|
|
529
|
+
if (allSkills.includes(e.name)) {
|
|
472
530
|
await rm(path.join(dir, e.name), { recursive: true, force: true });
|
|
473
531
|
}
|
|
474
532
|
}
|
|
@@ -484,7 +542,10 @@ async function syncCommand({ dest, all, dryRun, json, upstreamUrl, ref }) {
|
|
|
484
542
|
}
|
|
485
543
|
}
|
|
486
544
|
} catch {}
|
|
487
|
-
|
|
545
|
+
if (preservedRepoLocal.length) {
|
|
546
|
+
process.stdout.write(`迁移提示:${preservedRepoLocal.join(', ')} 已不再分发,现有副本已保留\n`);
|
|
547
|
+
}
|
|
548
|
+
const modeLabel = onlyProgramming ? '默认编程' : '全部可分发';
|
|
488
549
|
process.stdout.write(`技能:新增 ${installed}、更新 ${updated}(${modeLabel} ${allSkills.length})\n`);
|
|
489
550
|
process.stdout.write(`目标路径:${target}\n`);
|
|
490
551
|
}
|
|
@@ -584,7 +645,7 @@ async function checkCommand(args) {
|
|
|
584
645
|
} else {
|
|
585
646
|
const lines = [];
|
|
586
647
|
lines.push(`上游 HEAD: ${cmp.head}`);
|
|
587
|
-
const modeHint = onlyProgramming ? '(默认:engineering + 独有所需)' : '
|
|
648
|
+
const modeHint = onlyProgramming ? '(默认:engineering + 独有所需)' : '(全量上游)';
|
|
588
649
|
lines.push(`本地非独有: ${cmp.counts.local} 上游: ${cmp.counts.upstream} ${modeHint}`);
|
|
589
650
|
lines.push('');
|
|
590
651
|
const totalDiff = cmp.result.added.length + cmp.result.updated.length + cmp.result.removed.length + cmp.result.renamed.length;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
6
|
+
const CONFIG_PATH = path.join(ROOT, 'config', 'proprietary.json');
|
|
7
|
+
const config = JSON.parse(readFileSync(CONFIG_PATH, 'utf8'));
|
|
8
|
+
|
|
9
|
+
function toUniqueSet(value, key) {
|
|
10
|
+
if (!Array.isArray(value) || new Set(value).size !== value.length) {
|
|
11
|
+
throw new Error(`invalid proprietary classification: ${key} must be a unique array`);
|
|
12
|
+
}
|
|
13
|
+
return new Set(value);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const PROPRIETARY_SKILLS = toUniqueSet(config.all, 'all');
|
|
17
|
+
export const DISTRIBUTABLE_PROPRIETARY_SKILLS = toUniqueSet(config.distributable, 'distributable');
|
|
18
|
+
export const REPO_LOCAL_SKILLS = toUniqueSet(config.repoLocal, 'repoLocal');
|
|
19
|
+
export const DEFAULT_PROPRIETARY_SKILLS = toUniqueSet(config.default, 'default');
|
|
20
|
+
|
|
21
|
+
const classified = new Set([...DISTRIBUTABLE_PROPRIETARY_SKILLS, ...REPO_LOCAL_SKILLS]);
|
|
22
|
+
const missing = [...PROPRIETARY_SKILLS].filter((name) => !classified.has(name));
|
|
23
|
+
const extra = [...classified].filter((name) => !PROPRIETARY_SKILLS.has(name));
|
|
24
|
+
const overlap = [...DISTRIBUTABLE_PROPRIETARY_SKILLS].filter((name) => REPO_LOCAL_SKILLS.has(name));
|
|
25
|
+
const invalidDefaults = [...DEFAULT_PROPRIETARY_SKILLS].filter((name) => !DISTRIBUTABLE_PROPRIETARY_SKILLS.has(name));
|
|
26
|
+
if (missing.length || extra.length || overlap.length || invalidDefaults.length) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
`invalid proprietary classification: missing=${missing.join(',')} extra=${extra.join(',')} ` +
|
|
29
|
+
`overlap=${overlap.join(',')} defaults=${invalidDefaults.join(',')}`,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function isProprietarySkill(name) {
|
|
34
|
+
return PROPRIETARY_SKILLS.has(name);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function isDistributableProprietarySkill(name) {
|
|
38
|
+
return DISTRIBUTABLE_PROPRIETARY_SKILLS.has(name);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function isRepoLocalSkill(name) {
|
|
42
|
+
return REPO_LOCAL_SKILLS.has(name);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function isDefaultProprietarySkill(name) {
|
|
46
|
+
return DEFAULT_PROPRIETARY_SKILLS.has(name);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function isDefaultProgrammingSkill(name, engineering, required) {
|
|
50
|
+
return (
|
|
51
|
+
DEFAULT_PROPRIETARY_SKILLS.has(name) ||
|
|
52
|
+
engineering.has(name) ||
|
|
53
|
+
(required && required.has(name))
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function isDistributableSkill(name, knownNames) {
|
|
58
|
+
if (!knownNames) return isDistributableProprietarySkill(name);
|
|
59
|
+
const known = knownNames instanceof Set ? knownNames : new Set(knownNames);
|
|
60
|
+
return known.has(name) && !REPO_LOCAL_SKILLS.has(name);
|
|
61
|
+
}
|
package/config/proprietary.json
CHANGED
|
@@ -1,9 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
"
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
{
|
|
2
|
+
"all": [
|
|
3
|
+
"ci-guard",
|
|
4
|
+
"tdd-implement",
|
|
5
|
+
"grill-to-spec",
|
|
6
|
+
"diagnose-fix",
|
|
7
|
+
"commit-check",
|
|
8
|
+
"scaffold-functional-test",
|
|
9
|
+
"show-me"
|
|
10
|
+
],
|
|
11
|
+
"distributable": [
|
|
12
|
+
"tdd-implement",
|
|
13
|
+
"diagnose-fix",
|
|
14
|
+
"grill-to-spec",
|
|
15
|
+
"scaffold-functional-test",
|
|
16
|
+
"show-me"
|
|
17
|
+
],
|
|
18
|
+
"repoLocal": [
|
|
19
|
+
"ci-guard",
|
|
20
|
+
"commit-check"
|
|
21
|
+
],
|
|
22
|
+
"default": [
|
|
23
|
+
"tdd-implement",
|
|
24
|
+
"diagnose-fix",
|
|
25
|
+
"grill-to-spec",
|
|
26
|
+
"show-me"
|
|
27
|
+
]
|
|
28
|
+
}
|
package/package.json
CHANGED
package/scripts/sync-upstream.js
CHANGED
|
@@ -5,10 +5,10 @@ import { spawnSync } from 'node:child_process';
|
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import os from 'node:os';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import { PROPRIETARY_SKILLS } from '../bin/skill-boundaries.js';
|
|
8
9
|
|
|
9
10
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
10
11
|
const LOCAL_SKILLS_DIR = path.join(ROOT, '.agents', 'skills');
|
|
11
|
-
const PROPRIETARY_PATH = path.join(ROOT, 'config', 'proprietary.json');
|
|
12
12
|
const ENGINEERING_PATH = path.join(ROOT, 'config', 'engineering.json');
|
|
13
13
|
const REQUIRED_PATH = path.join(ROOT, 'config', 'required.json');
|
|
14
14
|
const UPSTREAM_URL = 'https://github.com/mattpocock/skills.git';
|
|
@@ -19,12 +19,7 @@ const RENAMES = {
|
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
async function loadProprietary() {
|
|
22
|
-
|
|
23
|
-
const raw = await readFile(PROPRIETARY_PATH, 'utf8');
|
|
24
|
-
return new Set(JSON.parse(raw));
|
|
25
|
-
} catch {
|
|
26
|
-
return new Set(['ci-guard', 'tdd-implement', 'grill-to-spec', 'diagnose-fix', 'commit-check', 'scaffold-functional-test', 'show-me']);
|
|
27
|
-
}
|
|
22
|
+
return new Set(PROPRIETARY_SKILLS);
|
|
28
23
|
}
|
|
29
24
|
|
|
30
25
|
async function loadEngineering() {
|
|
@@ -210,7 +205,8 @@ export async function applySync({ upstreamUrl, tmpDir, ref, dryRun = false, forc
|
|
|
210
205
|
const actions = [];
|
|
211
206
|
|
|
212
207
|
if (dryRun) {
|
|
213
|
-
|
|
208
|
+
await rm(dest, { recursive: true, force: true });
|
|
209
|
+
return { ...cmp, dest: null, actions, dryRun: true };
|
|
214
210
|
}
|
|
215
211
|
|
|
216
212
|
// 处理重命名
|
|
@@ -1,42 +1,43 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: diagnose-fix
|
|
3
|
-
description: "Complete diagnosis→fix→regression channel for bugs: diagnose, then fix
|
|
3
|
+
description: "Complete diagnosis→fix→regression channel for bugs and performance regressions: diagnose, then fix behind observed red regression evidence. Functional bugs require a failing regression test; performance regressions may use a benchmark/perf harness with an explicit failing threshold. Use when the user says diagnose/debug/fix this, or reports something broken/throwing/failing/slow — prefer this over diagnosing-bugs when a fix is wanted, not just a diagnosis."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Diagnose Fix
|
|
7
7
|
|
|
8
|
-
本 skill
|
|
8
|
+
本 skill 只连接诊断、受保护的最小修复和回归三个阶段。诊断细节以 [`diagnosing-bugs`](.agents/skills/diagnosing-bugs/SKILL.md) 为事实源,普通功能 bug 的红绿语义以 [`tdd`](.agents/skills/tdd/SKILL.md) 为事实源;不复制两个上游的完整步骤。
|
|
9
9
|
|
|
10
10
|
## 流程
|
|
11
11
|
|
|
12
12
|
### ① 诊断
|
|
13
13
|
|
|
14
|
-
委托 `diagnosing-bugs`
|
|
14
|
+
委托 `diagnosing-bugs` 完成修复前的诊断活动:建立能捕捉用户症状的 tight feedback loop,实际复现并最小化,再形成可证伪的假设并按单变量探针验证。不要依赖固定 Phase 编号;以上游当前“进入修复前必须完成的诊断出口”为事实源。
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
出口:反馈回路已实际变红,最小复现已确认,且导致症状的假设已有证据;此阶段不写修复代码。
|
|
17
17
|
|
|
18
|
-
### ②
|
|
18
|
+
### ② 受保护的最小修复
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
2. 加载 `tdd`,先把最小复现转成失败回归测试并实际看到 Red。
|
|
22
|
-
3. 只写让该测试 Green 的最小修复,并遵循 `tdd` 的红绿循环。
|
|
20
|
+
先判断 Red 证据类型:
|
|
23
21
|
|
|
24
|
-
|
|
22
|
+
- **功能 bug**:在正确的公共 seam 上提出回归测试边界,遵循 `tdd` 要求获得用户确认;把最小复现转成失败回归测试并实际看到 Red,再写最小修复直到 Green。
|
|
23
|
+
- **性能回归**:复用 `diagnosing-bugs` 的 performance branch,建立可重复的 benchmark、timing harness、query-count 或 profiler-derived threshold;必须先实际观察到该阈值失败,再做最小修复,并用同一测量方式确认转绿。不为满足形式强造一个无法代表真实性能症状的普通单元测试。
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
**硬门槛:**不存在已实际观察到的 **red-capable regression evidence** 时,不得写修复代码。功能 bug 的证据必须是正确 seam 上的失败回归测试;性能回归可使用带明确阈值且可重复的 benchmark/perf harness。不存在正确 seam 或可靠性能测量边界时,本身就是 finding;记录阻塞,不绕过证据直接修改。
|
|
26
|
+
|
|
27
|
+
出口:功能 bug 为 regression test Red → 最小修复 → Green;性能回归为 benchmark/perf threshold Red → 最小修复 → Green。两者都不进入 `tdd-implement` 的长流程。
|
|
27
28
|
|
|
28
29
|
### ③ 回归收尾
|
|
29
30
|
|
|
30
|
-
按 `diagnosing-bugs` 的收尾要求重跑阶段 ①
|
|
31
|
+
按 `diagnosing-bugs` 的收尾要求重跑阶段 ① 的原始、未最小化反馈回路,确认用户症状消失;功能 bug 重跑回归测试,性能回归重跑原始基准/测量;清理 `[DEBUG-...]` 探针和一次性 harness,并记录最终验证的假设。
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
出口:原始症状消失、对应 regression evidence 为 Green、临时诊断产物已清理。
|
|
33
34
|
|
|
34
35
|
## 回合连续性
|
|
35
36
|
|
|
36
|
-
诊断 →
|
|
37
|
+
诊断 → Red 证据 → 修复 → Green → 原始回路 → 清理在一个回合内连续推进。只有功能 bug 的 seam 确认、发现无正确 seam/测量边界、外部环境阻塞或整个阶段出口可以暂停;预告下一步后立即执行。
|
|
37
38
|
|
|
38
39
|
## 引用
|
|
39
40
|
|
|
40
|
-
-
|
|
41
|
-
- 修复:[`tdd`](.agents/skills/tdd/SKILL.md)
|
|
41
|
+
- 诊断与性能分支:[`diagnosing-bugs`](.agents/skills/diagnosing-bugs/SKILL.md)
|
|
42
|
+
- 功能 bug 修复:[`tdd`](.agents/skills/tdd/SKILL.md)
|
|
42
43
|
- 负向边界:[`references/anti-patterns.md`](references/anti-patterns.md)
|
|
@@ -5,14 +5,15 @@ SKILL.md 正文各阶段规则是正面约束;本文件是负向边界(不
|
|
|
5
5
|
## 诊断阶段
|
|
6
6
|
|
|
7
7
|
- 不跳过反馈回路直接猜根因:回路未红之前不进入假设、不写修复代码
|
|
8
|
-
-
|
|
8
|
+
- 功能 bug 不把最小复现永久留在 harness 里:必须转写为正确 seam 上的回归测试;性能回归可保留最小、可重复且有明确阈值的 benchmark/perf harness 作为回归证据
|
|
9
9
|
- 不一次改多个变量:探针一次只改一个,`[DEBUG-...]` 前缀标记
|
|
10
10
|
|
|
11
11
|
## 修复阶段
|
|
12
12
|
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
13
|
+
- 不绕过 red-capable regression evidence 直接改代码——功能 bug 没有正确 seam 是 finding;性能回归没有可靠测量边界同样是 finding,不是豁免
|
|
14
|
+
- 不为性能问题强造一个不能代表真实慢路径的普通单元测试;优先复用 diagnosis 中已经验证过的 benchmark、timing、query-count 或 profiler-derived threshold
|
|
15
|
+
- 不套用 tdd-implement 重流程:单个 bug/perf regression 的修复保持轻量,不引入完整 delivery orchestration
|
|
16
|
+
- 不重写 tdd 技能的红-绿语义:功能 bug 的 seam 定义、好测试标准、mocking 边界一律查上游技能
|
|
16
17
|
|
|
17
18
|
## 回归阶段
|
|
18
19
|
|
|
@@ -30,6 +30,10 @@ disable-model-invocation: true
|
|
|
30
30
|
|
|
31
31
|
出口:spec 已发布,路径、状态和未纳入范围已报告。
|
|
32
32
|
|
|
33
|
+
## 回合连续性
|
|
34
|
+
|
|
35
|
+
本 skill 是 Long-Horizon Skill。阶段 ① 达到出口后立即进入阶段 ②;正常的阶段切换、进度汇报或“接下来生成 spec”不是回合终点。仅在必须获得用户确认的 ADR/spec 草稿、明确外部阻塞、用户主动停止或整个 skill 出口时暂停。确认完成后在同一任务链继续推进到下一可验证出口,不要求用户额外回复“继续”。
|
|
36
|
+
|
|
33
37
|
## 本 skill 独有门禁
|
|
34
38
|
|
|
35
39
|
- ADR:草稿 → 用户确认 → 落盘,任何情况不例外;
|
|
@@ -1,33 +1,28 @@
|
|
|
1
|
-
# 守则:Grill-to-Spec
|
|
1
|
+
# 守则:Grill-to-Spec 本地增量规则
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
本文件只保存 `grill-to-spec` 相对上游 `grill-with-docs` / `to-spec` 的**增量约束**。Spec 的章节、User Story 形状、Implementation Decisions 等格式全部以 `to-spec` 为唯一事实源,本文件不复制上游模板。
|
|
4
4
|
|
|
5
|
-
## Glossary
|
|
5
|
+
## Glossary 增量规则
|
|
6
6
|
|
|
7
|
-
- 懒创建:首个术语解析时才建 `CONTEXT.md
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
- 定义 WHAT 非 HOW,1-2 句;opinionated,同义词列 `_Avoid_`;术语解析即 inline 更新,不批量
|
|
7
|
+
- 懒创建:首个术语解析时才建 `CONTEXT.md`;多上下文时先确认归属,归属不清则询问。
|
|
8
|
+
- 只收本上下文特有术语;定义 WHAT 非 HOW,避免把 glossary 变成实现草稿。
|
|
9
|
+
- glossary 可按上游流程 inline 更新,不额外增加确认轮次。
|
|
11
10
|
|
|
12
|
-
## ADR
|
|
11
|
+
## ADR 增量规则
|
|
13
12
|
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
- 草稿经用户显式确认后落盘,任何情况无例外(见 SKILL.md 流程① 与不可协商规则)
|
|
13
|
+
- 只有同时满足“难逆转 / 无上下文费解 / 存在真实权衡”时才提议 ADR。
|
|
14
|
+
- ADR 草稿必须完整展示并获得用户明确确认后才落盘;这是本 skill 的独有硬门禁。
|
|
15
|
+
- 不把 ADR 当 glossary 一样静默 inline 更新。
|
|
18
16
|
|
|
19
|
-
## Spec
|
|
17
|
+
## Spec 增量规则
|
|
20
18
|
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
- seams:既有优先于新建、取最高、理想数量 1,与用户确认
|
|
26
|
-
- 发布后标 `ready-for-agent`,triage 状态以 issue 文件顶部 `Status:` 行记录
|
|
19
|
+
- Spec 的结构与字段全部委托 `to-spec`,本文件不维护第二份模板。
|
|
20
|
+
- seam 提案并入最终 spec 草稿,不再制造独立的一次确认。
|
|
21
|
+
- 发布前只做一次最终 spec 明确确认;确认后写入并标记 `ready-for-agent`。
|
|
22
|
+
- 全文沿用已经确认的 glossary 词汇,并尊重所触区域既有 ADR。
|
|
27
23
|
|
|
28
|
-
##
|
|
24
|
+
## 反模式
|
|
29
25
|
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
- 不在本文件之外重复守则细节——SKILL.md 与 references 之间信息只在一处存在
|
|
26
|
+
- 不复制 `to-spec` 的章节清单、User Story 模板或 Implementation Decisions 细则。
|
|
27
|
+
- 不产出 Glossary / ADR / Spec 之外的额外设计文件。
|
|
28
|
+
- 不把本文件当逐条朗读的对话脚本;它只约束本地增量行为。
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: scaffold-functional-test
|
|
3
3
|
disable-model-invocation: false
|
|
4
|
-
description: "Scaffold a repo-specific functional-test skill from spec — use when the user wants to generate a customized functional-test suite/skill from a spec/README/help;
|
|
4
|
+
description: "Scaffold a repo-specific functional-test skill from spec — use when the user wants to generate a customized functional-test suite/skill from a spec/README/help; supports CLI, HTTP, browser, and file-oriented behaviors; not for regular instance execution nor for TDD"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Scaffold Functional Test
|
|
@@ -22,9 +22,11 @@ spec 不存在时可从 README 与 `--help` 建立候选清单,但必须把它
|
|
|
22
22
|
|
|
23
23
|
### ② 推导并确认实例
|
|
24
24
|
|
|
25
|
-
按 [`references/schema.md`](references/schema.md)
|
|
25
|
+
按 [`references/schema.md`](references/schema.md) 为每个行为选择最接近真实用户路径的实例类型:`cli`、`http`、`browser` 或 `file`。每个实例必须有溯源,不能用无来源的隐含行为扩张范围,也不能为了统一格式给 browser/http 行为强塞无意义的 stdout/exit-code 字段。
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
向用户展示实例清单并等待一次确认;确认前不落盘。
|
|
28
|
+
|
|
29
|
+
出口:实例清单已确认,每个实例类型、字段和断言完整且可追溯。
|
|
28
30
|
|
|
29
31
|
### ③ 生成或更新
|
|
30
32
|
|
|
@@ -39,19 +41,20 @@ spec 不存在时可从 README 与 `--help` 建立候选清单,但必须把它
|
|
|
39
41
|
|
|
40
42
|
### ④ 结构验证
|
|
41
43
|
|
|
42
|
-
生成后立即做快速、确定性的结构验证:文件存在、schema
|
|
44
|
+
生成后立即做快速、确定性的结构验证:文件存在、schema 字段、实例类型、实例溯源、spec hash、`generatedAt`、manual 段保护和内部链接均通过后再报告成功。不默认执行完整实例集,不启动服务,不产生功能测试副作用。
|
|
43
45
|
|
|
44
46
|
出口:结构验证结果为 `PASS`,失败则报告具体 gap,不回滚生成物。
|
|
45
47
|
|
|
46
48
|
## 可选行为验证
|
|
47
49
|
|
|
48
|
-
仅当用户明确要求运行实例集时,才调用生成的功能测试 skill
|
|
50
|
+
仅当用户明确要求运行实例集时,才调用生成的功能测试 skill 执行隔离、串行的实例验证;执行器按实例 `type` 捕获对应 evidence:CLI 的 stdout/stderr/exit code,HTTP 的 status/body/headers,browser 的页面/DOM/network/console 证据,file 的文件与内容 diff,并报告 expected-vs-actual 与 `PASS m/n`。这不是 scaffold 的默认步骤。
|
|
49
51
|
|
|
50
52
|
## 不做什么
|
|
51
53
|
|
|
52
54
|
- 不替代生成后的功能测试 skill;
|
|
53
55
|
- 不替代 `tdd`、`tdd-implement` 或 `commit-check`;
|
|
54
|
-
- 不覆盖 `<!-- manual -->` 段,不静默重生成,不把 README/`--help`
|
|
56
|
+
- 不覆盖 `<!-- manual -->` 段,不静默重生成,不把 README/`--help` 推断写成无溯源实例;
|
|
57
|
+
- 不把所有行为强制降格成 CLI 测试。
|
|
55
58
|
|
|
56
59
|
## 引用
|
|
57
60
|
|