@heihei0299/matt-skills 2.1.13 → 3.0.1

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 (104) hide show
  1. package/README.md +3 -2
  2. package/bin/cli.js +51 -122
  3. package/bin/skill-boundaries.js +0 -8
  4. package/bin/skill-config.js +21 -0
  5. package/bin/skill-selection.js +27 -0
  6. package/config/required.json +5 -0
  7. package/package.json +2 -1
  8. package/scripts/sync-upstream.js +3 -12
  9. package/template/.opencode/CONTEXT.md +7 -7
  10. package/template/.pi/CONTEXT.md +7 -7
  11. package/template/.agents/skills/ask-matt/PHASE-BOUNDARIES.md +0 -55
  12. package/template/.agents/skills/ask-matt/SKILL.md +0 -90
  13. package/template/.agents/skills/ask-matt/agents/openai.yaml +0 -5
  14. package/template/.agents/skills/code-review/SKILL.md +0 -87
  15. package/template/.agents/skills/code-review/agents/openai.yaml +0 -3
  16. package/template/.agents/skills/codebase-design/DEEPENING.md +0 -37
  17. package/template/.agents/skills/codebase-design/DESIGN-IT-TWICE.md +0 -44
  18. package/template/.agents/skills/codebase-design/SKILL.md +0 -114
  19. package/template/.agents/skills/codebase-design/agents/openai.yaml +0 -3
  20. package/template/.agents/skills/diagnose-fix/SKILL.md +0 -43
  21. package/template/.agents/skills/diagnose-fix/agents/openai.yaml +0 -5
  22. package/template/.agents/skills/diagnose-fix/references/anti-patterns.md +0 -21
  23. package/template/.agents/skills/diagnosing-bugs/SKILL.md +0 -138
  24. package/template/.agents/skills/diagnosing-bugs/agents/openai.yaml +0 -3
  25. package/template/.agents/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +0 -44
  26. package/template/.agents/skills/domain-modeling/ADR-FORMAT.md +0 -47
  27. package/template/.agents/skills/domain-modeling/CONTEXT-FORMAT.md +0 -60
  28. package/template/.agents/skills/domain-modeling/SKILL.md +0 -74
  29. package/template/.agents/skills/domain-modeling/agents/openai.yaml +0 -3
  30. package/template/.agents/skills/grill-me/SKILL.md +0 -7
  31. package/template/.agents/skills/grill-me/agents/openai.yaml +0 -5
  32. package/template/.agents/skills/grill-to-spec/SKILL.md +0 -55
  33. package/template/.agents/skills/grill-to-spec/agents/openai.yaml +0 -5
  34. package/template/.agents/skills/grill-to-spec/references/rules.md +0 -47
  35. package/template/.agents/skills/grill-with-docs/SKILL.md +0 -7
  36. package/template/.agents/skills/grill-with-docs/agents/openai.yaml +0 -5
  37. package/template/.agents/skills/grilling/SKILL.md +0 -28
  38. package/template/.agents/skills/grilling/agents/openai.yaml +0 -3
  39. package/template/.agents/skills/handoff/SKILL.md +0 -16
  40. package/template/.agents/skills/handoff/agents/openai.yaml +0 -5
  41. package/template/.agents/skills/implement/SKILL.md +0 -15
  42. package/template/.agents/skills/implement/agents/openai.yaml +0 -5
  43. package/template/.agents/skills/implement-review-loop/SKILL.md +0 -34
  44. package/template/.agents/skills/implement-review-loop/agents/openai.yaml +0 -5
  45. package/template/.agents/skills/improve-codebase-architecture/HTML-REPORT.md +0 -123
  46. package/template/.agents/skills/improve-codebase-architecture/SKILL.md +0 -71
  47. package/template/.agents/skills/improve-codebase-architecture/agents/openai.yaml +0 -5
  48. package/template/.agents/skills/instance-test/SKILL.md +0 -70
  49. package/template/.agents/skills/instance-test/agents/openai.yaml +0 -5
  50. package/template/.agents/skills/instance-test/references/instances.md +0 -75
  51. package/template/.agents/skills/prototype/LOGIC.md +0 -67
  52. package/template/.agents/skills/prototype/SKILL.md +0 -26
  53. package/template/.agents/skills/prototype/UI.md +0 -112
  54. package/template/.agents/skills/prototype/agents/openai.yaml +0 -3
  55. package/template/.agents/skills/research/SKILL.md +0 -12
  56. package/template/.agents/skills/research/agents/openai.yaml +0 -3
  57. package/template/.agents/skills/resolving-merge-conflicts/SKILL.md +0 -14
  58. package/template/.agents/skills/resolving-merge-conflicts/agents/openai.yaml +0 -3
  59. package/template/.agents/skills/scaffold-functional-test/SKILL.md +0 -64
  60. package/template/.agents/skills/scaffold-functional-test/agents/openai.yaml +0 -5
  61. package/template/.agents/skills/scaffold-functional-test/references/schema.md +0 -80
  62. package/template/.agents/skills/setup-matt-pocock-skills/SKILL.md +0 -116
  63. package/template/.agents/skills/setup-matt-pocock-skills/agents/openai.yaml +0 -5
  64. package/template/.agents/skills/setup-matt-pocock-skills/domain.md +0 -51
  65. package/template/.agents/skills/setup-matt-pocock-skills/issue-tracker-github.md +0 -45
  66. package/template/.agents/skills/setup-matt-pocock-skills/issue-tracker-gitlab.md +0 -46
  67. package/template/.agents/skills/setup-matt-pocock-skills/issue-tracker-local.md +0 -30
  68. package/template/.agents/skills/setup-matt-pocock-skills/triage-labels.md +0 -15
  69. package/template/.agents/skills/show-me/SKILL.md +0 -28
  70. package/template/.agents/skills/tdd/SKILL.md +0 -38
  71. package/template/.agents/skills/tdd/agents/openai.yaml +0 -3
  72. package/template/.agents/skills/tdd/mocking.md +0 -59
  73. package/template/.agents/skills/tdd/tests.md +0 -77
  74. package/template/.agents/skills/tdd-implement/SKILL.md +0 -81
  75. package/template/.agents/skills/tdd-implement/agents/openai.yaml +0 -5
  76. package/template/.agents/skills/tdd-implement/references/finalize.md +0 -16
  77. package/template/.agents/skills/tdd-implement/references/orchestration.md +0 -54
  78. package/template/.agents/skills/tdd-implement/references/verify.md +0 -15
  79. package/template/.agents/skills/teach/GLOSSARY-FORMAT.md +0 -35
  80. package/template/.agents/skills/teach/LEARNING-RECORD-FORMAT.md +0 -46
  81. package/template/.agents/skills/teach/MISSION-FORMAT.md +0 -31
  82. package/template/.agents/skills/teach/RESOURCES-FORMAT.md +0 -32
  83. package/template/.agents/skills/teach/SKILL.md +0 -140
  84. package/template/.agents/skills/teach/agents/openai.yaml +0 -5
  85. package/template/.agents/skills/to-questionnaire/SKILL.md +0 -54
  86. package/template/.agents/skills/to-questionnaire/agents/openai.yaml +0 -5
  87. package/template/.agents/skills/to-spec/SKILL.md +0 -75
  88. package/template/.agents/skills/to-spec/agents/openai.yaml +0 -5
  89. package/template/.agents/skills/to-tickets/SKILL.md +0 -105
  90. package/template/.agents/skills/to-tickets/agents/openai.yaml +0 -5
  91. package/template/.agents/skills/triage/AGENT-BRIEF.md +0 -207
  92. package/template/.agents/skills/triage/OUT-OF-SCOPE.md +0 -105
  93. package/template/.agents/skills/triage/SKILL.md +0 -112
  94. package/template/.agents/skills/triage/agents/openai.yaml +0 -5
  95. package/template/.agents/skills/wait-what/SKILL.md +0 -7
  96. package/template/.agents/skills/wait-what/agents/openai.yaml +0 -5
  97. package/template/.agents/skills/wayfinder/SKILL.md +0 -128
  98. package/template/.agents/skills/wayfinder/agents/openai.yaml +0 -5
  99. package/template/.agents/skills/wizard/SKILL.md +0 -44
  100. package/template/.agents/skills/wizard/agents/openai.yaml +0 -3
  101. package/template/.agents/skills/wizard/template.sh +0 -204
  102. package/template/.agents/skills/writing-for-agents/SKILL-MECHANICS.md +0 -22
  103. package/template/.agents/skills/writing-for-agents/SKILL.md +0 -81
  104. package/template/.agents/skills/writing-for-agents/agents/openai.yaml +0 -3
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # matt-skills
2
2
 
