@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.
Files changed (47) hide show
  1. package/.agents/skills/ci-guard/SKILL.md +6 -2
  2. package/.agents/skills/commit-check/scripts/scan-sensitive.sh +14 -6
  3. package/.agents/skills/diagnose-fix/SKILL.md +16 -15
  4. package/.agents/skills/diagnose-fix/references/anti-patterns.md +5 -4
  5. package/.agents/skills/grill-to-spec/SKILL.md +4 -0
  6. package/.agents/skills/grill-to-spec/references/rules.md +19 -24
  7. package/.agents/skills/scaffold-functional-test/SKILL.md +9 -6
  8. package/.agents/skills/scaffold-functional-test/references/schema.md +56 -11
  9. package/.agents/skills/tdd-implement/SKILL.md +25 -14
  10. package/.agents/skills/tdd-implement/references/contract.md +21 -0
  11. package/.agents/skills/tdd-implement/references/finalize.md +27 -0
  12. package/.agents/skills/tdd-implement/references/orchestration.md +21 -16
  13. package/.agents/skills/tdd-implement/references/red-green.md +25 -0
  14. package/.agents/skills/tdd-implement/references/stages.md +25 -39
  15. package/.agents/skills/tdd-implement/references/verify.md +24 -0
  16. package/README.md +48 -27
  17. package/bin/cli.js +85 -24
  18. package/bin/skill-boundaries.js +61 -0
  19. package/config/proprietary.json +28 -9
  20. package/package.json +1 -1
  21. package/scripts/sync-upstream.js +4 -8
  22. package/template/.agents/skills/diagnose-fix/SKILL.md +16 -15
  23. package/template/.agents/skills/diagnose-fix/references/anti-patterns.md +5 -4
  24. package/template/.agents/skills/grill-to-spec/SKILL.md +4 -0
  25. package/template/.agents/skills/grill-to-spec/references/rules.md +19 -24
  26. package/template/.agents/skills/scaffold-functional-test/SKILL.md +9 -6
  27. package/template/.agents/skills/scaffold-functional-test/references/schema.md +56 -11
  28. package/template/.agents/skills/tdd-implement/SKILL.md +25 -14
  29. package/template/.agents/skills/tdd-implement/references/contract.md +21 -0
  30. package/template/.agents/skills/tdd-implement/references/finalize.md +27 -0
  31. package/template/.agents/skills/tdd-implement/references/orchestration.md +21 -16
  32. package/template/.agents/skills/tdd-implement/references/red-green.md +25 -0
  33. package/template/.agents/skills/tdd-implement/references/stages.md +25 -39
  34. package/template/.agents/skills/tdd-implement/references/verify.md +24 -0
  35. package/template/.opencode/CONTEXT.md +7 -7
  36. package/template/.opencode/docs/agents/skill-design.md +3 -3
  37. package/template/.opencode/skills/README.md +1 -1
  38. package/template/.pi/CONTEXT.md +7 -7
  39. package/template/.pi/docs/agents/skill-design.md +3 -3
  40. package/template/.pi/skills/README.md +1 -1
  41. package/template/AGENTS.md +49 -30
  42. package/template/.agents/skills/ci-guard/SKILL.md +0 -50
  43. package/template/.agents/skills/ci-guard/agents/openai.yaml +0 -5
  44. package/template/.agents/skills/commit-check/SKILL.md +0 -95
  45. package/template/.agents/skills/commit-check/agents/openai.yaml +0 -5
  46. package/template/.agents/skills/commit-check/scripts/scan-sensitive.sh +0 -29
  47. 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 PROPRIETARY_DEFAULT.has(name) || engineering.has(name) || (required && required.has(name));
43
+ return isDefaultProgrammingSkill(name, engineering, required);
38
44
  }
