@heihei0299/matt-skills 1.3.2 → 1.5.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 (139) hide show
  1. package/.agents/skills/ci-guard/SKILL.md +94 -0
  2. package/.agents/skills/ci-guard/agents/openai.yaml +5 -0
  3. package/.agents/skills/commit-check/SKILL.md +13 -18
  4. package/.agents/skills/diagnose-fix/SKILL.md +0 -1
  5. package/.agents/skills/grill-to-spec/SKILL.md +0 -3
  6. package/.agents/skills/instance-test/SKILL.md +36 -27
  7. package/.agents/skills/instance-test/agents/openai.yaml +1 -1
  8. package/.agents/skills/instance-test/references/instances.md +63 -36
  9. package/.agents/skills/scaffold-functional-test/SKILL.md +75 -0
  10. package/.agents/skills/scaffold-functional-test/agents/openai.yaml +5 -0
  11. package/.agents/skills/tdd-implement/SKILL.md +0 -1
  12. package/.agents/skills/tdd-implement/references/orchestration.md +2 -2
  13. package/.agents/skills/tdd-implement/references/stages.md +2 -7
  14. package/README.md +55 -54
  15. package/bin/cli.js +216 -64
  16. package/config/engineering.json +20 -0
  17. package/config/proprietary.json +8 -1
  18. package/package.json +2 -1
  19. package/scripts/sync-upstream.js +69 -43
  20. package/template/.agents/skills/ask-matt/PHASE-BOUNDARIES.md +55 -0
  21. package/template/.agents/skills/ask-matt/SKILL.md +90 -0
  22. package/template/.agents/skills/ask-matt/agents/openai.yaml +5 -0
  23. package/template/.agents/skills/ci-guard/SKILL.md +94 -0
  24. package/template/.agents/skills/ci-guard/agents/openai.yaml +5 -0
  25. package/template/.agents/skills/code-review/SKILL.md +87 -0
  26. package/template/.agents/skills/code-review/agents/openai.yaml +3 -0
  27. package/template/.agents/skills/codebase-design/DEEPENING.md +37 -0
  28. package/template/.agents/skills/codebase-design/DESIGN-IT-TWICE.md +44 -0
  29. package/template/.agents/skills/codebase-design/SKILL.md +114 -0
  30. package/template/.agents/skills/codebase-design/agents/openai.yaml +3 -0
  31. package/template/{.opencode → .agents}/skills/commit-check/SKILL.md +13 -18
  32. package/template/{.pi → .agents}/skills/diagnose-fix/SKILL.md +0 -1
  33. package/template/.agents/skills/diagnosing-bugs/SKILL.md +138 -0
  34. package/template/.agents/skills/diagnosing-bugs/agents/openai.yaml +3 -0
  35. package/template/.agents/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +44 -0
  36. package/template/.agents/skills/domain-modeling/ADR-FORMAT.md +47 -0
  37. package/template/.agents/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
  38. package/template/.agents/skills/domain-modeling/SKILL.md +74 -0
  39. package/template/.agents/skills/domain-modeling/agents/openai.yaml +3 -0
  40. package/template/.agents/skills/grill-me/SKILL.md +7 -0
  41. package/template/.agents/skills/grill-me/agents/openai.yaml +5 -0
  42. package/template/{.pi → .agents}/skills/grill-to-spec/SKILL.md +0 -3
  43. package/template/.agents/skills/grill-with-docs/SKILL.md +7 -0
  44. package/template/.agents/skills/grill-with-docs/agents/openai.yaml +5 -0
  45. package/template/.agents/skills/grilling/SKILL.md +28 -0
  46. package/template/.agents/skills/grilling/agents/openai.yaml +3 -0
  47. package/template/.agents/skills/handoff/SKILL.md +16 -0
  48. package/template/.agents/skills/handoff/agents/openai.yaml +5 -0
  49. package/template/.agents/skills/implement/SKILL.md +15 -0
  50. package/template/.agents/skills/implement/agents/openai.yaml +5 -0
  51. package/template/.agents/skills/improve-codebase-architecture/HTML-REPORT.md +123 -0
  52. package/template/.agents/skills/improve-codebase-architecture/SKILL.md +71 -0
  53. package/template/.agents/skills/improve-codebase-architecture/agents/openai.yaml +5 -0
  54. package/template/.agents/skills/instance-test/SKILL.md +70 -0
  55. package/template/.agents/skills/instance-test/agents/openai.yaml +5 -0
  56. package/template/.agents/skills/instance-test/references/instances.md +75 -0
  57. package/template/.agents/skills/prototype/LOGIC.md +67 -0
  58. package/template/.agents/skills/prototype/SKILL.md +26 -0
  59. package/template/.agents/skills/prototype/UI.md +112 -0
  60. package/template/.agents/skills/prototype/agents/openai.yaml +3 -0
  61. package/template/.agents/skills/research/SKILL.md +12 -0
  62. package/template/.agents/skills/research/agents/openai.yaml +3 -0
  63. package/template/.agents/skills/resolving-merge-conflicts/SKILL.md +14 -0
  64. package/template/.agents/skills/resolving-merge-conflicts/agents/openai.yaml +3 -0
  65. package/template/.agents/skills/scaffold-functional-test/SKILL.md +75 -0
  66. package/template/.agents/skills/scaffold-functional-test/agents/openai.yaml +5 -0
  67. package/template/.agents/skills/setup-matt-pocock-skills/SKILL.md +116 -0
  68. package/template/.agents/skills/setup-matt-pocock-skills/agents/openai.yaml +5 -0
  69. package/template/.agents/skills/setup-matt-pocock-skills/domain.md +51 -0
  70. package/template/.agents/skills/setup-matt-pocock-skills/issue-tracker-github.md +45 -0
  71. package/template/.agents/skills/setup-matt-pocock-skills/issue-tracker-gitlab.md +46 -0
  72. package/template/.agents/skills/setup-matt-pocock-skills/issue-tracker-local.md +30 -0
  73. package/template/.agents/skills/setup-matt-pocock-skills/triage-labels.md +15 -0
  74. package/template/.agents/skills/tdd/SKILL.md +38 -0
  75. package/template/.agents/skills/tdd/agents/openai.yaml +3 -0
  76. package/template/.agents/skills/tdd/mocking.md +59 -0
  77. package/template/.agents/skills/tdd/tests.md +77 -0
  78. package/template/{.opencode → .agents}/skills/tdd-implement/SKILL.md +0 -1
  79. package/template/{.pi → .agents}/skills/tdd-implement/references/orchestration.md +2 -2
  80. package/template/{.opencode → .agents}/skills/tdd-implement/references/stages.md +2 -7
  81. package/template/.agents/skills/teach/GLOSSARY-FORMAT.md +35 -0
  82. package/template/.agents/skills/teach/LEARNING-RECORD-FORMAT.md +46 -0
  83. package/template/.agents/skills/teach/MISSION-FORMAT.md +31 -0
  84. package/template/.agents/skills/teach/RESOURCES-FORMAT.md +32 -0
  85. package/template/.agents/skills/teach/SKILL.md +140 -0
  86. package/template/.agents/skills/teach/agents/openai.yaml +5 -0
  87. package/template/.agents/skills/to-questionnaire/SKILL.md +54 -0
  88. package/template/.agents/skills/to-questionnaire/agents/openai.yaml +5 -0
  89. package/template/.agents/skills/to-spec/SKILL.md +75 -0
  90. package/template/.agents/skills/to-spec/agents/openai.yaml +5 -0
  91. package/template/.agents/skills/to-tickets/SKILL.md +105 -0
  92. package/template/.agents/skills/to-tickets/agents/openai.yaml +5 -0
  93. package/template/.agents/skills/triage/AGENT-BRIEF.md +207 -0
  94. package/template/.agents/skills/triage/OUT-OF-SCOPE.md +105 -0
  95. package/template/.agents/skills/triage/SKILL.md +112 -0
  96. package/template/.agents/skills/triage/agents/openai.yaml +5 -0
  97. package/template/.agents/skills/wait-what/SKILL.md +7 -0
  98. package/template/.agents/skills/wait-what/agents/openai.yaml +5 -0
  99. package/template/.agents/skills/wayfinder/SKILL.md +128 -0
  100. package/template/.agents/skills/wayfinder/agents/openai.yaml +5 -0
  101. package/template/.agents/skills/wizard/SKILL.md +44 -0
  102. package/template/.agents/skills/wizard/agents/openai.yaml +3 -0
  103. package/template/.agents/skills/wizard/template.sh +204 -0
  104. package/template/.agents/skills/writing-for-agents/SKILL-MECHANICS.md +22 -0
  105. package/template/.agents/skills/writing-for-agents/SKILL.md +81 -0
  106. package/template/.agents/skills/writing-for-agents/agents/openai.yaml +3 -0
  107. package/template/.opencode/CONTEXT.md +8 -8
  108. package/template/.opencode/skills/.gitkeep +0 -0
  109. package/template/.opencode/skills/README.md +4 -0
  110. package/template/.pi/CONTEXT.md +8 -8
  111. package/template/.pi/skills/.gitkeep +0 -0
  112. package/template/.pi/skills/README.md +4 -0
  113. package/template/AGENTS.md +5 -8
  114. package/template/.opencode/skills/diagnose-fix/SKILL.md +0 -66
  115. package/template/.opencode/skills/grill-to-spec/SKILL.md +0 -83
  116. package/template/.opencode/skills/instance-test/SKILL.md +0 -61
  117. package/template/.opencode/skills/instance-test/agents/openai.yaml +0 -5
  118. package/template/.opencode/skills/instance-test/references/instances.md +0 -48
  119. package/template/.opencode/skills/tdd-implement/references/orchestration.md +0 -136
  120. package/template/.pi/skills/commit-check/SKILL.md +0 -67
  121. package/template/.pi/skills/commit-check/agents/openai.yaml +0 -5
  122. package/template/.pi/skills/commit-check/scripts/scan-sensitive.sh +0 -36
  123. package/template/.pi/skills/diagnose-fix/agents/openai.yaml +0 -5
  124. package/template/.pi/skills/diagnose-fix/references/anti-patterns.md +0 -20
  125. package/template/.pi/skills/grill-to-spec/agents/openai.yaml +0 -5
  126. package/template/.pi/skills/grill-to-spec/references/rules.md +0 -33
  127. package/template/.pi/skills/instance-test/SKILL.md +0 -61
  128. package/template/.pi/skills/instance-test/agents/openai.yaml +0 -5
  129. package/template/.pi/skills/instance-test/references/instances.md +0 -48
  130. package/template/.pi/skills/tdd-implement/SKILL.md +0 -48
  131. package/template/.pi/skills/tdd-implement/agents/openai.yaml +0 -5
  132. package/template/.pi/skills/tdd-implement/references/stages.md +0 -316
  133. /package/template/{.opencode → .agents}/skills/commit-check/agents/openai.yaml +0 -0
  134. /package/template/{.opencode → .agents}/skills/commit-check/scripts/scan-sensitive.sh +0 -0
  135. /package/template/{.opencode → .agents}/skills/diagnose-fix/agents/openai.yaml +0 -0
  136. /package/template/{.opencode → .agents}/skills/diagnose-fix/references/anti-patterns.md +0 -0
  137. /package/template/{.opencode → .agents}/skills/grill-to-spec/agents/openai.yaml +0 -0
  138. /package/template/{.opencode → .agents}/skills/grill-to-spec/references/rules.md +0 -0
  139. /package/template/{.opencode → .agents}/skills/tdd-implement/agents/openai.yaml +0 -0