3
- 面向项目仓库的 Agent skills 与配置模板。模板包含共享 skills、`AGENTS.md`、项目上下文占位文件,以及 pi / opencode 所需的项目配置。
3
+ 面向项目仓库的 Agent skills 与配置模板。模板包含项目 skeleton、`AGENTS.md`、项目上下文占位文件,以及 pi / opencode 所需的项目配置;共享 Skills 由 CLI 从 Workspace 的 canonical source 直接组装到目标项目。
4
4
 
5
5
  ## 模板内容
6
6
 
@@ -8,11 +8,12 @@
8
8
  template/
9
9
  ├── AGENTS.md Agent 行为路由与项目上下文入口
10
10
  ├── PROJECT.md 目标项目填写的目标、范围和主要入口
11
- ├── .agents/skills/ 共享 skills 的唯一项目级来源
12
11
  ├── .opencode/ opencode agents、commands、docs
13
12
  └── .pi/ pi prompts、docs 与项目自定义 skills 占位
14
13
  ```
15
14
 
15
+ 共享 Skills 不作为 Template Snapshot 的持久化副本;`init`、`sync` 和 `install` 根据 distribution boundary 从 Workspace 的 canonical source 分发到目标项目的 `.agents/skills/`。
16
+
16
17
  - `PROJECT.md` 描述项目是什么;操作规则放在 `AGENTS.md`。
17
18
  - `.opencode/CONTEXT.md` / `.pi/CONTEXT.md` 保存领域术语与边界。
18
19
  - `.opencode/skills/` 与 `.pi/skills/` 仅用于项目自定义 skills。
package/bin/cli.js CHANGED
@@ -6,12 +6,12 @@ import { fileURLToPath } from 'node:url';
6
6
  import prompts from 'prompts';
7
7
  import {
8
8
  PROPRIETARY_SKILLS,
9
- isDefaultProgrammingSkill,
10
- isDistributableProprietarySkill,
11
9
  isDistributableSkill,
12
10
  isRepoLocalSkill,
13
11
  REPO_LOCAL_SKILLS,
14
12
  } from './skill-boundaries.js';
13
+ import { resolveSkillNames } from './skill-selection.js';
14
+ import { loadSkillSet } from './skill-config.js';
15
15
 
16
16
  const SKILLS_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', '.agents', 'skills');
17
17
  const TEMPLATE_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'template');
@@ -19,32 +19,15 @@ const ENGINEERING_PATH = path.join(path.dirname(fileURLToPath(import.meta.url)),
19
19
  const REQUIRED_PATH = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'config', 'required.json');
20
20
  let ENGINEERING_SKILLS = null;
21
21
  async function loadEngineeringSkills() {
22
- if (ENGINEERING_SKILLS) return ENGINEERING_SKILLS;
23
- try {
24
- const raw = await readFile(ENGINEERING_PATH, 'utf8');
25
- ENGINEERING_SKILLS = new Set(JSON.parse(raw));
26
- } catch {
27
- ENGINEERING_SKILLS = new Set(['ask-matt','code-review','codebase-design','diagnosing-bugs','domain-modeling','grill-with-docs','implement','improve-codebase-architecture','prototype','research','resolving-merge-conflicts','setup-matt-pocock-skills','tdd','to-spec','to-tickets','triage','wayfinder','wizard']);
28
- }
22
+ if (!ENGINEERING_SKILLS) ENGINEERING_SKILLS = await loadSkillSet(ENGINEERING_PATH, 'engineering');
29
23
  return ENGINEERING_SKILLS;
30
24
  }
25
+
31
26
  let REQUIRED_SKILLS = null;
32
27
  async function loadRequiredSkills() {
33
- if (REQUIRED_SKILLS) return REQUIRED_SKILLS;
34
- try {
35
- const raw = await readFile(REQUIRED_PATH, 'utf8');
36
- REQUIRED_SKILLS = new Set(JSON.parse(raw));
37
- } catch {
38
- REQUIRED_SKILLS = new Set(['grilling', 'grill-me', 'handoff']);
39
- }
28
+ if (!REQUIRED_SKILLS) REQUIRED_SKILLS = await loadSkillSet(REQUIRED_PATH, 'required');
40
29
  return REQUIRED_SKILLS;
41
30
  }
42
- function isProgrammingSkill(name, engineering, required) {
43
- return isDefaultProgrammingSkill(name, engineering, required);
44
- }
45
- function isProgrammingAll(name, engineering) {
46
- return isDistributableProprietarySkill(name) || engineering.has(name);
47
- }
48
31
  process.stdout.on('error', (err) => {
49
32
  if (err.code === 'EPIPE') process.exit(0);
50
33
  throw err;
@@ -156,27 +139,35 @@ function parseFrontmatter(text) {
156
139
  return fields;
157
140
  }
158
141
 
159
- async function listSkills({ onlyProgramming = false } = {}) {
142
+ async function listAvailableSkillNames() {
160
143
  const entries = await readdir(SKILLS_DIR, { withFileTypes: true });
144
+ return entries
145
+ .filter((entry) => entry.isDirectory())
146
+ .filter((entry) => !entry.name.endsWith('.bak'))
147
+ .filter((entry) => entry.name !== 'skill-creator' && entry.name !== '.git')
148
+ .map((entry) => entry.name);
149
+ }
150
+
151
+ async function listSkillNames({ onlyProgramming = false } = {}) {
152
+ return resolveSkillNames({
153
+ availableNames: await listAvailableSkillNames(),
154
+ mode: onlyProgramming ? 'default' : 'all',
155
+ engineering: onlyProgramming ? await loadEngineeringSkills() : [],
156
+ required: onlyProgramming ? await loadRequiredSkills() : [],
157
+ });
158
+ }
159
+
160
+ async function listSkills({ onlyProgramming = false } = {}) {
161
161
  const skills = [];
162
- let engineering = null;
163
- let required = null;
164
- if (onlyProgramming) engineering = await loadEngineeringSkills();
165
- if (onlyProgramming) required = await loadRequiredSkills();
166
- for (const entry of entries) {
167
- if (!entry.isDirectory()) continue;
168
- if (entry.name.endsWith('.bak')) continue;
169
- if (entry.name === 'skill-creator') continue;
170
- if (isRepoLocalSkill(entry.name)) continue;
171
- if (onlyProgramming && !isProgrammingSkill(entry.name, engineering, required)) continue;
162
+ for (const name of await listSkillNames({ onlyProgramming })) {
172
163
  let content;
173
164
  try {
174
- content = await readFile(path.join(SKILLS_DIR, entry.name, 'SKILL.md'), 'utf8');
165
+ content = await readFile(path.join(SKILLS_DIR, name, 'SKILL.md'), 'utf8');
175
166
  } catch {
176
167
  continue;
177
168
  }
178
- const { name, description } = parseFrontmatter(content);
179
- if (name && description) skills.push({ name, description });
169
+ const { description } = parseFrontmatter(content);
170
+ if (description) skills.push({ name, description });
180
171
  }
181
172
  return skills.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
182
173
  }
@@ -193,7 +184,7 @@ async function pathExists(p) {
193
184
  function shouldCopyTemplatePath(src) {
194
185
  const relative = path.relative(TEMPLATE_DIR, src);
195
186
  const parts = relative.split(path.sep);
196
- return !(parts[0] === '.agents' && parts[1] === 'skills' && isRepoLocalSkill(parts[2]));
187
+ return !(parts[0] === '.agents' && parts[1] === 'skills');
197
188
  }
198
189
 
199
190
  const TOOLS = ['codex', 'pi', 'opencode', 'claude'];
@@ -243,10 +234,8 @@ async function promptSkills(skills) {
243
234
 
244
235
  async function installCommand({ dest, all, force, tools, global }) {
245
236
  const onlyProgramming = !all;
246
- const engineering = onlyProgramming ? await loadEngineeringSkills() : null;
247
- const required = onlyProgramming ? await loadRequiredSkills() : null;
248
- const skillsAll = await listSkills({ onlyProgramming: false });
249
- const skills = onlyProgramming ? skillsAll.filter(s => isProgrammingSkill(s.name, engineering, required)) : skillsAll;
237
+ const skillNames = await listSkillNames({ onlyProgramming });
238
+ const skills = await listSkills({ onlyProgramming });
250
239
  let targets;
251
240
  if (dest) {
252
241
  targets = [{ tool: null, dir: path.resolve(process.cwd(), dest) }];
@@ -271,12 +260,12 @@ async function installCommand({ dest, all, force, tools, global }) {
271
260
  }
272
261
  targets = [...seen.values()];
273
262
  }
274
- const selected = all ? skills.map((s) => s.name) : await promptSkills(skills);
263
+ const selected = all ? skillNames : await promptSkills(skills);
275
264
  if (selected.length === 0) {
276
265
  process.stdout.write('未选择任何技能,未安装任何技能\n');
277
266
  return;
278
267
  }
279
- const knownDistributable = new Set(skills.map((skill) => skill.name));
268
+ const knownDistributable = new Set(skillNames);
280
269
  for (const name of selected) {
281
270
  if (!isDistributableSkill(name, knownDistributable)) {
282
271
  throw new Error(`${name} is repository-local or unavailable and cannot be distributed`);
@@ -312,23 +301,16 @@ async function initCommand({ dest, all }) {
312
301
  force: true,
313
302
  filter: shouldCopyTemplatePath,
314
303
  });
315
- process.stdout.write('模板:已复制(AGENTS.md、.agents/skills、.opencode/、.pi/)\n');
316
- // 默认范围(engineering + 独有所需 + 默认独有),--all 才保留其余 productivity
317
- if (onlyProgramming && path.resolve(target) !== path.resolve(path.join(path.dirname(fileURLToPath(import.meta.url)), '..'))) {
318
- const engineering = await loadEngineeringSkills();
319
- const required = await loadRequiredSkills();
320
- const skillsDirFilter = path.join(target, '.agents', 'skills');
321
- try {
322
- const entries = await readdir(skillsDirFilter, { withFileTypes: true });
323
- for (const e of entries) {
324
- if (!e.isDirectory()) continue;
325
- if (e.name.endsWith('.bak') || e.name === '.git' || e.name === 'skill-creator') continue;
326
- if (!isProgrammingSkill(e.name, engineering, required)) {
327
- await rm(path.join(skillsDirFilter, e.name), { recursive: true, force: true });
328
- }
329
- }
330
- } catch {}
304
+ const selectedSkills = await listSkillNames({ onlyProgramming });
305
+ const skillsDir = path.join(target, '.agents', 'skills');
306
+ await mkdir(skillsDir, { recursive: true });
307
+ for (const name of selectedSkills) {
308
+ const source = path.join(SKILLS_DIR, name);
309
+ const destination = path.join(skillsDir, name);
310
+ if (path.resolve(source) === path.resolve(destination)) continue;
311
+ await cp(source, destination, { recursive: true, force: true });
331
312
  }
313
+ process.stdout.write('模板:已复制(AGENTS.md、.opencode/、.pi/)\n');
332
314
  }
333
315
  // 统计(区分编程 vs 全量)
334
316
  const skillsDir = path.join(target, '.agents', 'skills');
@@ -338,11 +320,10 @@ async function initCommand({ dest, all }) {
338
320
  installed = entries.filter((e) => e.isDirectory() && !e.name.endsWith('.bak') && e.name !== '.git' && e.name !== 'skill-creator' && !isRepoLocalSkill(e.name)).length;
339
321
  } catch {}
340
322
  const allSkillsFull = await listSkills({ onlyProgramming: false });
341
- const engineeringForStats = await loadEngineeringSkills();
342
- const requiredForStats = await loadRequiredSkills();
343
- const programmingCount = allSkillsFull.filter(s => isProgrammingSkill(s.name, engineeringForStats, requiredForStats)).length;
323
+ const programmingSkills = await listSkills({ onlyProgramming: true });
324
+ const programmingCount = programmingSkills.length;
344
325
  const upstreamFull = allSkillsFull.filter((s) => !PROPRIETARY_SKILLS.has(s.name)).length;
345
- const upstreamProg = allSkillsFull.filter((s) => !PROPRIETARY_SKILLS.has(s.name) && (engineeringForStats.has(s.name) || requiredForStats.has(s.name))).length;
326
+ const upstreamProg = programmingSkills.filter((s) => !PROPRIETARY_SKILLS.has(s.name)).length;
346
327
  const displayTotal = onlyProgramming ? programmingCount : allSkillsFull.length;
347
328
  const displayUpstream = onlyProgramming ? upstreamProg : upstreamFull;
348
329
  if (path.resolve(skillsDir) === path.resolve(SKILLS_DIR)) {
@@ -399,22 +380,10 @@ async function syncCommand({ dest, all, dryRun, json, upstreamUrl, ref }) {
399
380
  });
400
381
  return;
401
382
  }
402
- // 默认范围:分别复制非 skills 部分,skills 由后续 allSkills 循环处理
383
+ // Skeleton only; shared Skills are copied from the canonical source below.
403
384
  await cp(path.join(TEMPLATE_DIR, 'AGENTS.md'), path.join(target, 'AGENTS.md'), { force: true });
404
385
  await cp(path.join(TEMPLATE_DIR, '.opencode'), path.join(target, '.opencode'), { recursive: true, force: true });
405
386
  await cp(path.join(TEMPLATE_DIR, '.pi'), path.join(target, '.pi'), { recursive: true, force: true });
406
- // .agents/skills 不通过模板拷贝,留给后续按 allSkills 精确同步
407
- await mkdir(path.join(target, '.agents', 'skills'), { recursive: true });
408
- // 若 .agents 下有非 skills 文件(未来扩展),也拷贝但排除 skills
409
- try {
410
- const agEntries = await readdir(path.join(TEMPLATE_DIR, '.agents'), { withFileTypes: true });
411
- for (const e of agEntries) {
412
- if (e.name === 'skills') continue;
413
- const src = path.join(TEMPLATE_DIR, '.agents', e.name);
414
- const dst = path.join(target, '.agents', e.name);
415
- await cp(src, dst, { recursive: true, force: true });
416
- }
417
- } catch {}
418
387
  }
419
388
  if (!(await pathExists(marker))) {
420
389
  process.stdout.write('未检测到现有项目(AGENTS.md 不存在),将执行全新初始化\n');
@@ -424,7 +393,7 @@ async function syncCommand({ dest, all, dryRun, json, upstreamUrl, ref }) {
424
393
  force: true,
425
394
  filter: shouldCopyTemplatePath,
426
395
  });
427
- process.stdout.write('模板:已复制(AGENTS.md、.agents/skills、.opencode/、.pi/)\n');
396
+ process.stdout.write('模板:已复制(AGENTS.md、.opencode/、.pi/)\n');
428
397
  } else {
429
398
  process.stdout.write('同步:检测到现有项目,将增量更新\n');
430
399
  let skipAgents = false;
@@ -436,26 +405,8 @@ async function syncCommand({ dest, all, dryRun, json, upstreamUrl, ref }) {
436
405
  } catch {}
437
406
  }
438
407
  if (skipAgents) {
439
- if (onlyProgramming) {
440
- await cp(path.join(TEMPLATE_DIR, '.opencode'), path.join(target, '.opencode'), { recursive: true, force: true });
441
- await cp(path.join(TEMPLATE_DIR, '.pi'), path.join(target, '.pi'), { recursive: true, force: true });
442
- // .agents 跳过 AGENTS.md 定制,skills 由后续处理
443
- try {
444
- const agEntries = await readdir(path.join(TEMPLATE_DIR, '.agents'), { withFileTypes: true });
445
- for (const e of agEntries) {
446
- if (e.name === 'skills') continue;
447
- await cp(path.join(TEMPLATE_DIR, '.agents', e.name), path.join(target, '.agents', e.name), { recursive: true, force: true });
448
- }
449
- } catch {}
450
- } else {
451
- await cp(path.join(TEMPLATE_DIR, '.agents'), path.join(target, '.agents'), {
452
- recursive: true,
453
- force: true,
454
- filter: shouldCopyTemplatePath,
455
- });
456
- await cp(path.join(TEMPLATE_DIR, '.opencode'), path.join(target, '.opencode'), { recursive: true, force: true });
457
- await cp(path.join(TEMPLATE_DIR, '.pi'), path.join(target, '.pi'), { recursive: true, force: true });
458
- }
408
+ await cp(path.join(TEMPLATE_DIR, '.opencode'), path.join(target, '.opencode'), { recursive: true, force: true });
409
+ await cp(path.join(TEMPLATE_DIR, '.pi'), path.join(target, '.pi'), { recursive: true, force: true });
459
410
  process.stdout.write('模板:已同步(AGENTS.md 跳过,已含定制)\n');
460
411
  } else {
461
412
  if (onlyProgramming) await copyTemplateFiltered();
@@ -468,15 +419,7 @@ async function syncCommand({ dest, all, dryRun, json, upstreamUrl, ref }) {
468
419
  }
469
420
  }
470
421
  // 技能同步:--all 仅更新同名可分发技能内容,存在则覆盖,不存在则新增,并更新 AGENTS.md(由上一步已处理);默认范围为默认 programming,不删多余
471
- const entries = await readdir(SKILLS_DIR, { withFileTypes: true });
472
- const allNames = entries.filter((e) => e.isDirectory() && !e.name.endsWith('.bak') && e.name !== 'skill-creator' && e.name !== '.git').map((e) => e.name);
473
- let allSkills = allNames.sort();
474
- allSkills = allSkills.filter((name) => !isRepoLocalSkill(name));
475
- if (onlyProgramming) {
476
- const engineering = await loadEngineeringSkills();
477
- const required = await loadRequiredSkills();
478
- allSkills = allSkills.filter(n => isProgrammingSkill(n, engineering, required));
479
- }
422
+ const allSkills = await listSkillNames({ onlyProgramming });
480
423
  const skillsDir = path.join(target, '.agents', 'skills');
481
424
  await mkdir(skillsDir, { recursive: true });
482
425
  const preservedRepoLocal = [];
@@ -515,22 +458,8 @@ async function syncCommand({ dest, all, dryRun, json, upstreamUrl, ref }) {
515
458
  installed++;
516
459
  }
517
460
  }
518
- // 旧镜像自动清理:.pi/skills .opencode/skills 中残留的共享技能一律删除,保留项目自定义
519
- // 仅清理当前全量/编程集合中的技能,避免误删自定义
520
- for (const harness of ['.pi/skills', '.opencode/skills']) {
521
- const dir = path.join(target, harness);
522
- if (!(await pathExists(dir))) continue;
523
- let hsEntries = [];
524
- try { hsEntries = await readdir(dir, { withFileTypes: true }); } catch { continue; }
525
- for (const e of hsEntries) {
526
- if (!e.isDirectory()) continue;
527
- if (e.name === '.git' || e.name.endsWith('.bak')) continue;
528
- if (e.name === '.gitkeep' || e.name === 'README.md') continue;
529
- if (allSkills.includes(e.name)) {
530
- await rm(path.join(dir, e.name), { recursive: true, force: true });
531
- }
532
- }
533
- }
461
+ // Harness skill directories may contain project-local Skills. Preserve them because
462
+ // their origin cannot be distinguished safely from a historical shared mirror.
534
463
  // 清理过时的 .pi/settings.json 指向
535
464
  try {
536
465
  const piSettings = path.join(target, '.pi/settings.json');
@@ -46,14 +46,6 @@ export function isDefaultProprietarySkill(name) {
46
46
  return DEFAULT_PROPRIETARY_SKILLS.has(name);
47
47
  }
48
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
49
  export function isDistributableSkill(name, knownNames) {
58
50
  if (!knownNames) return isDistributableProprietarySkill(name);
59
51
  const known = knownNames instanceof Set ? knownNames : new Set(knownNames);
@@ -0,0 +1,21 @@
1
+ import { readFile } from 'node:fs/promises';
2
+
3
+ export async function loadSkillSet(file, label) {
4
+ let raw;
5
+ try {
6
+ raw = await readFile(file, 'utf8');
7
+ } catch (error) {
8
+ throw new Error(`unable to read ${label} skill config: ${error.message}`);
9
+ }
10
+
11
+ let value;
12
+ try {
13
+ value = JSON.parse(raw);
14
+ } catch (error) {
15
+ throw new Error(`invalid ${label} skill config: ${error.message}`);
16
+ }
17
+ if (!Array.isArray(value) || value.some((name) => typeof name !== 'string')) {
18
+ throw new Error(`invalid ${label} skill config: expected an array of strings`);
19
+ }
20
+ return new Set(value);
21
+ }
@@ -0,0 +1,27 @@
1
+ import {
2
+ DEFAULT_PROPRIETARY_SKILLS,
3
+ isDistributableSkill,
4
+ } from './skill-boundaries.js';
5
+
6
+ function asSet(value) {
7
+ return value instanceof Set ? new Set(value) : new Set(value ?? []);
8
+ }
9
+
10
+ export function resolveSkillNames({ availableNames, mode = 'default', engineering, required }) {
11
+ if (mode !== 'default' && mode !== 'all') {
12
+ throw new Error(`unknown skill selection mode: ${mode}`);
13
+ }
14
+
15
+ const available = asSet(availableNames);
16
+ const candidates = mode === 'all'
17
+ ? available
18
+ : new Set([
19
+ ...asSet(engineering),
20
+ ...asSet(required),
21
+ ...DEFAULT_PROPRIETARY_SKILLS,
22
+ ]);
23
+
24
+ return [...candidates]
25
+ .filter((name) => available.has(name) && isDistributableSkill(name, available))
26
+ .sort();
27
+ }
@@ -0,0 +1,5 @@
1
+ [
2
+ "grilling",
3
+ "grill-me",
4
+ "handoff"
5
+ ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heihei0299/matt-skills",
3
- "version": "2.1.13",
3
+ "version": "3.0.1",
4
4
  "description": "Agent skills + 项目配置模板:一条命令初始化 opencode / pi-agent 项目(含 mattpocock/skills 上游技能)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,6 +14,7 @@
14
14
  "scripts/codex-smoke.js",
15
15
  "config/proprietary.json",
16
16
  "config/engineering.json",
17
+ "config/required.json",
17
18
  "README.md"
18
19
  ],
19
20
  "scripts": {
@@ -6,6 +6,7 @@ import path from 'node:path';
6
6
  import os from 'node:os';
7
7
  import { fileURLToPath } from 'node:url';
8
8
  import { PROPRIETARY_SKILLS } from '../bin/skill-boundaries.js';
9
+ import { loadSkillSet } from '../bin/skill-config.js';
9
10
 
10
11
  const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
11
12
  const LOCAL_SKILLS_DIR = path.join(ROOT, '.agents', 'skills');
@@ -23,20 +24,10 @@ async function loadProprietary() {
23
24
  }
24
25
 
25
26
  async function loadEngineering() {
26
- try {
27
- const raw = await readFile(ENGINEERING_PATH, 'utf8');
28
- return new Set(JSON.parse(raw));
29
- } catch {
30
- return new Set(['ask-matt','code-review','codebase-design','diagnosing-bugs','domain-modeling','grill-with-docs','implement','improve-codebase-architecture','prototype','research','resolving-merge-conflicts','setup-matt-pocock-skills','tdd','to-spec','to-tickets','triage','wayfinder','wizard']);
31
- }
27
+ return loadSkillSet(ENGINEERING_PATH, 'engineering');
32
28
  }
33
29
  async function loadRequired() {
34
- try {
35
- const raw = await readFile(REQUIRED_PATH, 'utf8');
36
- return new Set(JSON.parse(raw));
37
- } catch {
38
- return new Set(['grilling', 'grill-me', 'handoff']);
39
- }
30
+ return loadSkillSet(REQUIRED_PATH, 'required');
40
31
  }
41
32
 
42
33
  async function hashFile(filePath) {
@@ -5,11 +5,11 @@ The domain vocabulary for this repo — two sections: how this repository is pos
5
5
  ## Repository
6
6
 
7
7
  **Template Repository** (模板仓库):
8
- This repository's identity. It is the config repo for mattpocock/skills: it distributes project-level config (AGENTS.md behavior routing, `.opencode/docs/agents/` discipline files, `.opencode/CONTEXT.md` glossary) plus the explicitly allowed distributable skills via `.agents/skills`. The workspace also retains repo-local maintenance skills that are never distributed. The workspace content is projected into `template/` as a Template Snapshot, and initializing a Target Repository is a one-time copy of that snapshot. The workspace copy also serves this repo's own sessions.
8
+ This repository's identity. It is the config repo for mattpocock/skills: it distributes project-level config (AGENTS.md behavior routing, `.opencode/docs/agents/` discipline files, `.opencode/CONTEXT.md` glossary) and the explicitly allowed distributable Skills. The Workspace also retains repo-local maintenance Skills that are never distributed. The Template Snapshot projects the project skeleton; the CLI assembles selected distributable Skills from the Workspace when initializing or synchronizing a Target Repository. The Workspace copy also serves this repo's own sessions.
9
9
  _Avoid_: skill distribution repo
10
10
 
11
11
  **Upstream Repository** (上游仓库):
12
- mattpocock/skills — the source of the 26 skill bodies (skills/engineering + skills/productivity) that are mirrored into this repo's `.agents/skills` and then distributed via the Template Snapshot. This repo syncs them via `scripts/sync-upstream.js` and `matt-skills sync`.
12
+ mattpocock/skills — the source of the 26 Skill bodies (skills/engineering + skills/productivity) that are mirrored into this repo's Workspace canonical Skill source and then distributed by the CLI. This repo syncs them via `scripts/sync-upstream.js` and `matt-skills sync`.
13
13
  _Avoid_: source repo, skill origin
14
14
 
15
15
  **Proprietary Skill** (独有技能):
@@ -17,23 +17,23 @@ A skill that does not exist upstream and lives only in this repo (currently ci-g
17
17
  _Avoid_: private skill, local skill
18
18
 
19
19
  **Workspace** (工作区):
20
- The root-level working copies of the template content — `.agents/skills/` (upstream plus all proprietary skills, single source), `.opencode/` (issue-audit agent, explicit-skill commands), `.pi/` (pi-agent prompts: `issue-audit`), `AGENTS.md`, `CONTEXT.md`, `docs/`. Where this repo's own sessions load, modify, and test the content. The template paths project only the distributable subset: `.agents/skills/` `template/.agents/skills/`, `.opencode/commands/*.md` → `template/.opencode/commands/`, `.pi/prompts/issue-audit.md` → `template/.pi/prompts/issue-audit.md`, root-level `CONTEXT.md` and `docs/agents/` → `template/.opencode/` + `template/.pi/`. Harness-specific skill dirs `.pi/skills/` and `.opencode/skills/` are reserved for project-local custom skills (empty placeholders with `.gitkeep` + `README.md` in the template).
20
+ The root-level working copies of the template content — `.agents/skills/` (upstream plus all proprietary Skills, the canonical shared Skill source), `.opencode/` (issue-audit agent, explicit-Skill commands), `.pi/` (pi-agent prompts: `issue-audit`), `AGENTS.md`, `CONTEXT.md`, `docs/`. This is where the repo's own sessions load, modify and test the content. The Template Snapshot projects only the distributable configuration and skeleton: `.opencode/commands/*.md` → `template/.opencode/commands/`, `.pi/prompts/issue-audit.md` → `template/.pi/prompts/issue-audit.md`, and root-level `CONTEXT.md` and `docs/agents/` → `template/.opencode/` + `template/.pi/`. The CLI assembles selected shared Skills from the canonical source into a Target Repository. Harness-specific Skill dirs `.pi/skills/` and `.opencode/skills/` are reserved for project-local custom Skills (empty placeholders with `.gitkeep` + `README.md` in the Template Snapshot).
21
21
  _Avoid_: working copy, source repo
22
22
 
23
23
  **Template Snapshot** (模板快照):
24
- Everything under `template/` — the distributable snapshot of workspace content: only allowed skills under `.agents/skills/` (single source for user projects), harness skill dirs `.pi/skills/` + `.opencode/skills/` as empty placeholders for project custom skills, distributable opencode commands under `.opencode/commands/`, the pi issue-audit command under `.pi/prompts/`, discipline files and glossary under `.opencode/` + `.pi/`, AGENTS.md at the top level, generated by `node scripts/build-template.js`, used to initialize other repositories. Repo-local skills and commands remain workspace-only. The sync direction is one-way: workspacesnapshot. `test/template-sync.test.js` guards the mirror stays in sync.
24
+ Everything under `template/` — the distributable snapshot of project skeleton content: harness Skill dirs `.pi/skills/` + `.opencode/skills/` as empty placeholders for project custom Skills, distributable opencode commands under `.opencode/commands/`, the pi issue-audit command under `.pi/prompts/`, discipline files and glossary under `.opencode/` + `.pi/`, and AGENTS.md at the top level. It is generated by `node scripts/build-template.js` and used to initialize other repositories. Shared Skills and repo-local Skills remain in the Workspace; the CLI distributes only the selected shared Skills. The sync direction for the skeleton is one-way: WorkspaceTemplate Snapshot. Template structure tests guard the content the snapshot owns.
25
25
  _Avoid_: release snapshot, published snapshot
26
26
 
27
27
  **Target Repository** (目标仓库):
28
- A repository initialized by copying `template/` into its root (`cp -r template/. <target>/`). It then loads all distributable shared skills from its own `.agents/skills/`, project-local custom skills from `.pi/skills/` / `.opencode/skills/` (if any), the distributable commands from `.opencode/commands/` (opencode) and the `issue-audit` command from `.pi/prompts/` (pi) / `.opencode/commands/` (opencode), and the project-level global config from `.opencode/` + `.pi/` and `AGENTS.md`. Repo-local matt-skills maintenance skills are not part of a Target Repository by default.
28
+ A repository initialized by copying the Template Snapshot skeleton into its root and then receiving selected distributable shared Skills from the CLI. It loads shared Skills from its own `.agents/skills/`, project-local custom Skills from `.pi/skills/` / `.opencode/skills/` (if any), the distributable commands from `.opencode/commands/` (opencode) and the `issue-audit` command from `.pi/prompts/` (pi) / `.opencode/commands/` (opencode), and the project-level global config from `.opencode/` + `.pi/` and AGENTS.md. Repo-local matt-skills maintenance Skills are not part of a Target Repository by default.
29
29
  _Avoid_: inheriting repo, child repo
30
30
 
31
31
  **Initialize** (初始化):
32
- The one-time action of setting up a Target Repository: copying `template/` into its root (`cp -r template/. <target>/`). All distributable skills are already included via `template/.agents/skills/`; no separate upstream fetch is needed. Repo-local skills are intentionally absent. Copying, not inheriting — no runtime relationship survives the copy.
32
+ The one-time action of setting up a Target Repository: copying the Template Snapshot skeleton into its root, then having the CLI create its shared Skill directory and copy the selected distributable Skills from the Workspace canonical source. No separate upstream fetch is needed. Repo-local Skills are intentionally absent. Copying, not inheriting — no runtime relationship survives the copy.
33
33
  _Avoid_: inherit, bootstrap
34
34
 
35
35
  **Sync** (同步):
36
- `matt-skills sync` 同步 Target Repository 的模板与技能:`--dry-run` 仅对比不写盘(`check`,`--json` 可解析,有差异 `exit 1`),默认安全增量(`AGENTS.md` 有定制如 `tdd-implement` 则跳过,`.agents/skills` 按默认 programming 范围覆盖但不删多余技能,`template/.opencode/.pi` 增量 `add/update`,旧镜像 `.pi/skills` + `.opencode/skills` 中残留的共享技能自动清理但保留项目自定义)、`--all` 仅更新同名可分发技能内容(存在则覆盖,不存在则新增)并更新 `AGENTS.md`(不跳过定制),不删多余技能。Repo-local skill 永远不新增、不覆盖、不删除,只在发现历史副本时提示保留。`update` 已合并到 `sync` 并删除。
36
+ `matt-skills sync` 同步 Target Repository 的 skeleton 与 Skills:`--dry-run` 仅对比不写盘(`check`,`--json` 可解析,有差异 `exit 1`),默认安全增量(`AGENTS.md` 有定制如 `tdd-implement` 则跳过,canonical shared Skills 按默认 programming 范围覆盖但不删多余,Template Snapshot 的 `.opencode` / `.pi` 配置增量 add/update,`.pi/skills` `.opencode/skills` 中已有的 project-local Skill 不按名称删除,避免把项目自定义 Skill 误删)、`--all` 仅更新同名可分发 Skill 内容(存在则覆盖,不存在则新增)并更新 `AGENTS.md`(不跳过定制),不删多余 Skill。Repo-local Skill 永远不新增、不覆盖、不删除,只在发现历史副本时提示保留。`update` 已合并到 `sync` 并删除。
37
37
  _Avoid_: update, force sync
38
38
 
39
39
  ## Skill Design
@@ -5,11 +5,11 @@ The domain vocabulary for this repo — two sections: how this repository is pos
5
5
  ## Repository
6
6
 
7
7
  **Template Repository** (模板仓库):
8
- This repository's identity. It is the config repo for mattpocock/skills: it distributes project-level config (AGENTS.md behavior routing, `.opencode/docs/agents/` discipline files, `.opencode/CONTEXT.md` glossary) plus the explicitly allowed distributable skills via `.agents/skills`. The workspace also retains repo-local maintenance skills that are never distributed. The workspace content is projected into `template/` as a Template Snapshot, and initializing a Target Repository is a one-time copy of that snapshot. The workspace copy also serves this repo's own sessions.
8
+ This repository's identity. It is the config repo for mattpocock/skills: it distributes project-level config (AGENTS.md behavior routing, `.opencode/docs/agents/` discipline files, `.opencode/CONTEXT.md` glossary) and the explicitly allowed distributable Skills. The Workspace also retains repo-local maintenance Skills that are never distributed. The Template Snapshot projects the project skeleton; the CLI assembles selected distributable Skills from the Workspace when initializing or synchronizing a Target Repository. The Workspace copy also serves this repo's own sessions.
9
9
  _Avoid_: skill distribution repo
10
10
 
11
11
  **Upstream Repository** (上游仓库):
12
- mattpocock/skills — the source of the 26 skill bodies (skills/engineering + skills/productivity) that are mirrored into this repo's `.agents/skills` and then distributed via the Template Snapshot. This repo syncs them via `scripts/sync-upstream.js` and `matt-skills sync`.
12
+ mattpocock/skills — the source of the 26 Skill bodies (skills/engineering + skills/productivity) that are mirrored into this repo's Workspace canonical Skill source and then distributed by the CLI. This repo syncs them via `scripts/sync-upstream.js` and `matt-skills sync`.
13
13
  _Avoid_: source repo, skill origin
14
14
 
15
15
  **Proprietary Skill** (独有技能):
@@ -17,23 +17,23 @@ A skill that does not exist upstream and lives only in this repo (currently ci-g
17
17
  _Avoid_: private skill, local skill
18
18
 
19
19
  **Workspace** (工作区):
20
- The root-level working copies of the template content — `.agents/skills/` (upstream plus all proprietary skills, single source), `.opencode/` (issue-audit agent, explicit-skill commands), `.pi/` (pi-agent prompts: `issue-audit`), `AGENTS.md`, `CONTEXT.md`, `docs/`. Where this repo's own sessions load, modify, and test the content. The template paths project only the distributable subset: `.agents/skills/` `template/.agents/skills/`, `.opencode/commands/*.md` → `template/.opencode/commands/`, `.pi/prompts/issue-audit.md` → `template/.pi/prompts/issue-audit.md`, root-level `CONTEXT.md` and `docs/agents/` → `template/.opencode/` + `template/.pi/`. Harness-specific skill dirs `.pi/skills/` and `.opencode/skills/` are reserved for project-local custom skills (empty placeholders with `.gitkeep` + `README.md` in the template).
20
+ The root-level working copies of the template content — `.agents/skills/` (upstream plus all proprietary Skills, the canonical shared Skill source), `.opencode/` (issue-audit agent, explicit-Skill commands), `.pi/` (pi-agent prompts: `issue-audit`), `AGENTS.md`, `CONTEXT.md`, `docs/`. This is where the repo's own sessions load, modify and test the content. The Template Snapshot projects only the distributable configuration and skeleton: `.opencode/commands/*.md` → `template/.opencode/commands/`, `.pi/prompts/issue-audit.md` → `template/.pi/prompts/issue-audit.md`, and root-level `CONTEXT.md` and `docs/agents/` → `template/.opencode/` + `template/.pi/`. The CLI assembles selected shared Skills from the canonical source into a Target Repository. Harness-specific Skill dirs `.pi/skills/` and `.opencode/skills/` are reserved for project-local custom Skills (empty placeholders with `.gitkeep` + `README.md` in the Template Snapshot).
21
21
  _Avoid_: working copy, source repo
22
22
 
23
23
  **Template Snapshot** (模板快照):
24
- Everything under `template/` — the distributable snapshot of workspace content: only allowed skills under `.agents/skills/` (single source for user projects), harness skill dirs `.pi/skills/` + `.opencode/skills/` as empty placeholders for project custom skills, distributable opencode commands under `.opencode/commands/`, the pi issue-audit command under `.pi/prompts/`, discipline files and glossary under `.opencode/` + `.pi/`, AGENTS.md at the top level, generated by `node scripts/build-template.js`, used to initialize other repositories. Repo-local skills and commands remain workspace-only. The sync direction is one-way: workspacesnapshot. `test/template-sync.test.js` guards the mirror stays in sync.
24
+ Everything under `template/` — the distributable snapshot of project skeleton content: harness Skill dirs `.pi/skills/` + `.opencode/skills/` as empty placeholders for project custom Skills, distributable opencode commands under `.opencode/commands/`, the pi issue-audit command under `.pi/prompts/`, discipline files and glossary under `.opencode/` + `.pi/`, and AGENTS.md at the top level. It is generated by `node scripts/build-template.js` and used to initialize other repositories. Shared Skills and repo-local Skills remain in the Workspace; the CLI distributes only the selected shared Skills. The sync direction for the skeleton is one-way: WorkspaceTemplate Snapshot. Template structure tests guard the content the snapshot owns.
25
25
  _Avoid_: release snapshot, published snapshot
26
26
 
27
27
  **Target Repository** (目标仓库):
28
- A repository initialized by copying `template/` into its root (`cp -r template/. <target>/`). It then loads all distributable shared skills from its own `.agents/skills/`, project-local custom skills from `.pi/skills/` / `.opencode/skills/` (if any), the distributable commands from `.opencode/commands/` (opencode) and the `issue-audit` command from `.pi/prompts/` (pi) / `.opencode/commands/` (opencode), and the project-level global config from `.opencode/` + `.pi/` and `AGENTS.md`. Repo-local matt-skills maintenance skills are not part of a Target Repository by default.
28
+ A repository initialized by copying the Template Snapshot skeleton into its root and then receiving selected distributable shared Skills from the CLI. It loads shared Skills from its own `.agents/skills/`, project-local custom Skills from `.pi/skills/` / `.opencode/skills/` (if any), the distributable commands from `.opencode/commands/` (opencode) and the `issue-audit` command from `.pi/prompts/` (pi) / `.opencode/commands/` (opencode), and the project-level global config from `.opencode/` + `.pi/` and AGENTS.md. Repo-local matt-skills maintenance Skills are not part of a Target Repository by default.
29
29
  _Avoid_: inheriting repo, child repo
30
30
 
31
31
  **Initialize** (初始化):
32
- The one-time action of setting up a Target Repository: copying `template/` into its root (`cp -r template/. <target>/`). All distributable skills are already included via `template/.agents/skills/`; no separate upstream fetch is needed. Repo-local skills are intentionally absent. Copying, not inheriting — no runtime relationship survives the copy.
32
+ The one-time action of setting up a Target Repository: copying the Template Snapshot skeleton into its root, then having the CLI create its shared Skill directory and copy the selected distributable Skills from the Workspace canonical source. No separate upstream fetch is needed. Repo-local Skills are intentionally absent. Copying, not inheriting — no runtime relationship survives the copy.
33
33
  _Avoid_: inherit, bootstrap
34
34
 
35
35
  **Sync** (同步):
36
- `matt-skills sync` 同步 Target Repository 的模板与技能:`--dry-run` 仅对比不写盘(`check`,`--json` 可解析,有差异 `exit 1`),默认安全增量(`AGENTS.md` 有定制如 `tdd-implement` 则跳过,`.agents/skills` 按默认 programming 范围覆盖但不删多余技能,`template/.opencode/.pi` 增量 `add/update`,旧镜像 `.pi/skills` + `.opencode/skills` 中残留的共享技能自动清理但保留项目自定义)、`--all` 仅更新同名可分发技能内容(存在则覆盖,不存在则新增)并更新 `AGENTS.md`(不跳过定制),不删多余技能。Repo-local skill 永远不新增、不覆盖、不删除,只在发现历史副本时提示保留。`update` 已合并到 `sync` 并删除。
36
+ `matt-skills sync` 同步 Target Repository 的 skeleton 与 Skills:`--dry-run` 仅对比不写盘(`check`,`--json` 可解析,有差异 `exit 1`),默认安全增量(`AGENTS.md` 有定制如 `tdd-implement` 则跳过,canonical shared Skills 按默认 programming 范围覆盖但不删多余,Template Snapshot 的 `.opencode` / `.pi` 配置增量 add/update,`.pi/skills` `.opencode/skills` 中已有的 project-local Skill 不按名称删除,避免把项目自定义 Skill 误删)、`--all` 仅更新同名可分发 Skill 内容(存在则覆盖,不存在则新增)并更新 `AGENTS.md`(不跳过定制),不删多余 Skill。Repo-local Skill 永远不新增、不覆盖、不删除,只在发现历史副本时提示保留。`update` 已合并到 `sync` 并删除。
37
37
  _Avoid_: update, force sync
38
38
 
39
39
  ## Skill Design
@@ -1,55 +0,0 @@
1
- # Phase boundaries
2
-
3
- A **phase** is a chunk of work inside a session: the grilling, the implementation, the QA. The definition is fuzzy on purpose: a phase ends when you think *"ok, we're done with that"*.
4
-
5
- The **phase boundary** is the gap between two phases, and it is the only place this decision belongs. Mid-phase there is no decision to make: continue, or split the work that's left into subagents. Compacting mid-phase makes the agent lose the thread.
6
-
7
- ## The five options
8
-
9
- | Option | What it does |
10
- | ------------ | --------------------------------------------------------------- |
11
- | **Continue** | Stay in the session. No context switch at all. |
12
- | **`/clear`** | Empty the context window and start from nothing. |
13
- | **`/handoff`** | Write a portable markdown file and seed a session anywhere with it. |
14
- | **Subagent** | Send the task to its own context window and get a report back. |
15
- | **`/compact`** | Compress this context and seed a fresh session with the summary. |
16
-
17
- ## The tree
18
-
19
- Work top to bottom at the boundary. The first **yes** wins.
20
-
21
- **1. Can you continue in this session?** Two things make the answer yes: the next phase needs this phase as a **primary source**, or you have enough [smart zone](https://www.aihero.dev/ai-coding-dictionary/smart-zone) left (~150k tokens) for the next phase to fit. Grilling → implementation is the standard yes: the implementation wants the reasoning verbatim, not a summary of it. Continue costs nothing and loses nothing, so rule it out before anything else.
22
-
23
- **2. Is the context irrelevant to what comes next?** Is everything in this session (the exploration, the decisions, the dead ends) disposable? If so, **`/clear`**. It is the cheapest move on the board: it takes no time and hands back the whole window. `/clear` also isn't terminal: the old session stays resumable.
24
-
25
- The cost of getting this wrong is one-way. Clear a *relevant* context and you lose the **why** behind what you built, and no amount of reading the diff back gets it returned.
26
-
27
- **3. Do you need to hand off?** `/handoff` is narrow. You need it only when you are:
28
-
29
- - swapping to a **new harness** (Claude → Codex),
30
- - moving to a **new directory** or repo,
31
- - sending the work to a **colleague**,
32
- - or forking a side task you found **mid-phase** without derailing what you're doing.
33
-
34
- That list is the whole clause. What `/handoff` buys is **portability**: a file that travels. If nothing is travelling, you don't need it.
35
-
36
- **4. Can the task be done AFK?** Is it scoped tightly enough to run with you away from the keyboard, no steering? Then send it to a **subagent** and leave this session untouched. Automated review is the standard case: the agent reads the diff and reports, and you aren't needed while it does.
37
-
38
- **5. Otherwise, `/compact`.** Relevant context, same harness, same directory, and you need to stay in the loop: this is where the tree lands, and it lands here often. Pass it an instruction (`/compact we're going to QA this area`) so the summary keeps what the next phase needs.
39
-
40
- `/compact` is the **default, not the first reach**. It sits at the bottom because the four questions above it are all cheaper or more precise. The failure mode when people start here is a fresh session that is confidently wrong about a decision the summary flattened.
41
-
42
- ## Primary and secondary sources
43
-
44
- Every move except **Continue** turns a **primary source** into a **secondary source**: the session as it happened, replaced by a summary of it. The trade is always the same shape:
45
-
46
- | Source | Information | Noise | Room to move |
47
- | --------------------------------- | ----------- | ----- | ------------ |
48
- | Primary (Continue) | Full | Lots | Little |
49
- | Secondary (`/compact`, `/handoff`) | Lossy | Less | Lots |
50
-
51
- This is why question 1 comes first. You only pay the lossiness when staying costs more than it saves.
52
-
53
- ## These are judgement calls
54
-
55
- The questions are not objective: each has taste in it, and the same boundary can go two ways on two days. The value is in asking them **in order**, at the boundary rather than in the middle of the work.