@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.
- package/README.md +3 -2
- package/bin/cli.js +51 -122
- package/bin/skill-boundaries.js +0 -8
- package/bin/skill-config.js +21 -0
- package/bin/skill-selection.js +27 -0
- package/config/required.json +5 -0
- package/package.json +2 -1
- package/scripts/sync-upstream.js +3 -12
- package/template/.opencode/CONTEXT.md +7 -7
- package/template/.pi/CONTEXT.md +7 -7
- package/template/.agents/skills/ask-matt/PHASE-BOUNDARIES.md +0 -55
- package/template/.agents/skills/ask-matt/SKILL.md +0 -90
- package/template/.agents/skills/ask-matt/agents/openai.yaml +0 -5
- package/template/.agents/skills/code-review/SKILL.md +0 -87
- package/template/.agents/skills/code-review/agents/openai.yaml +0 -3
- package/template/.agents/skills/codebase-design/DEEPENING.md +0 -37
- package/template/.agents/skills/codebase-design/DESIGN-IT-TWICE.md +0 -44
- package/template/.agents/skills/codebase-design/SKILL.md +0 -114
- package/template/.agents/skills/codebase-design/agents/openai.yaml +0 -3
- package/template/.agents/skills/diagnose-fix/SKILL.md +0 -43
- package/template/.agents/skills/diagnose-fix/agents/openai.yaml +0 -5
- package/template/.agents/skills/diagnose-fix/references/anti-patterns.md +0 -21
- package/template/.agents/skills/diagnosing-bugs/SKILL.md +0 -138
- package/template/.agents/skills/diagnosing-bugs/agents/openai.yaml +0 -3
- package/template/.agents/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +0 -44
- package/template/.agents/skills/domain-modeling/ADR-FORMAT.md +0 -47
- package/template/.agents/skills/domain-modeling/CONTEXT-FORMAT.md +0 -60
- package/template/.agents/skills/domain-modeling/SKILL.md +0 -74
- package/template/.agents/skills/domain-modeling/agents/openai.yaml +0 -3
- package/template/.agents/skills/grill-me/SKILL.md +0 -7
- package/template/.agents/skills/grill-me/agents/openai.yaml +0 -5
- package/template/.agents/skills/grill-to-spec/SKILL.md +0 -55
- package/template/.agents/skills/grill-to-spec/agents/openai.yaml +0 -5
- package/template/.agents/skills/grill-to-spec/references/rules.md +0 -47
- package/template/.agents/skills/grill-with-docs/SKILL.md +0 -7
- package/template/.agents/skills/grill-with-docs/agents/openai.yaml +0 -5
- package/template/.agents/skills/grilling/SKILL.md +0 -28
- package/template/.agents/skills/grilling/agents/openai.yaml +0 -3
- package/template/.agents/skills/handoff/SKILL.md +0 -16
- package/template/.agents/skills/handoff/agents/openai.yaml +0 -5
- package/template/.agents/skills/implement/SKILL.md +0 -15
- package/template/.agents/skills/implement/agents/openai.yaml +0 -5
- package/template/.agents/skills/implement-review-loop/SKILL.md +0 -34
- package/template/.agents/skills/implement-review-loop/agents/openai.yaml +0 -5
- package/template/.agents/skills/improve-codebase-architecture/HTML-REPORT.md +0 -123
- package/template/.agents/skills/improve-codebase-architecture/SKILL.md +0 -71
- package/template/.agents/skills/improve-codebase-architecture/agents/openai.yaml +0 -5
- package/template/.agents/skills/instance-test/SKILL.md +0 -70
- package/template/.agents/skills/instance-test/agents/openai.yaml +0 -5
- package/template/.agents/skills/instance-test/references/instances.md +0 -75
- package/template/.agents/skills/prototype/LOGIC.md +0 -67
- package/template/.agents/skills/prototype/SKILL.md +0 -26
- package/template/.agents/skills/prototype/UI.md +0 -112
- package/template/.agents/skills/prototype/agents/openai.yaml +0 -3
- package/template/.agents/skills/research/SKILL.md +0 -12
- package/template/.agents/skills/research/agents/openai.yaml +0 -3
- package/template/.agents/skills/resolving-merge-conflicts/SKILL.md +0 -14
- package/template/.agents/skills/resolving-merge-conflicts/agents/openai.yaml +0 -3
- package/template/.agents/skills/scaffold-functional-test/SKILL.md +0 -64
- package/template/.agents/skills/scaffold-functional-test/agents/openai.yaml +0 -5
- package/template/.agents/skills/scaffold-functional-test/references/schema.md +0 -80
- package/template/.agents/skills/setup-matt-pocock-skills/SKILL.md +0 -116
- package/template/.agents/skills/setup-matt-pocock-skills/agents/openai.yaml +0 -5
- package/template/.agents/skills/setup-matt-pocock-skills/domain.md +0 -51
- package/template/.agents/skills/setup-matt-pocock-skills/issue-tracker-github.md +0 -45
- package/template/.agents/skills/setup-matt-pocock-skills/issue-tracker-gitlab.md +0 -46
- package/template/.agents/skills/setup-matt-pocock-skills/issue-tracker-local.md +0 -30
- package/template/.agents/skills/setup-matt-pocock-skills/triage-labels.md +0 -15
- package/template/.agents/skills/show-me/SKILL.md +0 -28
- package/template/.agents/skills/tdd/SKILL.md +0 -38
- package/template/.agents/skills/tdd/agents/openai.yaml +0 -3
- package/template/.agents/skills/tdd/mocking.md +0 -59
- package/template/.agents/skills/tdd/tests.md +0 -77
- package/template/.agents/skills/tdd-implement/SKILL.md +0 -81
- package/template/.agents/skills/tdd-implement/agents/openai.yaml +0 -5
- package/template/.agents/skills/tdd-implement/references/finalize.md +0 -16
- package/template/.agents/skills/tdd-implement/references/orchestration.md +0 -54
- package/template/.agents/skills/tdd-implement/references/verify.md +0 -15
- package/template/.agents/skills/teach/GLOSSARY-FORMAT.md +0 -35
- package/template/.agents/skills/teach/LEARNING-RECORD-FORMAT.md +0 -46
- package/template/.agents/skills/teach/MISSION-FORMAT.md +0 -31
- package/template/.agents/skills/teach/RESOURCES-FORMAT.md +0 -32
- package/template/.agents/skills/teach/SKILL.md +0 -140
- package/template/.agents/skills/teach/agents/openai.yaml +0 -5
- package/template/.agents/skills/to-questionnaire/SKILL.md +0 -54
- package/template/.agents/skills/to-questionnaire/agents/openai.yaml +0 -5
- package/template/.agents/skills/to-spec/SKILL.md +0 -75
- package/template/.agents/skills/to-spec/agents/openai.yaml +0 -5
- package/template/.agents/skills/to-tickets/SKILL.md +0 -105
- package/template/.agents/skills/to-tickets/agents/openai.yaml +0 -5
- package/template/.agents/skills/triage/AGENT-BRIEF.md +0 -207
- package/template/.agents/skills/triage/OUT-OF-SCOPE.md +0 -105
- package/template/.agents/skills/triage/SKILL.md +0 -112
- package/template/.agents/skills/triage/agents/openai.yaml +0 -5
- package/template/.agents/skills/wait-what/SKILL.md +0 -7
- package/template/.agents/skills/wait-what/agents/openai.yaml +0 -5
- package/template/.agents/skills/wayfinder/SKILL.md +0 -128
- package/template/.agents/skills/wayfinder/agents/openai.yaml +0 -5
- package/template/.agents/skills/wizard/SKILL.md +0 -44
- package/template/.agents/skills/wizard/agents/openai.yaml +0 -3
- package/template/.agents/skills/wizard/template.sh +0 -204
- package/template/.agents/skills/writing-for-agents/SKILL-MECHANICS.md +0 -22
- package/template/.agents/skills/writing-for-agents/SKILL.md +0 -81
- 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
|
|
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)
|
|
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)
|
|
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
|
|
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
|
-
|
|
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,
|
|
165
|
+
content = await readFile(path.join(SKILLS_DIR, name, 'SKILL.md'), 'utf8');
|
|
175
166
|
} catch {
|
|
176
167
|
continue;
|
|
177
168
|
}
|
|
178
|
-
const {
|
|
179
|
-
if (
|
|
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'
|
|
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
|
|
247
|
-
const
|
|
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 ?
|
|
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(
|
|
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
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
const
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
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
|
|
342
|
-
const
|
|
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 =
|
|
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
|
-
//
|
|
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、.
|
|
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
|
-
|
|
440
|
-
|
|
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
|
|
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
|
-
//
|
|
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');
|
package/bin/skill-boundaries.js
CHANGED
|
@@ -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
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heihei0299/matt-skills",
|
|
3
|
-
"version": "
|
|
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": {
|
package/scripts/sync-upstream.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
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: Workspace → Template 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
|
|
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
|
|
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
|
|
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
|
package/template/.pi/CONTEXT.md
CHANGED
|
@@ -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)
|
|
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
|
|
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
|
|
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
|
|
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: Workspace → Template 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
|
|
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
|
|
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
|
|
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.
|