package/bin/cli.js CHANGED
@@ -7,7 +7,26 @@ import prompts from 'prompts';
7
7
 
8
8
  const SKILLS_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', '.agents', 'skills');
9
9
  const TEMPLATE_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'template');
10
- const PROPRIETARY_SKILLS = new Set(['tdd-implement', 'grill-to-spec', 'diagnose-fix', 'commit-check', 'instance-test']);
10
+ const PROPRIETARY_SKILLS = new Set(['ci-guard', 'tdd-implement', 'grill-to-spec', 'diagnose-fix', 'commit-check', 'scaffold-functional-test']);
11
+ const PROPRIETARY_DEFAULT = new Set(['tdd-implement', 'diagnose-fix', 'commit-check', 'grill-to-spec']); // 默认仅装核心 3,--all 才装全部 6
12
+ const ENGINEERING_PATH = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'config', 'engineering.json');
13
+ let ENGINEERING_SKILLS = null;
14
+ async function loadEngineeringSkills() {
15
+ if (ENGINEERING_SKILLS) return ENGINEERING_SKILLS;
16
+ try {
17
+ const raw = await readFile(ENGINEERING_PATH, 'utf8');
18
+ ENGINEERING_SKILLS = new Set(JSON.parse(raw));
19
+ } catch {
20
+ 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']);
21
+ }
22
+ return ENGINEERING_SKILLS;
23
+ }
24
+ function isProgrammingSkill(name, engineering) {
25
+ return PROPRIETARY_DEFAULT.has(name) || engineering.has(name);
26
+ }
27
+ function isProgrammingAll(name, engineering) {
28
+ return PROPRIETARY_SKILLS.has(name) || engineering.has(name);
29
+ }
11
30
  process.stdout.on('error', (err) => {
12
31
  if (err.code === 'EPIPE') process.exit(0);
13
32
  throw err;
@@ -16,31 +35,37 @@ process.stdout.on('error', (err) => {
16
35
  const HELP = `matt-skills — install and manage this skill collection
17
36
 
18
37
  Usage:
19
- matt-skills init [options] Initialize a project: template + upstream skills
20
- matt-skills sync [--apply|--force] [--dest <path>] [--upstream <url>] [--ref <ref>] [--json] Sync existing project to latest template + skills
21
- matt-skills list [--json] List available skills and their descriptions
38
+ matt-skills init [options] Initialize a project: template + skills (.agents/skills)
39
+ matt-skills sync [--apply|--force] [--all] [--dest <path>] [--upstream <url>] [--ref <ref>] [--json] Sync existing project to latest template + skills
40
+ matt-skills list [--all] [--json] List available skills and their descriptions
22
41
  matt-skills install [options] Install skills (interactive by default)
23
- matt-skills check [--json] [--upstream <url>] [--ref <ref>]
42
+ matt-skills check [--all] [--json] [--upstream <url>] [--ref <ref>]
24
43
  Check if upstream skills are up to date (read-only)
25
44
  matt-skills --help Show this help
26
45
 
27
46
  Init options:
28
47
  --dest <path> Target directory (default: current directory)
29
48
  --force Overwrite existing files
49
+ --all Include non-programming skills (productivity) and optional proprietary; default only core programming (engineering 18 + default proprietary 4 → 22)
30
50
  Sync options:
31
51
  --apply Apply changes (safe incremental, respects custom AGENTS.md)
32
52
  --force Hard overwrite (backup .bak + full sync)
53
+ --all Include non-programming and optional proprietary; default only core programming (22)
33
54
  --dest <path> Target directory (default: current directory)
34
55
  --upstream <url> Upstream repo URL (default: https://github.com/mattpocock/skills.git)
35
56
  --ref <ref> Upstream ref (default: HEAD)
36
57
  --json Output as JSON
37
58
  Check options:
59
+ --all Include non-programming and optional proprietary; default only core programming (22)
38
60
  --json Output as JSON
39
61
  --upstream <url> Upstream repo URL (default: https://github.com/mattpocock/skills.git)
40
62
  --ref <ref> Upstream ref (default: HEAD)
63
+ List options:
64
+ --all List all skills (default only core programming 22)
65
+ --json Output as JSON
41
66
  Install options:
42
- --tools <a,b> Install for the given tools (codex, pi, opencode, claude); skips tool selection
43
- --all Install all skills; skips skill selection
67
+ --tools <a,b> Install for the given tools (codex, pi, opencode, claude); skips tool selection — 共享技能统一指向 .agents/skills,.pi/skills/.opencode/skills 仅用于项目自定义
68
+ --all Install all skills (default only core programming 22); skips skill selection
44
69
  --force Overwrite existing skills
45
70
  --global Install to the user's global skill directories
46
71
  --project Install to project skill directories (default)
@@ -63,12 +88,16 @@ function parseFrontmatter(text) {
63
88
  return fields;
64
89
  }
65
90
 
66
- async function listSkills() {
91
+ async function listSkills({ onlyProgramming = false } = {}) {
67
92
  const entries = await readdir(SKILLS_DIR, { withFileTypes: true });
68
93
  const skills = [];
94
+ let engineering = null;
95
+ if (onlyProgramming) engineering = await loadEngineeringSkills();
69
96
  for (const entry of entries) {
70
97
  if (!entry.isDirectory()) continue;
71
98
  if (entry.name.endsWith('.bak')) continue;
99
+ if (entry.name === 'skill-creator') continue;
100
+ if (onlyProgramming && !isProgrammingSkill(entry.name, engineering)) continue;
72
101
  let content;
73
102
  try {
74
103
  content = await readFile(path.join(SKILLS_DIR, entry.name, 'SKILL.md'), 'utf8');
@@ -99,11 +128,13 @@ async function backupIfExists(p) {
99
128
 
100
129
  const TOOLS = ['codex', 'pi', 'opencode', 'claude'];
101
130
 
131
+ // 统一源:共享技能全部在 .agents/skills,harness 的 .pi/skills/.opencode/skills 仅用于项目自定义
132
+ // 为兼容历史,pi/opencode/claude 的项目安装仍解析但统一指向 .agents/skills,并给出提示
102
133
  const PROJECT_DIRS = {
103
134
  codex: '.agents/skills',
104
- pi: '.pi/skills',
105
- opencode: '.opencode/skills',
106
- claude: '.claude/skills',
135
+ pi: '.agents/skills',
136
+ opencode: '.agents/skills',
137
+ claude: '.agents/skills',
107
138
  };
108
139
 
109
140
  const GLOBAL_DIRS = {
@@ -141,7 +172,10 @@ async function promptSkills(skills) {
141
172
  }
142
173
 
143
174
  async function installCommand({ dest, all, force, tools, global }) {
144
- const skills = await listSkills();
175
+ const onlyProgramming = !all;
176
+ const engineering = onlyProgramming ? await loadEngineeringSkills() : null;
177
+ const skillsAll = await listSkills({ onlyProgramming: false });
178
+ const skills = onlyProgramming ? skillsAll.filter(s => isProgrammingSkill(s.name, engineering)) : skillsAll;
145
179
  let targets;
146
180
  if (dest) {
147
181
  targets = [{ tool: null, dir: path.resolve(process.cwd(), dest) }];
@@ -153,7 +187,18 @@ async function installCommand({ dest, all, force, tools, global }) {
153
187
  process.stdout.write('未选择任何工具,未安装任何技能\n');
154
188
  return;
155
189
  }
190
+ // 共享技能统一源提示:pi/opencode 的项目目录已改为 .agents/skills
191
+ const needsHint = selectedTools.some((t) => t === 'pi' || t === 'opencode');
192
+ if (needsHint && !global) {
193
+ process.stdout.write('提示:共享技能统一在 .agents/skills,.pi/skills/.opencode/skills 仅用于项目自定义技能\n');
194
+ }
156
195
  targets = selectedTools.map((tool) => ({ tool, dir: toolDir(tool, global) }));
196
+ // 去重:多个工具可能映射到同一目录(如 pi/opencode/codex 都指向 .agents/skills),合并去重避免重复计数
197
+ const seen = new Map();
198
+ for (const t of targets) {
199
+ if (!seen.has(t.dir)) seen.set(t.dir, t);
200
+ }
201
+ targets = [...seen.values()];
157
202
  }
158
203
  const selected = all ? skills.map((s) => s.name) : await promptSkills(skills);
159
204
  if (selected.length === 0) {
@@ -178,9 +223,10 @@ async function installCommand({ dest, all, force, tools, global }) {
178
223
  }
179
224
  }
180
225
 
181
- async function initCommand({ dest, force }) {
226
+ async function initCommand({ dest, force, all }) {
182
227
  const target = dest ? path.resolve(process.cwd(), dest) : process.cwd();
183
228
  const marker = path.join(target, 'AGENTS.md');
229
+ const onlyProgramming = !all;
184
230
  if (!force && (await pathExists(marker))) {
185
231
  process.stdout.write('模板已存在(AGENTS.md),跳过;用 --force 覆盖\n');
186
232
  } else {
@@ -188,50 +234,66 @@ async function initCommand({ dest, force }) {
188
234
  const cur = path.join(target, 'AGENTS.md');
189
235
  if (await pathExists(cur)) await backupIfExists(cur);
190
236
  await cp(TEMPLATE_DIR, target, { recursive: true, force: true });
191
- process.stdout.write('模板:已覆盖(AGENTS.md、.opencode/、.pi/)\n');
237
+ process.stdout.write('模板:已覆盖(AGENTS.md、.agents/skills、.opencode/、.pi/)\n');
192
238
  } else {
193
239
  await cp(TEMPLATE_DIR, target, { recursive: true, force: true });
194
- process.stdout.write('模板:已复制(AGENTS.md、.opencode/、.pi/)\n');
240
+ process.stdout.write('模板:已复制(AGENTS.md、.agents/skills、.opencode/、.pi/)\n');
241
+ }
242
+ // 默认仅编程(engineering + proprietary),--all 才保留 productivity
243
+ if (onlyProgramming && path.resolve(target) !== path.resolve(path.join(path.dirname(fileURLToPath(import.meta.url)), '..'))) {
244
+ const engineering = await loadEngineeringSkills();
245
+ const skillsDirFilter = path.join(target, '.agents', 'skills');
246
+ try {
247
+ const entries = await readdir(skillsDirFilter, { withFileTypes: true });
248
+ for (const e of entries) {
249
+ if (!e.isDirectory()) continue;
250
+ if (e.name.endsWith('.bak') || e.name === '.git' || e.name === 'skill-creator') continue;
251
+ if (!isProgrammingSkill(e.name, engineering)) {
252
+ await rm(path.join(skillsDirFilter, e.name), { recursive: true, force: true });
253
+ }
254
+ }
255
+ } catch {}
195
256
  }
196
257
  }
197
- const entries = await readdir(SKILLS_DIR, { withFileTypes: true });
198
- const upstream = entries
199
- .filter((e) => e.isDirectory() && !PROPRIETARY_SKILLS.has(e.name) && !e.name.endsWith('.bak'))
200
- .map((e) => e.name)
201
- .sort();
258
+ // 统计(区分编程 vs 全量)
202
259
  const skillsDir = path.join(target, '.agents', 'skills');
203
260
  let installed = 0;
204
- let skipped = 0;
205
- for (const name of upstream) {
206
- const src = path.join(SKILLS_DIR, name);
207
- const dst = path.join(skillsDir, name);
208
- if (src === dst) {
209
- skipped++;
210
- continue;
211
- }
212
- if (!force && (await pathExists(dst))) {
213
- skipped++;
214
- continue;
261
+ try {
262
+ const entries = await readdir(skillsDir, { withFileTypes: true });
263
+ installed = entries.filter((e) => e.isDirectory() && !e.name.endsWith('.bak') && e.name !== '.git' && e.name !== 'skill-creator').length;
264
+ } catch {}
265
+ const allSkillsFull = await listSkills({ onlyProgramming: false });
266
+ const engineeringForStats = await loadEngineeringSkills();
267
+ const programmingCount = allSkillsFull.filter(s => isProgrammingSkill(s.name, engineeringForStats)).length;
268
+ const upstreamFull = allSkillsFull.filter((s) => !PROPRIETARY_SKILLS.has(s.name)).length;
269
+ const upstreamProg = allSkillsFull.filter((s) => !PROPRIETARY_SKILLS.has(s.name) && engineeringForStats.has(s.name)).length;
270
+ const displayTotal = onlyProgramming ? programmingCount : allSkillsFull.length;
271
+ const displayUpstream = onlyProgramming ? upstreamProg : upstreamFull;
272
+ if (path.resolve(skillsDir) === path.resolve(SKILLS_DIR)) {
273
+ process.stdout.write(`技能:已装 ${installed}、跳过 0(全量 ${allSkillsFull.length},含上游 ${upstreamFull};编程 ${programmingCount},含上游 ${upstreamProg})\n`);
274
+ } else {
275
+ if (onlyProgramming) {
276
+ process.stdout.write(`技能:已装 ${installed}(编程 ${displayTotal},含上游 ${displayUpstream};全量 ${allSkillsFull.length},含上游 ${upstreamFull})\n`);
277
+ } else {
278
+ process.stdout.write(`技能:已装 ${installed}(全量 ${displayTotal},含上游 ${displayUpstream})\n`);
215
279
  }
216
- await cp(src, dst, { recursive: true, force: true });
217
- installed++;
218
280
  }
219
- process.stdout.write(`上游技能:已装 ${installed}、跳过 ${skipped}\n`);
220
281
  process.stdout.write(`目标路径:${target}\n`);
221
282
  }
222
-
223
- async function syncCommand({ dest, force, apply, upstreamUrl, ref, json }) {
283
+ async function syncCommand({ dest, force, apply, upstreamUrl, ref, json, all }) {
284
+ const onlyProgramming = !all;
224
285
  // 默认:仅对比不写盘 (check 模式),--apply / --force 显式写盘
225
286
  const doApply = apply || force;
226
287
  if (!doApply) {
227
288
  const { compare } = await import('../scripts/sync-upstream.js');
228
- const cmp = await compare({ upstreamUrl, ref });
289
+ const cmp = await compare({ upstreamUrl, ref, onlyProgramming });
229
290
  if (json) {
230
- process.stdout.write(JSON.stringify({ head: cmp.head, counts: cmp.counts, result: cmp.result }, null, 2) + '\n');
291
+ process.stdout.write(JSON.stringify({ head: cmp.head, counts: cmp.counts, result: cmp.result, onlyProgramming }, null, 2) + '\n');
231
292
  } else {
232
293
  const lines = [];
233
294
  lines.push(`上游 HEAD: ${cmp.head}`);
234
- lines.push(`本地非独有: ${cmp.counts.local} 上游: ${cmp.counts.upstream}`);
295
+ const modeHint = onlyProgramming ? '(仅编程)' : '(全量)';
296
+ lines.push(`本地非独有: ${cmp.counts.local} 上游: ${cmp.counts.upstream} ${modeHint}`);
235
297
  lines.push('');
236
298
  const totalDiff = cmp.result.added.length + cmp.result.updated.length + cmp.result.removed.length + cmp.result.renamed.length;
237
299
  if (totalDiff === 0) {
@@ -254,15 +316,46 @@ async function syncCommand({ dest, force, apply, upstreamUrl, ref, json }) {
254
316
 
255
317
  const target = dest ? path.resolve(process.cwd(), dest) : process.cwd();
256
318
  const marker = path.join(target, 'AGENTS.md');
319
+ // 模板同步:仅编程模式下过滤 skills,仅同步 programming 子集
320
+ async function copyTemplateFiltered() {
321
+ if (!onlyProgramming) {
322
+ await cp(TEMPLATE_DIR, target, { recursive: true, force: true });
323
+ return;
324
+ }
325
+ // 仅编程:分别复制非 skills 部分,skills 由后续 allSkills 循环处理
326
+ await cp(path.join(TEMPLATE_DIR, 'AGENTS.md'), path.join(target, 'AGENTS.md'), { force: true });
327
+ await cp(path.join(TEMPLATE_DIR, '.opencode'), path.join(target, '.opencode'), { recursive: true, force: true });
328
+ await cp(path.join(TEMPLATE_DIR, '.pi'), path.join(target, '.pi'), { recursive: true, force: true });
329
+ // .agents/skills 不通过模板拷贝,留给后续按 allSkills 精确同步
330
+ await mkdir(path.join(target, '.agents', 'skills'), { recursive: true });
331
+ // 若 .agents 下有非 skills 文件(未来扩展),也拷贝但排除 skills
332
+ try {
333
+ const agEntries = await readdir(path.join(TEMPLATE_DIR, '.agents'), { withFileTypes: true });
334
+ for (const e of agEntries) {
335
+ if (e.name === 'skills') continue;
336
+ const src = path.join(TEMPLATE_DIR, '.agents', e.name);
337
+ const dst = path.join(target, '.agents', e.name);
338
+ await cp(src, dst, { recursive: true, force: true });
339
+ }
340
+ } catch {}
341
+ }
257
342
  if (!(await pathExists(marker))) {
258
343
  process.stdout.write('未检测到现有项目(AGENTS.md 不存在),将执行全新初始化\n');
259
- await cp(TEMPLATE_DIR, target, { recursive: true, force: true });
260
- process.stdout.write('模板:已复制(AGENTS.md、.opencode/、.pi/)\n');
344
+ if (onlyProgramming) {
345
+ await copyTemplateFiltered();
346
+ } else {
347
+ await cp(TEMPLATE_DIR, target, { recursive: true, force: true });
348
+ }
349
+ process.stdout.write('模板:已复制(AGENTS.md、.agents/skills、.opencode/、.pi/)\n');
261
350
  } else if (force) {
262
351
  process.stdout.write('同步:检测到现有项目,将增量更新\n');
263
352
  await backupIfExists(path.join(target, 'AGENTS.md'));
264
- await cp(TEMPLATE_DIR, target, { recursive: true, force: true });
265
- process.stdout.write('模板:已覆盖(AGENTS.md、.opencode/、.pi/)\n');
353
+ if (onlyProgramming) {
354
+ await copyTemplateFiltered();
355
+ } else {
356
+ await cp(TEMPLATE_DIR, target, { recursive: true, force: true });
357
+ }
358
+ process.stdout.write('模板:已覆盖(AGENTS.md、.agents/skills、.opencode/、.pi/)\n');
266
359
  } else if (apply) {
267
360
  process.stdout.write('同步:检测到现有项目,将增量更新\n');
268
361
  let skipAgents = false;
@@ -271,32 +364,56 @@ async function syncCommand({ dest, force, apply, upstreamUrl, ref, json }) {
271
364
  if (content.includes('tdd-implement')) skipAgents = true;
272
365
  } catch {}
273
366
  if (skipAgents) {
274
- await cp(path.join(TEMPLATE_DIR, '.opencode'), path.join(target, '.opencode'), { recursive: true, force: true });
275
- await cp(path.join(TEMPLATE_DIR, '.pi'), path.join(target, '.pi'), { recursive: true, force: true });
367
+ if (onlyProgramming) {
368
+ await cp(path.join(TEMPLATE_DIR, '.opencode'), path.join(target, '.opencode'), { recursive: true, force: true });
369
+ await cp(path.join(TEMPLATE_DIR, '.pi'), path.join(target, '.pi'), { recursive: true, force: true });
370
+ // .agents 跳过 AGENTS.md 定制,skills 由后续处理
371
+ try {
372
+ const agEntries = await readdir(path.join(TEMPLATE_DIR, '.agents'), { withFileTypes: true });
373
+ for (const e of agEntries) {
374
+ if (e.name === 'skills') continue;
375
+ await cp(path.join(TEMPLATE_DIR, '.agents', e.name), path.join(target, '.agents', e.name), { recursive: true, force: true });
376
+ }
377
+ } catch {}
378
+ } else {
379
+ await cp(path.join(TEMPLATE_DIR, '.agents'), path.join(target, '.agents'), { recursive: true, force: true });
380
+ await cp(path.join(TEMPLATE_DIR, '.opencode'), path.join(target, '.opencode'), { recursive: true, force: true });
381
+ await cp(path.join(TEMPLATE_DIR, '.pi'), path.join(target, '.pi'), { recursive: true, force: true });
382
+ }
276
383
  process.stdout.write('模板:已同步(AGENTS.md 跳过,已含定制)\n');
277
384
  } else {
278
- await cp(TEMPLATE_DIR, target, { recursive: true, force: true });
279
- process.stdout.write('模板:已同步(AGENTS.md、.opencode/、.pi/)\n');
385
+ if (onlyProgramming) {
386
+ await copyTemplateFiltered();
387
+ } else {
388
+ await cp(TEMPLATE_DIR, target, { recursive: true, force: true });
389
+ }
390
+ process.stdout.write('模板:已同步(AGENTS.md、.agents/skills、.opencode/、.pi/)\n');
280
391
  }
281
392
  } else {
282
- // fallback for any other doApply case (should not reach here because !doApply already returned)
283
393
  process.stdout.write('同步:检测到现有项目,将增量更新\n');
284
- await cp(TEMPLATE_DIR, target, { recursive: true, force: true });
285
- process.stdout.write('模板:已同步(AGENTS.md、.opencode/、.pi/)\n');
394
+ if (onlyProgramming) {
395
+ await copyTemplateFiltered();
396
+ } else {
397
+ await cp(TEMPLATE_DIR, target, { recursive: true, force: true });
398
+ }
399
+ process.stdout.write('模板:已同步(AGENTS.md、.agents/skills、.opencode/、.pi/)\n');
286
400
  }
401
+ // 技能同步:按编程过滤(默认仅编程,--all 全量)
287
402
  const entries = await readdir(SKILLS_DIR, { withFileTypes: true });
288
- const upstream = entries
289
- .filter((e) => e.isDirectory() && !PROPRIETARY_SKILLS.has(e.name) && !e.name.endsWith('.bak'))
290
- .map((e) => e.name)
291
- .sort();
403
+ const allNames = entries.filter((e) => e.isDirectory() && !e.name.endsWith('.bak') && e.name !== 'skill-creator' && e.name !== '.git').map((e) => e.name);
404
+ let allSkills = allNames.sort();
405
+ if (onlyProgramming) {
406
+ const engineering = await loadEngineeringSkills();
407
+ allSkills = allSkills.filter(n => isProgrammingSkill(n, engineering));
408
+ }
292
409
  const skillsDir = path.join(target, '.agents', 'skills');
293
410
  await mkdir(skillsDir, { recursive: true });
294
411
  let installed = 0;
295
412
  let updated = 0;
296
- for (const name of upstream) {
413
+ for (const name of allSkills) {
297
414
  const src = path.join(SKILLS_DIR, name);
298
415
  const dst = path.join(skillsDir, name);
299
- if (src === dst) {
416
+ if (path.resolve(src) === path.resolve(dst)) {
300
417
  updated++;
301
418
  continue;
302
419
  }
@@ -310,6 +427,8 @@ async function syncCommand({ dest, force, apply, upstreamUrl, ref, json }) {
310
427
  installed++;
311
428
  }
312
429
  }
430
+ // --force 时删除多余;仅编程模式下多余指不在编程集合中的,--all 模式下多余指不在全量中的
431
+ // 默认 --apply 保留多余(不删除),符合“默认保留、--force 删除”
313
432
  if (force) {
314
433
  let localEntries = [];
315
434
  try {
@@ -319,12 +438,40 @@ async function syncCommand({ dest, force, apply, upstreamUrl, ref, json }) {
319
438
  if (!e.isDirectory()) continue;
320
439
  if (e.name.endsWith('.bak')) continue;
321
440
  if (e.name === '.git') continue;
322
- if (PROPRIETARY_SKILLS.has(e.name)) continue;
323
- if (upstream.includes(e.name)) continue;
441
+ if (e.name === 'skill-creator') continue;
442
+ if (allSkills.includes(e.name)) continue;
324
443
  await rm(path.join(skillsDir, e.name), { recursive: true, force: true });
325
444
  }
326
445
  }
327
- process.stdout.write(`上游技能:新增 ${installed}、更新 ${updated}\n`);
446
+ // 旧镜像自动清理:.pi/skills 与 .opencode/skills 中残留的共享技能一律删除,保留项目自定义
447
+ // 仅清理当前全量/编程集合中的技能,避免误删自定义
448
+ for (const harness of ['.pi/skills', '.opencode/skills']) {
449
+ const dir = path.join(target, harness);
450
+ if (!(await pathExists(dir))) continue;
451
+ let hsEntries = [];
452
+ try { hsEntries = await readdir(dir, { withFileTypes: true }); } catch { continue; }
453
+ for (const e of hsEntries) {
454
+ if (!e.isDirectory()) continue;
455
+ if (e.name === '.git' || e.name.endsWith('.bak')) continue;
456
+ if (e.name === '.gitkeep' || e.name === 'README.md') continue;
457
+ if (allSkills.includes(e.name) || PROPRIETARY_SKILLS.has(e.name)) {
458
+ await rm(path.join(dir, e.name), { recursive: true, force: true });
459
+ }
460
+ }
461
+ }
462
+ // 清理过时的 .pi/settings.json 指向
463
+ try {
464
+ const piSettings = path.join(target, '.pi/settings.json');
465
+ if (await pathExists(piSettings)) {
466
+ const txt = await readFile(piSettings, 'utf8');
467
+ if (txt.includes('.opencode/skills') || txt.includes('../.opencode')) {
468
+ const { writeFile } = await import('node:fs/promises');
469
+ await writeFile(piSettings, '{}\n');
470
+ }
471
+ }
472
+ } catch {}
473
+ const modeLabel = onlyProgramming ? '编程' : '全量';
474
+ process.stdout.write(`技能:新增 ${installed}、更新 ${updated}(${modeLabel} ${allSkills.length})\n`);
328
475
  process.stdout.write(`目标路径:${target}\n`);
329
476
  }
330
477
 
@@ -333,6 +480,7 @@ function parseInitArgs(args) {
333
480
  let force = false;
334
481
  let apply = false;
335
482
  let json = false;
483
+ let all = false;
336
484
  let upstreamUrl;
337
485
  let ref;
338
486
  for (let i = 0; i < args.length; i++) {
@@ -341,13 +489,14 @@ function parseInitArgs(args) {
341
489
  else if (arg.startsWith('--dest=')) dest = arg.slice('--dest='.length);
342
490
  else if (arg === '--force') force = true;
343
491
  else if (arg === '--apply') apply = true;
492
+ else if (arg === '--all') all = true;
344
493
  else if (arg === '--json') json = true;
345
494
  else if (arg === '--upstream') upstreamUrl = args[++i];
346
495
  else if (arg.startsWith('--upstream=')) upstreamUrl = arg.slice('--upstream='.length);
347
496
  else if (arg === '--ref') ref = args[++i];
348
497
  else if (arg.startsWith('--ref=')) ref = arg.slice('--ref='.length);
349
498
  }
350
- return { dest, force, apply, json, upstreamUrl, ref };
499
+ return { dest, force, apply, json, all, upstreamUrl, ref };
351
500
  }
352
501
 
353
502
  function parseInstallArgs(args) {
@@ -376,17 +525,19 @@ function parseInstallArgs(args) {
376
525
  async function checkCommand(args) {
377
526
  const { compare } = await import('../scripts/sync-upstream.js');
378
527
  const json = args.includes('--json');
528
+ const onlyProgramming = !args.includes('--all');
379
529
  const upstreamIdx = args.indexOf('--upstream');
380
530
  const upstreamUrl = upstreamIdx !== -1 ? args[upstreamIdx + 1] : undefined;
381
531
  const refIdx = args.indexOf('--ref');
382
532
  const ref = refIdx !== -1 ? args[refIdx + 1] : undefined;
383
- const cmp = await compare({ upstreamUrl, ref });
533
+ const cmp = await compare({ upstreamUrl, ref, onlyProgramming });
384
534
  if (json) {
385
- process.stdout.write(JSON.stringify({ head: cmp.head, counts: cmp.counts, result: cmp.result }, null, 2) + '\n');
535
+ process.stdout.write(JSON.stringify({ head: cmp.head, counts: cmp.counts, result: cmp.result, onlyProgramming }, null, 2) + '\n');
386
536
  } else {
387
537
  const lines = [];
388
538
  lines.push(`上游 HEAD: ${cmp.head}`);
389
- lines.push(`本地非独有: ${cmp.counts.local} 上游: ${cmp.counts.upstream}`);
539
+ const modeHint = onlyProgramming ? '(仅编程)' : '(全量)';
540
+ lines.push(`本地非独有: ${cmp.counts.local} 上游: ${cmp.counts.upstream} ${modeHint}`);
390
541
  lines.push('');
391
542
  const totalDiff = cmp.result.added.length + cmp.result.updated.length + cmp.result.removed.length + cmp.result.renamed.length;
392
543
  if (totalDiff === 0) {
@@ -414,7 +565,8 @@ async function main() {
414
565
  }
415
566
  const [command, ...rest] = args;
416
567
  if (command === 'list') {
417
- const skills = await listSkills();
568
+ const onlyProgramming = !rest.includes('--all');
569
+ const skills = await listSkills({ onlyProgramming });
418
570
  if (rest.includes('--json')) {
419
571
  process.stdout.write(`${JSON.stringify(skills, null, 2)}\n`);
420
572
  } else {
@@ -0,0 +1,20 @@
1
+ [
2
+ "ask-matt",
3
+ "code-review",
4
+ "codebase-design",
5
+ "diagnosing-bugs",
6
+ "domain-modeling",
7
+ "grill-with-docs",
8
+ "implement",
9
+ "improve-codebase-architecture",
10
+ "prototype",
11
+ "research",
12
+ "resolving-merge-conflicts",
13
+ "setup-matt-pocock-skills",
14
+ "tdd",
15
+ "to-spec",
16
+ "to-tickets",
17
+ "triage",
18
+ "wayfinder",
19
+ "wizard"
20
+ ]
@@ -1 +1,8 @@
1
- ["tdd-implement", "grill-to-spec", "diagnose-fix", "commit-check", "instance-test"]
1
+ [
2
+ "ci-guard",
3
+ "tdd-implement",
4
+ "grill-to-spec",
5
+ "diagnose-fix",
6
+ "commit-check",
7
+ "scaffold-functional-test"
8
+ ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heihei0299/matt-skills",
3
- "version": "1.3.2",
3
+ "version": "1.5.2",
4
4
  "description": "Agent skills + 项目配置模板:一条命令初始化 opencode / pi-agent 项目(含 mattpocock/skills 上游技能)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,6 +12,7 @@
12
12
  ".agents/skills/",
13
13
  "scripts/sync-upstream.js",
14
14
  "config/proprietary.json",
15
+ "config/engineering.json",
15
16
  "README.md"
16
17
  ],
17
18
  "scripts": {