39
45
  function isProgrammingAll(name, engineering) {
40
- return PROPRIETARY_SKILLS.has(name) || engineering.has(name);
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 non-programming skills (productivity) and optional proprietary; default only core programming (engineering 18 + required 4 + default proprietary 4 → 26)
73
+ --all Include all distributable skills; default only default programming skills
68
74
  --help, -h Show this help
69
- 提示:已有 AGENTS.md 时普通 init 跳过;显式 init --all 刷新模板并覆盖全量 skills。
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 仅增量同步默认技能(26)且 AGENTS.md 有定制则跳过;--all 时对同名技能 upsert 并强制更新 AGENTS.md。
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 core programming 26)
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 non-programming and optional proprietary; default only core programming (26: engineering 18 + required 4 + default proprietary 4)
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 core programming 26); skips skill selection
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, { recursive: true, force: true });
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(全量 ${allSkillsFull.length},含上游 ${upstreamFull};编程 ${programmingCount},含上游 ${upstreamProg})\n`);
349
+ process.stdout.write(`技能:已装 ${installed}、跳过 0(可分发 ${allSkillsFull.length},含上游 ${upstreamFull};默认编程 ${programmingCount},含上游 ${upstreamProg})\n`);
326
350
  } else {
327
351
  if (onlyProgramming) {
328
- process.stdout.write(`技能:已装 ${installed}(编程 ${displayTotal},含上游 ${displayUpstream};全量 ${allSkillsFull.length},含上游 ${upstreamFull})\n`);
352
+ process.stdout.write(`技能:已装 ${installed}(默认编程 ${displayTotal},含上游 ${displayUpstream};可分发 ${allSkillsFull.length},含上游 ${upstreamFull})\n`);
329
353
  } else {
330
- process.stdout.write(`技能:已装 ${installed}(全量 ${displayTotal},含上游 ${displayUpstream})\n`);
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, { recursive: true, force: true });
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, { recursive: true, force: true });
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'), { recursive: true, force: true });
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, { recursive: true, force: true });
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 仅更新同名技能内容,存在则覆盖,不存在则新增,并更新 AGENTS.md(由上一步已处理);默认范围 26,--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) || PROPRIETARY_SKILLS.has(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
- const modeLabel = onlyProgramming ? '编程' : '全量';
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
+ }
@@ -1,9 +1,28 @@
1
- [
2
- "ci-guard",
3
- "tdd-implement",
4
- "grill-to-spec",
5
- "diagnose-fix",
6
- "commit-check",
7
- "scaffold-functional-test",
8
- "show-me"
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heihei0299/matt-skills",
3
- "version": "2.0.2",
3
+ "version": "2.1.2",
4
4
  "description": "Agent skills + 项目配置模板:一条命令初始化 opencode / pi-agent 项目(含 mattpocock/skills 上游技能)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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
- try {
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
- return { ...cmp, actions, dryRun: true };
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 via a TDD red-green loop with a hard gate (no fix code before a failing regression test). 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."
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 只连接诊断、TDD 修复和回归三个阶段。诊断细节以 [`diagnosing-bugs`](.agents/skills/diagnosing-bugs/SKILL.md) 为事实源,红绿语义以 [`tdd`](.agents/skills/tdd/SKILL.md) 为事实源;不复制两个上游的完整步骤。
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` 完成 Phase 1–4:建立能捕捉用户症状的 tight feedback loop,实际复现并最小化,再形成可证伪的假设并按单变量探针验证。
14
+ 委托 `diagnosing-bugs` 完成修复前的诊断活动:建立能捕捉用户症状的 tight feedback loop,实际复现并最小化,再形成可证伪的假设并按单变量探针验证。不要依赖固定 Phase 编号;以上游当前“进入修复前必须完成的诊断出口”为事实源。
15
15
 
16
- 出口:反馈回路已实际变红,且最小复现已确认;此阶段不写修复代码。
16
+ 出口:反馈回路已实际变红,最小复现已确认,且导致症状的假设已有证据;此阶段不写修复代码。
17
17
 
18
- ### ② TDD 修复
18
+ ### ② 受保护的最小修复
19
19
 
20
- 1. 在正确的公共 seam 上提出回归测试边界,并遵循 `tdd` 要求获得用户确认;只确认本次 seam,不建立重型 seam ledger。
21
- 2. 加载 `tdd`,先把最小复现转成失败回归测试并实际看到 Red。
22
- 3. 只写让该测试 Green 的最小修复,并遵循 `tdd` 的红绿循环。
20
+ 先判断 Red 证据类型:
23
21
 
24
- **硬门槛:**不存在已实际观察到的失败回归测试时,不得写任何修复代码。不存在正确 seam 时,本身就是 finding;记录架构阻塞,不绕过测试直接修改。
22
+ - **功能 bug**:在正确的公共 seam 上提出回归测试边界,遵循 `tdd` 要求获得用户确认;把最小复现转成失败回归测试并实际看到 Red,再写最小修复直到 Green。
23
+ - **性能回归**:复用 `diagnosing-bugs` 的 performance branch,建立可重复的 benchmark、timing harness、query-count 或 profiler-derived threshold;必须先实际观察到该阈值失败,再做最小修复,并用同一测量方式确认转绿。不为满足形式强造一个无法代表真实性能症状的普通单元测试。
25
24
 
26
- 出口:回归测试先 Red,最小修复后 Green;不进入 `tdd-implement` 的长流程。
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` 的收尾要求重跑阶段 ① 的原始、未最小化反馈回路,确认用户症状消失;清理 `[DEBUG-...]` 探针和一次性 harness,并记录最终验证的假设。
31
+ 按 `diagnosing-bugs` 的收尾要求重跑阶段 ① 的原始、未最小化反馈回路,确认用户症状消失;功能 bug 重跑回归测试,性能回归重跑原始基准/测量;清理 `[DEBUG-...]` 探针和一次性 harness,并记录最终验证的假设。
31
32
 
32
- 出口:原始症状消失、回归测试 Green、临时诊断产物已清理。
33
+ 出口:原始症状消失、对应 regression evidence 为 Green、临时诊断产物已清理。
33
34
 
34
35
  ## 回合连续性
35
36
 
36
- 诊断 → seam 确认Red → 修复 → Green → 原始回路 → 清理在一个回合内连续推进。只在用户必须确认 seam、发现无 seam finding、外部环境阻塞或整个阶段出口时暂停;预告下一步后立即执行。
37
+ 诊断 → Red 证据 → 修复 → Green → 原始回路 → 清理在一个回合内连续推进。只有功能 bug 的 seam 确认、发现无正确 seam/测量边界、外部环境阻塞或整个阶段出口可以暂停;预告下一步后立即执行。
37
38
 
38
39
  ## 引用
39
40
 
40
- - 诊断:[`diagnosing-bugs`](.agents/skills/diagnosing-bugs/SKILL.md)
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
- - 不把最小复现留在 harness 里:必须转写为正确 seam 上的回归测试,harness 只作诊断工具
8
+ - 功能 bug 不把最小复现永久留在 harness 里:必须转写为正确 seam 上的回归测试;性能回归可保留最小、可重复且有明确阈值的 benchmark/perf harness 作为回归证据
9
9
  - 不一次改多个变量:探针一次只改一个,`[DEBUG-...]` 前缀标记
10
10
 
11
11
  ## 修复阶段
12
12
 
13
- - 不绕过测试直接改代码(见 SKILL.md ②无逃生舱)——没有 seam 是 finding,不是豁免
14
- - 不套用 tdd-implement 重流程:见 SKILL.md ②轻量声明——单 seam 修复直走红-绿,不引入重流程编排
15
- - 不重写 tdd 技能的红-绿语义:seam 定义、好测试标准、mocking 边界一律查上游技能
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
- 三类产出物(Glossary / ADR / Spec)格式的**唯一细节出处**,由 SKILL.md 直链引用。SKILL.md 只保留流程、导航与不可协商规则,本文件不重复流程内容。
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
- - 只是 glossary:零实现细节,不当 spec/scratch pad
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
- - 三条件全满足才提议(难逆转 / 无上下文费解 / 真实权衡);`docs/adr/` 懒创建
15
- - 格式:标题 + 1-3 句正文;可选节(Status/Considered Options/Consequences)按需,大多数不需要
16
- - 编号:`0001-slug.md` 顺序递增,扫描最高号 +1
17
- - 草稿经用户显式确认后落盘,任何情况无例外(见 SKILL.md 流程① 与不可协商规则)
13
+ - 只有同时满足“难逆转 / 无上下文费解 / 存在真实权衡”时才提议 ADR。
14
+ - ADR 草稿必须完整展示并获得用户明确确认后才落盘;这是本 skill 的独有硬门禁。
15
+ - 不把 ADR 当 glossary 一样静默 inline 更新。
18
16
 
19
- ## Spec 守则
17
+ ## Spec 增量规则
20
18
 
21
- - 完整七节模板逐节不缺:Problem Statement / Solution / User Stories / Implementation Decisions / Testing Decisions / Out of Scope / Further Notes
22
- - User Stories:长编号列表,`As an <actor>, I want a <feature>, so that <benefit>` 格式
23
- - Implementation Decisions:不含文件路径/代码片段;例外——原型产出的决策密集片段可 inline,注明来源并裁剪至决策部分
24
- - 全文贯穿 glossary 词汇;尊重所触区域既有 ADR
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
- - 不把 ADR glossary 一样 inline 更新(见 SKILL.md 不可协商规则)
32
- - 不产出守则之外的文件:产出物只有三种(Glossary / ADR / Spec)
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; not for regular instance execution (use the generated instance-test skill) nor for TDD (use tdd-implement)"
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 字段、实例溯源、spec hash、`generatedAt`、manual 段保护和内部链接均通过后再报告成功。不默认执行完整实例集,不启动服务,不产生功能测试副作用。
44
+ 生成后立即做快速、确定性的结构验证:文件存在、schema 字段、实例类型、实例溯源、spec hash、`generatedAt`、manual 段保护和内部链接均通过后再报告成功。不默认执行完整实例集,不启动服务,不产生功能测试副作用。
43
45
 
44
46
  出口:结构验证结果为 `PASS`,失败则报告具体 gap,不回滚生成物。
45
47
 
46
48
  ## 可选行为验证
47
49
 
48
- 仅当用户明确要求运行实例集时,才调用生成的功能测试 skill 执行隔离、串行的实例验证;届时按实例捕获 stdout/stderrexit code expected-vs-actual evidence,并由执行 skill 报告 `PASS m/n`。这不是 scaffold 的默认步骤。
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