@marcomigozzi/trailhead 0.4.0 → 0.5.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.it.md +22 -15
- package/README.md +23 -16
- package/bin/lib/codex-projection.js +102 -60
- package/bin/lib/codex-projection.test.js +119 -17
- package/bin/trailhead.js +205 -51
- package/bin/trailhead.test.js +195 -15
- package/package.json +1 -1
- package/plugins/trailhead/.claude-plugin/plugin.json +8 -3
- package/plugins/trailhead/agents/trailhead-code-review.md +9 -0
- package/plugins/trailhead/agents/trailhead-codebase-map.md +9 -0
- package/plugins/trailhead/agents/trailhead-debug.md +9 -0
- package/plugins/trailhead/agents/trailhead-executor.md +9 -0
- package/plugins/trailhead/agents/trailhead-fix.md +9 -0
- package/plugins/trailhead/agents/trailhead-plan.md +9 -0
- package/plugins/trailhead/agents/trailhead-research.md +9 -0
- package/plugins/trailhead/commands/adopt.md +1 -1
- package/plugins/trailhead/commands/bug.md +1 -1
- package/plugins/trailhead/commands/config.md +1 -1
- package/plugins/trailhead/commands/dashboard.md +1 -1
- package/plugins/trailhead/commands/grill.md +1 -1
- package/plugins/trailhead/commands/idea.md +1 -1
- package/plugins/trailhead/commands/inbox.md +1 -1
- package/plugins/trailhead/commands/map.md +1 -1
- package/plugins/trailhead/commands/new.md +1 -1
- package/plugins/trailhead/commands/note.md +1 -1
- package/plugins/trailhead/commands/pause.md +1 -1
- package/plugins/trailhead/commands/quick.md +3 -1
- package/plugins/trailhead/commands/resume.md +1 -1
- package/plugins/trailhead/commands/seed.md +1 -1
- package/plugins/trailhead/commands/split.md +1 -1
- package/plugins/trailhead/commands/ticket.md +1 -1
- package/plugins/trailhead/commands/todo.md +1 -1
- package/plugins/trailhead/commands/update.md +1 -1
- package/plugins/trailhead/commands/whiteboard.md +1 -1
- package/plugins/trailhead/commands/work.md +1 -1
- package/plugins/trailhead/hooks/hooks.json +4 -0
- package/plugins/trailhead/hooks/trailhead-commit-guard.js +1 -1
- package/plugins/trailhead/hooks/trailhead-install-guard.js +176 -0
- package/plugins/trailhead/hooks/trailhead-install-guard.test.js +118 -0
- package/plugins/trailhead/hooks/trailhead-secret-guard.js +19 -4
- package/plugins/trailhead/hooks/trailhead-secret-guard.test.js +41 -1
- package/plugins/trailhead/skills/{trailhead/references/configuration.md → _shared/configuration-reference.md} +29 -9
- package/plugins/trailhead/skills/_shared/configuration.md +8 -0
- package/plugins/trailhead/skills/_shared/multi-map.md +27 -0
- package/plugins/trailhead/skills/_shared/out-of-scope.md +11 -0
- package/plugins/trailhead/skills/_shared/principles.md +8 -0
- package/plugins/trailhead/skills/_shared/session-handoff.md +22 -0
- package/plugins/trailhead/skills/_shared/substrate-commands.md +159 -0
- package/plugins/trailhead/skills/_shared/substrate.md +35 -0
- package/plugins/trailhead/skills/{trailhead/references → _shared}/teamwork.md +5 -4
- package/plugins/trailhead/skills/{trailhead/references → _shared}/techniques/acceptance-testing.md +3 -2
- package/plugins/trailhead/skills/_shared/techniques/code-review.md +18 -0
- package/plugins/trailhead/skills/{trailhead/references → _shared}/techniques/codebase-map.md +1 -1
- package/plugins/trailhead/skills/{trailhead/references → _shared}/techniques/debug.md +4 -1
- package/plugins/trailhead/skills/_shared/techniques/plan-review.md +21 -0
- package/plugins/trailhead/skills/_shared/techniques/prototype.md +39 -0
- package/plugins/trailhead/skills/{trailhead/references → _shared}/techniques/research.md +2 -1
- package/plugins/trailhead/skills/_shared/techniques.md +20 -0
- package/plugins/trailhead/skills/_shared/ticket-anatomy.md +32 -0
- package/plugins/trailhead/skills/_shared/ticket-language.md +3 -0
- package/plugins/trailhead/skills/trailhead/SKILL.md +26 -453
- package/plugins/trailhead/skills/trailhead-capture/SKILL.md +29 -0
- package/plugins/trailhead/skills/{trailhead → trailhead-capture}/references/capture.md +4 -4
- package/plugins/trailhead/skills/trailhead-chart/SKILL.md +52 -0
- package/plugins/trailhead/skills/trailhead-chart/references/charting.md +24 -0
- package/plugins/trailhead/skills/{trailhead → trailhead-chart}/references/inbox.md +6 -6
- package/plugins/trailhead/skills/trailhead-manage/SKILL.md +30 -0
- package/plugins/trailhead/skills/{trailhead → trailhead-manage}/references/updating.md +12 -0
- package/plugins/trailhead/skills/trailhead-view/SKILL.md +113 -0
- package/plugins/trailhead/skills/trailhead-work/SKILL.md +66 -0
- package/plugins/trailhead/skills/trailhead-work/references/ticket-engines.md +52 -0
- package/plugins/trailhead/skills/trailhead/references/techniques/code-review.md +0 -14
- package/plugins/trailhead/skills/trailhead/references/techniques/plan-review.md +0 -24
- package/plugins/trailhead/skills/trailhead/references/techniques/prototype.md +0 -18
- /package/plugins/trailhead/skills/{trailhead/references → _shared}/techniques/domain-vocabulary.md +0 -0
- /package/plugins/trailhead/skills/{trailhead/references → _shared}/techniques/grilling.md +0 -0
- /package/plugins/trailhead/skills/{trailhead/references → _shared}/techniques/tdd.md +0 -0
package/bin/trailhead.test.js
CHANGED
|
@@ -74,7 +74,7 @@ ok('codex: SKILL.md still carries the adapter header', skillMainContent.includes
|
|
|
74
74
|
const engineBody = skillMainContent.slice(skillMainContent.indexOf('</codex_skill_adapter>'));
|
|
75
75
|
ok('codex: SKILL.md engine body has no /trailhead: substring', !engineBody.includes('/trailhead:'));
|
|
76
76
|
|
|
77
|
-
ok('codex:
|
|
77
|
+
ok('codex: _shared/techniques/grilling.md exists', fs.existsSync(path.join(codexDir, 'skills', '_shared', 'techniques', 'grilling.md')));
|
|
78
78
|
|
|
79
79
|
const agentsYamlPath = path.join(codexDir, 'skills', 'trailhead', 'agents', 'openai.yaml');
|
|
80
80
|
ok('codex: agents/openai.yaml exists', fs.existsSync(agentsYamlPath));
|
|
@@ -90,13 +90,29 @@ ok('codex: no settings.json file', !fs.existsSync(path.join(codexDir, 'settings.
|
|
|
90
90
|
// #46: per-verb discoverability now projects one Codex SKILL per verb
|
|
91
91
|
// (skills/trailhead-<verb>/), invocable as $trailhead-<verb>. The old
|
|
92
92
|
// ~/.codex/prompts/ shims never surfaced as slash commands on Codex.
|
|
93
|
-
|
|
93
|
+
// #46 per-verb discoverability skills. `work` collides with the work CLUSTER
|
|
94
|
+
// dir, so its discoverability is the cluster itself; use a non-colliding verb.
|
|
94
95
|
ok('codex: per-verb skills/trailhead-bug/SKILL.md exists', fs.existsSync(path.join(codexDir, 'skills', 'trailhead-bug', 'SKILL.md')));
|
|
95
|
-
ok('codex: trailhead-
|
|
96
|
-
ok('codex: trailhead-
|
|
97
|
-
ok('codex: per-verb skill is explicit-only', fs.readFileSync(path.join(codexDir, 'skills', 'trailhead-
|
|
96
|
+
ok('codex: trailhead-bug SKILL.md frontmatter is at byte 0', fs.readFileSync(path.join(codexDir, 'skills', 'trailhead-bug', 'SKILL.md'), 'utf8').startsWith('---\nname: trailhead-bug\n'));
|
|
97
|
+
ok('codex: trailhead-bug skill delegates to $trailhead bug', fs.readFileSync(path.join(codexDir, 'skills', 'trailhead-bug', 'SKILL.md'), 'utf8').includes('$trailhead bug'));
|
|
98
|
+
ok('codex: per-verb skill is explicit-only', fs.readFileSync(path.join(codexDir, 'skills', 'trailhead-bug', 'agents', 'openai.yaml'), 'utf8').includes('allow_implicit_invocation: false'));
|
|
99
|
+
// The trailhead-work dir is the WORK CLUSTER skill (not a thin verb delegator).
|
|
100
|
+
ok('codex: trailhead-work is the cluster skill (frontmatter name at byte 0)', fs.readFileSync(path.join(codexDir, 'skills', 'trailhead-work', 'SKILL.md'), 'utf8').startsWith('---\nname: trailhead-work\n'));
|
|
101
|
+
ok('codex: trailhead-work cluster carries the adapter header', fs.readFileSync(path.join(codexDir, 'skills', 'trailhead-work', 'SKILL.md'), 'utf8').includes('<codex_skill_adapter>'));
|
|
102
|
+
ok('codex: NO thin trailhead-work verb-delegator (cluster occupies the name)', !fs.readFileSync(path.join(codexDir, 'skills', 'trailhead-work', 'SKILL.md'), 'utf8').includes('This is a thin discoverability entry'));
|
|
98
103
|
ok('codex: no ~/.codex/prompts shims left (old mechanism dropped)', !fs.existsSync(path.join(codexDir, 'prompts', 'trailhead.md')) && !fs.existsSync(path.join(codexDir, 'prompts', 'trailhead-work.md')));
|
|
99
104
|
|
|
105
|
+
// The split ships all sibling engine skills so the ../_shared/ relative refs resolve.
|
|
106
|
+
ok('codex: _shared/ projected', fs.existsSync(path.join(codexDir, 'skills', '_shared', 'substrate.md')));
|
|
107
|
+
for (const cl of ['trailhead-chart', 'trailhead-work', 'trailhead-view', 'trailhead-capture', 'trailhead-manage']) {
|
|
108
|
+
ok(`codex: cluster ${cl} SKILL.md projected`, fs.existsSync(path.join(codexDir, 'skills', cl, 'SKILL.md')));
|
|
109
|
+
ok(`codex: cluster ${cl} carries the adapter header`, fs.readFileSync(path.join(codexDir, 'skills', cl, 'SKILL.md'), 'utf8').includes('<codex_skill_adapter>'));
|
|
110
|
+
ok(`codex: cluster ${cl} frontmatter precedes the adapter header`, (() => { const c = fs.readFileSync(path.join(codexDir, 'skills', cl, 'SKILL.md'), 'utf8'); return c.startsWith('---\n') && c.indexOf('name:') < c.indexOf('<codex_skill_adapter>'); })());
|
|
111
|
+
ok(`codex: cluster ${cl} agents/openai.yaml explicit-only`, fs.readFileSync(path.join(codexDir, 'skills', cl, 'agents', 'openai.yaml'), 'utf8').includes('allow_implicit_invocation: false'));
|
|
112
|
+
}
|
|
113
|
+
// A cluster's ../_shared/ reference target actually resolves as a sibling.
|
|
114
|
+
ok('codex: trailhead-work/../_shared/substrate.md resolves', fs.existsSync(path.join(codexDir, 'skills', 'trailhead-work', '..', '_shared', 'substrate.md')));
|
|
115
|
+
|
|
100
116
|
// --- codex hooks (#29) --------------------------------------------------------
|
|
101
117
|
const codexHooksJsonPath = path.join(codexDir, 'hooks.json');
|
|
102
118
|
ok('codex: hooks.json exists', fs.existsSync(codexHooksJsonPath));
|
|
@@ -115,18 +131,59 @@ ok('codex: skills/trailhead/hooks/lib/commit-message-check.js exists (commit-gua
|
|
|
115
131
|
const codexConfigTomlPath = path.join(codexDir, 'config.toml');
|
|
116
132
|
ok('codex: config.toml exists', fs.existsSync(codexConfigTomlPath));
|
|
117
133
|
ok('codex: config.toml enables hooks feature', fs.readFileSync(codexConfigTomlPath, 'utf8').includes('hooks = true'));
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
ok('codex: config.toml
|
|
122
|
-
|
|
134
|
+
// All 7 agents are always projected uniformly (pinned or pin-less), so
|
|
135
|
+
// multi_agent_v2 is ON even with no models.codex.* set (this install,
|
|
136
|
+
// repoRoot, sets none): the adapter §D registry is always present.
|
|
137
|
+
ok('codex: config.toml enables multi_agent_v2 even with no models.codex.* pins projected',
|
|
138
|
+
fs.readFileSync(codexConfigTomlPath, 'utf8').includes('multi_agent_v2 = true'));
|
|
139
|
+
|
|
140
|
+
const codexAgentsDirPath = path.join(codexDir, 'agents');
|
|
141
|
+
const noPinFixTomlPath = path.join(codexAgentsDirPath, 'trailhead-fix.toml');
|
|
142
|
+
const noPinMapTomlPath = path.join(codexAgentsDirPath, 'trailhead-codebase-map.toml');
|
|
143
|
+
ok('codex: trailhead-fix.toml is projected with no models.codex.* set', fs.existsSync(noPinFixTomlPath));
|
|
144
|
+
ok('codex: trailhead-codebase-map.toml is projected with no models.codex.* set', fs.existsSync(noPinMapTomlPath));
|
|
145
|
+
ok('codex: trailhead-fix.toml is pin-less (no model = line) with no models.codex.* set',
|
|
146
|
+
fs.existsSync(noPinFixTomlPath) && !/^model = /m.test(fs.readFileSync(noPinFixTomlPath, 'utf8')));
|
|
123
147
|
|
|
124
|
-
// --- codex
|
|
148
|
+
// --- codex --symlink: link verbatim artifacts (hooks + templates), keep skills projected ---
|
|
149
|
+
const codexSymDir = mktmp();
|
|
150
|
+
runInstaller([`--codex`, `--symlink`, `--dir=${codexSymDir}`]);
|
|
151
|
+
ok('codex symlink: skills/trailhead/hooks/trailhead-secret-guard.js is a symlink',
|
|
152
|
+
fs.lstatSync(path.join(codexSymDir, 'skills', 'trailhead', 'hooks', 'trailhead-secret-guard.js')).isSymbolicLink());
|
|
153
|
+
ok('codex symlink: skills/trailhead/hooks/lib/commit-message-check.js is a symlink',
|
|
154
|
+
fs.lstatSync(path.join(codexSymDir, 'skills', 'trailhead', 'hooks', 'lib', 'commit-message-check.js')).isSymbolicLink());
|
|
155
|
+
ok('codex symlink: skills/trailhead/templates is a symlink',
|
|
156
|
+
fs.lstatSync(path.join(codexSymDir, 'skills', 'trailhead', 'templates')).isSymbolicLink());
|
|
157
|
+
ok('codex symlink: a hook symlink resolves into the package source',
|
|
158
|
+
fs.realpathSync(path.join(codexSymDir, 'skills', 'trailhead', 'hooks', 'trailhead-secret-guard.js')) ===
|
|
159
|
+
fs.realpathSync(path.join(repoRoot, 'plugins', 'trailhead', 'hooks', 'trailhead-secret-guard.js')));
|
|
160
|
+
ok('codex symlink: converted SKILL.md stays a regular file (projected, not linked)',
|
|
161
|
+
!fs.lstatSync(path.join(codexSymDir, 'skills', 'trailhead', 'SKILL.md')).isSymbolicLink());
|
|
162
|
+
// Reinstall over the existing symlink install must not throw (EEXIST guard).
|
|
163
|
+
runInstaller([`--codex`, `--symlink`, `--dir=${codexSymDir}`]);
|
|
164
|
+
ok('codex symlink: reinstall over existing symlinks succeeds',
|
|
165
|
+
fs.lstatSync(path.join(codexSymDir, 'skills', 'trailhead', 'hooks', 'trailhead-secret-guard.js')).isSymbolicLink());
|
|
166
|
+
// Default (copy) codex install keeps hooks/templates as real files, not symlinks.
|
|
167
|
+
ok('codex copy: hooks are regular files (not symlinks)',
|
|
168
|
+
!fs.lstatSync(path.join(codexDir, 'skills', 'trailhead', 'hooks', 'trailhead-secret-guard.js')).isSymbolicLink());
|
|
169
|
+
ok('codex copy: templates is a regular dir (not a symlink)',
|
|
170
|
+
!fs.lstatSync(path.join(codexDir, 'skills', 'trailhead', 'templates')).isSymbolicLink());
|
|
171
|
+
|
|
172
|
+
// --- codex agent TOML projection (#38, #88) -------------------------------------
|
|
125
173
|
// repoRoot's own .trailhead/config.json has no models.codex.*, so a plain
|
|
126
|
-
// install (cwd: repoRoot, no models.codex anywhere) projects
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
174
|
+
// install (cwd: repoRoot, no models.codex anywhere) still projects all 7
|
|
175
|
+
// agents pin-less (uniform projection, #88), never zero.
|
|
176
|
+
ok('codex: all 7 trailhead-*.toml exist under agents/ even with models.codex.* unset', (() => {
|
|
177
|
+
const dir = path.join(codexDir, 'agents');
|
|
178
|
+
if (!fs.existsSync(dir)) return false;
|
|
179
|
+
const tomls = fs.readdirSync(dir).filter((f) => /^trailhead-.*\.toml$/.test(f));
|
|
180
|
+
return tomls.length === 7;
|
|
181
|
+
})());
|
|
182
|
+
ok('codex: every trailhead-*.toml is pin-less when models.codex.* is unset', (() => {
|
|
183
|
+
const dir = path.join(codexDir, 'agents');
|
|
184
|
+
const tomls = fs.readdirSync(dir).filter((f) => /^trailhead-.*\.toml$/.test(f));
|
|
185
|
+
return tomls.every((f) => !/^model = /m.test(fs.readFileSync(path.join(dir, f), 'utf8')));
|
|
186
|
+
})());
|
|
130
187
|
|
|
131
188
|
// --- codex migration (#46): install over old prompt shims sweeps them --------
|
|
132
189
|
const migCodexDir = mktmp();
|
|
@@ -138,12 +195,26 @@ ok('codex migration: stale prompts/trailhead.md swept on install', !fs.existsSyn
|
|
|
138
195
|
ok('codex migration: stale prompts/trailhead-work.md swept on install', !fs.existsSync(path.join(migCodexDir, 'prompts', 'trailhead-work.md')));
|
|
139
196
|
ok('codex migration: per-verb skill projected in its place', fs.existsSync(path.join(migCodexDir, 'skills', 'trailhead-work', 'SKILL.md')));
|
|
140
197
|
|
|
198
|
+
// codex migration: a stale trailhead-owned skill dir (pre-split monolith /
|
|
199
|
+
// removed cluster) is swept on reinstall; a co-tenant skill is preserved.
|
|
200
|
+
const migCodexSkillDir = mktmp();
|
|
201
|
+
runInstaller([`--codex`, `--dir=${migCodexSkillDir}`]);
|
|
202
|
+
fs.mkdirSync(path.join(migCodexSkillDir, 'skills', 'trailhead-monolith'), { recursive: true });
|
|
203
|
+
fs.writeFileSync(path.join(migCodexSkillDir, 'skills', 'trailhead-monolith', 'SKILL.md'), 'stale\n');
|
|
204
|
+
fs.mkdirSync(path.join(migCodexSkillDir, 'skills', 'other-plugin'), { recursive: true });
|
|
205
|
+
fs.writeFileSync(path.join(migCodexSkillDir, 'skills', 'other-plugin', 'SKILL.md'), 'co-tenant\n');
|
|
206
|
+
runInstaller([`--codex`, `--dir=${migCodexSkillDir}`]);
|
|
207
|
+
ok('codex migration: stale trailhead-monolith skill dir swept on reinstall', !fs.existsSync(path.join(migCodexSkillDir, 'skills', 'trailhead-monolith')));
|
|
208
|
+
ok('codex migration: co-tenant skill preserved', fs.existsSync(path.join(migCodexSkillDir, 'skills', 'other-plugin')));
|
|
209
|
+
|
|
141
210
|
// --- codex uninstall (same tmp) ----------------------------------------------
|
|
142
211
|
runInstaller([`--codex`, `--dir=${codexDir}`, '--uninstall']);
|
|
143
212
|
ok('codex uninstall: skills/trailhead gone', !fs.existsSync(path.join(codexDir, 'skills', 'trailhead')));
|
|
144
213
|
ok('codex uninstall: legacy trailhead engine dir gone', !fs.existsSync(path.join(codexDir, 'trailhead')));
|
|
145
214
|
ok('codex uninstall: per-verb skills/trailhead-work gone', !fs.existsSync(path.join(codexDir, 'skills', 'trailhead-work')));
|
|
146
215
|
ok('codex uninstall: per-verb skills/trailhead-bug gone', !fs.existsSync(path.join(codexDir, 'skills', 'trailhead-bug')));
|
|
216
|
+
ok('codex uninstall: _shared gone', !fs.existsSync(path.join(codexDir, 'skills', '_shared')));
|
|
217
|
+
ok('codex uninstall: cluster trailhead-view gone', !fs.existsSync(path.join(codexDir, 'skills', 'trailhead-view')));
|
|
147
218
|
ok('codex uninstall: hooks.json no longer contains trailhead commands', (() => {
|
|
148
219
|
const h = JSON.parse(fs.readFileSync(codexHooksJsonPath, 'utf8'));
|
|
149
220
|
const str = JSON.stringify(h);
|
|
@@ -157,6 +228,12 @@ runInstaller([`--claude`, `--dir=${claudeDir}`]);
|
|
|
157
228
|
|
|
158
229
|
const claudeSkillPath = path.join(claudeDir, 'skills', 'trailhead', 'SKILL.md');
|
|
159
230
|
ok('claude: skills/trailhead/SKILL.md exists', fs.existsSync(claudeSkillPath));
|
|
231
|
+
// The split ships all sibling engine skills on Claude too.
|
|
232
|
+
ok('claude: skills/_shared/substrate.md exists', fs.existsSync(path.join(claudeDir, 'skills', '_shared', 'substrate.md')));
|
|
233
|
+
for (const cl of ['trailhead-chart', 'trailhead-work', 'trailhead-view', 'trailhead-capture', 'trailhead-manage']) {
|
|
234
|
+
ok(`claude: skills/${cl}/SKILL.md exists`, fs.existsSync(path.join(claudeDir, 'skills', cl, 'SKILL.md')));
|
|
235
|
+
}
|
|
236
|
+
ok('claude: cluster ../_shared/ reference resolves', fs.existsSync(path.join(claudeDir, 'skills', 'trailhead-work', '..', '_shared', 'substrate.md')));
|
|
160
237
|
ok('claude: commands/trailhead/work.md exists', fs.existsSync(path.join(claudeDir, 'commands', 'trailhead', 'work.md')));
|
|
161
238
|
ok('claude: hooks/trailhead-commit-guard.js exists', fs.existsSync(path.join(claudeDir, 'hooks', 'trailhead-commit-guard.js')));
|
|
162
239
|
ok('claude: hooks/lib/commit-message-check.js exists (commit-guard require target)',
|
|
@@ -176,6 +253,90 @@ ok('claude: trailhead-commit-guard.js loads without MODULE_NOT_FOUND', (() => {
|
|
|
176
253
|
})());
|
|
177
254
|
ok('claude: SKILL.md does not start with the codex adapter header', !fs.readFileSync(claudeSkillPath, 'utf8').startsWith('<codex_skill_adapter>'));
|
|
178
255
|
|
|
256
|
+
// --- claude: engine agents registered as user subagents -----------------------
|
|
257
|
+
// Without these under <configDir>/agents/, every technique dispatch fails and
|
|
258
|
+
// falls back to running inline on the session model, so the per-technique
|
|
259
|
+
// config.models.* split silently never applies.
|
|
260
|
+
const claudeAgentsDir = path.join(claudeDir, 'agents');
|
|
261
|
+
const expectedAgents = ['trailhead-plan', 'trailhead-executor', 'trailhead-research', 'trailhead-code-review', 'trailhead-debug', 'trailhead-fix', 'trailhead-codebase-map'];
|
|
262
|
+
for (const a of expectedAgents) {
|
|
263
|
+
ok(`claude: agents/${a}.md placed`, fs.existsSync(path.join(claudeAgentsDir, `${a}.md`)));
|
|
264
|
+
}
|
|
265
|
+
ok('claude: agent file registers its subagent name in frontmatter',
|
|
266
|
+
fs.readFileSync(path.join(claudeAgentsDir, 'trailhead-executor.md'), 'utf8').startsWith('---\nname: trailhead-executor\n'));
|
|
267
|
+
ok('claude: agents are real files on a copy install (not symlinks)',
|
|
268
|
+
!fs.lstatSync(path.join(claudeAgentsDir, 'trailhead-plan.md')).isSymbolicLink());
|
|
269
|
+
|
|
270
|
+
// --- claude --symlink: dev install links hooks live too (not just skills/commands) ---
|
|
271
|
+
const symDir = mktmp();
|
|
272
|
+
runInstaller([`--claude`, `--symlink`, `--dir=${symDir}`]);
|
|
273
|
+
ok('claude symlink: skills/trailhead is a symlink',
|
|
274
|
+
fs.lstatSync(path.join(symDir, 'skills', 'trailhead')).isSymbolicLink());
|
|
275
|
+
ok('claude symlink: hooks/trailhead-secret-guard.js is a symlink',
|
|
276
|
+
fs.lstatSync(path.join(symDir, 'hooks', 'trailhead-secret-guard.js')).isSymbolicLink());
|
|
277
|
+
ok('claude symlink: hooks/lib/commit-message-check.js is a symlink',
|
|
278
|
+
fs.lstatSync(path.join(symDir, 'hooks', 'lib', 'commit-message-check.js')).isSymbolicLink());
|
|
279
|
+
ok('claude symlink: hook symlink resolves into the package source',
|
|
280
|
+
fs.realpathSync(path.join(symDir, 'hooks', 'trailhead-secret-guard.js')) ===
|
|
281
|
+
fs.realpathSync(path.join(repoRoot, 'plugins', 'trailhead', 'hooks', 'trailhead-secret-guard.js')));
|
|
282
|
+
ok('claude symlink: a symlinked commit-guard still loads its lib (no MODULE_NOT_FOUND)', (() => {
|
|
283
|
+
let out = '';
|
|
284
|
+
try {
|
|
285
|
+
out = String(execFileSync(process.execPath, [path.join(symDir, 'hooks', 'trailhead-commit-guard.js')],
|
|
286
|
+
{ input: '{"tool_name":"Bash","tool_input":{"command":"echo hi"}}', stdio: 'pipe' }));
|
|
287
|
+
} catch (e) { out = String(e.stdout || '') + String(e.stderr || ''); }
|
|
288
|
+
return !out.includes('MODULE_NOT_FOUND') && !out.includes('Cannot find module');
|
|
289
|
+
})());
|
|
290
|
+
// Reinstalling over an existing symlink install must not throw (EEXIST guard).
|
|
291
|
+
runInstaller([`--claude`, `--symlink`, `--dir=${symDir}`]);
|
|
292
|
+
ok('claude symlink: reinstall over existing symlinks succeeds',
|
|
293
|
+
fs.lstatSync(path.join(symDir, 'hooks', 'trailhead-secret-guard.js')).isSymbolicLink());
|
|
294
|
+
ok('claude symlink: agents/trailhead-plan.md is a symlink',
|
|
295
|
+
fs.lstatSync(path.join(symDir, 'agents', 'trailhead-plan.md')).isSymbolicLink());
|
|
296
|
+
ok('claude symlink: agent symlink resolves into the package source',
|
|
297
|
+
fs.realpathSync(path.join(symDir, 'agents', 'trailhead-plan.md')) ===
|
|
298
|
+
fs.realpathSync(path.join(repoRoot, 'plugins', 'trailhead', 'agents', 'trailhead-plan.md')));
|
|
299
|
+
// Default (copy) install keeps hooks as real files, not symlinks.
|
|
300
|
+
ok('claude copy: hooks/trailhead-secret-guard.js is a regular file (not a symlink)',
|
|
301
|
+
!fs.lstatSync(path.join(claudeDir, 'hooks', 'trailhead-secret-guard.js')).isSymbolicLink());
|
|
302
|
+
|
|
303
|
+
// --- claude migration: reinstall over an old layout sweeps stale skill dirs ---
|
|
304
|
+
// An old install may carry a pre-split monolith (skills/trailhead-monolith) or a
|
|
305
|
+
// removed cluster (skills/trailhead-legacy) the current install no longer ships.
|
|
306
|
+
// Reinstalling must sweep every stale trailhead-owned skill dir, while leaving a
|
|
307
|
+
// co-tenant plugin's own skill (skills/other-plugin) untouched.
|
|
308
|
+
const migClaudeDir = mktmp();
|
|
309
|
+
runInstaller([`--claude`, `--dir=${migClaudeDir}`]);
|
|
310
|
+
fs.mkdirSync(path.join(migClaudeDir, 'skills', 'trailhead-monolith'), { recursive: true });
|
|
311
|
+
fs.writeFileSync(path.join(migClaudeDir, 'skills', 'trailhead-monolith', 'SKILL.md'), 'stale monolith\n');
|
|
312
|
+
fs.mkdirSync(path.join(migClaudeDir, 'skills', 'trailhead-legacy'), { recursive: true });
|
|
313
|
+
fs.writeFileSync(path.join(migClaudeDir, 'skills', 'trailhead-legacy', 'SKILL.md'), 'stale cluster\n');
|
|
314
|
+
fs.mkdirSync(path.join(migClaudeDir, 'skills', 'other-plugin'), { recursive: true });
|
|
315
|
+
fs.writeFileSync(path.join(migClaudeDir, 'skills', 'other-plugin', 'SKILL.md'), 'co-tenant\n');
|
|
316
|
+
runInstaller([`--claude`, `--dir=${migClaudeDir}`]);
|
|
317
|
+
ok('claude migration: stale trailhead-monolith swept on reinstall', !fs.existsSync(path.join(migClaudeDir, 'skills', 'trailhead-monolith')));
|
|
318
|
+
ok('claude migration: stale trailhead-legacy cluster swept on reinstall', !fs.existsSync(path.join(migClaudeDir, 'skills', 'trailhead-legacy')));
|
|
319
|
+
ok('claude migration: co-tenant skill preserved (only trailhead names swept)', fs.existsSync(path.join(migClaudeDir, 'skills', 'other-plugin')));
|
|
320
|
+
ok('claude migration: current dispatcher present after reinstall', fs.existsSync(path.join(migClaudeDir, 'skills', 'trailhead', 'SKILL.md')));
|
|
321
|
+
ok('claude migration: current cluster present after reinstall', fs.existsSync(path.join(migClaudeDir, 'skills', 'trailhead-work', 'SKILL.md')));
|
|
322
|
+
// The agents dir gets the same name-only sweep on reinstall: a stale trailhead
|
|
323
|
+
// agent (renamed/removed) is swept, a co-tenant agent left alone.
|
|
324
|
+
fs.writeFileSync(path.join(migClaudeDir, 'agents', 'trailhead-oldagent.md'), '---\nname: trailhead-oldagent\n---\n');
|
|
325
|
+
fs.writeFileSync(path.join(migClaudeDir, 'agents', 'other-plugin-agent.md'), '---\nname: other-plugin-agent\n---\n');
|
|
326
|
+
runInstaller([`--claude`, `--dir=${migClaudeDir}`]);
|
|
327
|
+
ok('claude migration: stale trailhead-oldagent.md swept on reinstall', !fs.existsSync(path.join(migClaudeDir, 'agents', 'trailhead-oldagent.md')));
|
|
328
|
+
ok('claude migration: co-tenant agent preserved on reinstall', fs.existsSync(path.join(migClaudeDir, 'agents', 'other-plugin-agent.md')));
|
|
329
|
+
ok('claude migration: engine agents present after reinstall', fs.existsSync(path.join(migClaudeDir, 'agents', 'trailhead-executor.md')));
|
|
330
|
+
|
|
331
|
+
// Uninstall also sweeps a stale trailhead-owned dir the current package no
|
|
332
|
+
// longer ships (monolith), not just the current engineSkillDirs() set.
|
|
333
|
+
const uninstMigDir = mktmp();
|
|
334
|
+
runInstaller([`--claude`, `--dir=${uninstMigDir}`]);
|
|
335
|
+
fs.mkdirSync(path.join(uninstMigDir, 'skills', 'trailhead-monolith'), { recursive: true });
|
|
336
|
+
fs.writeFileSync(path.join(uninstMigDir, 'skills', 'trailhead-monolith', 'SKILL.md'), 'stale\n');
|
|
337
|
+
runInstaller([`--claude`, `--dir=${uninstMigDir}`, '--uninstall']);
|
|
338
|
+
ok('claude uninstall: stale trailhead-monolith swept too', !fs.existsSync(path.join(uninstMigDir, 'skills', 'trailhead-monolith')));
|
|
339
|
+
|
|
179
340
|
// --- claude uninstall: remove trailhead's lib, keep a co-tenant's -------------
|
|
180
341
|
// The Claude hooks/lib dir is shared with other plugins. Drop a fake co-tenant
|
|
181
342
|
// lib next to trailhead's, uninstall, and assert only trailhead's own lib file
|
|
@@ -188,6 +349,25 @@ runInstaller([`--claude`, `--dir=${coTenantDir}`, '--uninstall']);
|
|
|
188
349
|
ok('claude uninstall: trailhead lib removed', !fs.existsSync(path.join(coTenantDir, 'hooks', 'lib', 'commit-message-check.js')));
|
|
189
350
|
ok('claude uninstall: co-tenant lib preserved (no recursive wipe)', fs.existsSync(coTenantLib));
|
|
190
351
|
|
|
352
|
+
// The agents dir is shared too: a co-tenant's own agent survives uninstall,
|
|
353
|
+
// while trailhead's are removed by name.
|
|
354
|
+
const coTenantAgentDir = mktmp();
|
|
355
|
+
runInstaller([`--claude`, `--dir=${coTenantAgentDir}`]);
|
|
356
|
+
const foreignAgent = path.join(coTenantAgentDir, 'agents', 'other-plugin-agent.md');
|
|
357
|
+
fs.writeFileSync(foreignAgent, '---\nname: other-plugin-agent\n---\n');
|
|
358
|
+
ok('claude: engine agent present before uninstall', fs.existsSync(path.join(coTenantAgentDir, 'agents', 'trailhead-plan.md')));
|
|
359
|
+
runInstaller([`--claude`, `--dir=${coTenantAgentDir}`, '--uninstall']);
|
|
360
|
+
ok('claude uninstall: trailhead agents removed by name', !fs.existsSync(path.join(coTenantAgentDir, 'agents', 'trailhead-plan.md')));
|
|
361
|
+
ok('claude uninstall: co-tenant agent preserved (no recursive wipe)', fs.existsSync(foreignAgent));
|
|
362
|
+
|
|
363
|
+
// Uninstall removes every split skill (dispatcher + clusters + _shared) by name.
|
|
364
|
+
const splitDir = mktmp();
|
|
365
|
+
runInstaller([`--claude`, `--dir=${splitDir}`]);
|
|
366
|
+
runInstaller([`--claude`, `--dir=${splitDir}`, '--uninstall']);
|
|
367
|
+
for (const nm of ['trailhead', 'trailhead-chart', 'trailhead-work', 'trailhead-view', 'trailhead-capture', 'trailhead-manage', '_shared']) {
|
|
368
|
+
ok(`claude uninstall: skills/${nm} removed`, !fs.existsSync(path.join(splitDir, 'skills', nm)));
|
|
369
|
+
}
|
|
370
|
+
|
|
191
371
|
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
192
372
|
ok('claude: settings.json exists', fs.existsSync(settingsPath));
|
|
193
373
|
const settingsContent = fs.readFileSync(settingsPath, 'utf8');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marcomigozzi/trailhead",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Start and drive large projects as a map of tickets on GitHub Issues. Map-based onboarding + a discuss→plan→execute→verify engine, all on the Issues. Installs the trailhead skill, commands, and hooks into your agent's config dir.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"trailhead": "bin/trailhead.js"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trailhead",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Start and drive large projects as a map of tickets on GitHub Issues, resolving one at a time. Map-based onboarding + a discuss→plan→execute→verify engine with atomic commits, all on the Issues.
|
|
3
|
+
"version": "0.5.1",
|
|
4
|
+
"description": "Start and drive large projects as a map of tickets on GitHub Issues, resolving one at a time. Map-based onboarding + a discuss→plan→execute→verify engine with atomic commits, all on the Issues. Ships as a dispatcher plus cohesion-clustered skills over a shared core; depends on nothing beyond an authenticated gh CLI.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Marco Migozzi",
|
|
7
7
|
"url": "https://github.com/ToRvaLDz"
|
|
@@ -17,7 +17,12 @@
|
|
|
17
17
|
"capture"
|
|
18
18
|
],
|
|
19
19
|
"skills": [
|
|
20
|
-
"./skills/trailhead"
|
|
20
|
+
"./skills/trailhead",
|
|
21
|
+
"./skills/trailhead-chart",
|
|
22
|
+
"./skills/trailhead-work",
|
|
23
|
+
"./skills/trailhead-view",
|
|
24
|
+
"./skills/trailhead-capture",
|
|
25
|
+
"./skills/trailhead-manage"
|
|
21
26
|
],
|
|
22
27
|
"hooks": "./hooks/hooks.json"
|
|
23
28
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: trailhead-code-review
|
|
3
|
+
description: "trailhead code-review subagent: adversarially reviews a ticket's diff and reports; never edits or commits."
|
|
4
|
+
tools: Read, Grep, Glob, Bash, WebFetch, WebSearch, TodoWrite
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
trailhead's code-review subagent. Review the ticket's diff adversarially on the Code review technique's axes, verifying each finding before reporting. Do not defer to a convention or a settled-decision memory.
|
|
8
|
+
|
|
9
|
+
The full protocol is injected at spawn from the trailhead skill's single source (`_shared/techniques/code-review.md`); follow that, not any embedded copy. Read-only by policy: it reviews and reports into trailhead's `VERIFY` comment, it must never Edit/Write or commit. Model-agnostic by design: the engine passes the `config.models.review` model as the spawn-time override, so config stays authoritative.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: trailhead-codebase-map
|
|
3
|
+
description: "trailhead codebase-map reader subagent: maps one non-overlapping facet of the repo. Read-only."
|
|
4
|
+
tools: Read, Grep, Glob, Bash, WebFetch, WebSearch, TodoWrite
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
trailhead's codebase-map reader subagent. Map ONE non-overlapping facet of the repo for the distilled `trailhead:codebase` issue; do not read another reader's territory.
|
|
8
|
+
|
|
9
|
+
The full protocol is injected at spawn from the trailhead skill's single source (`_shared/techniques/codebase-map.md`); follow that, not any embedded copy. Read-only by policy: read-only by construction. Model-agnostic by design: the engine passes the session model at spawn (no dedicated model key), so config stays authoritative.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: trailhead-debug
|
|
3
|
+
description: "trailhead debug subagent: finds a defect's root cause by the scientific method."
|
|
4
|
+
tools: Read, Write, Edit, Grep, Glob, Bash, WebFetch, WebSearch, TodoWrite
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
trailhead's debug subagent. Find the root cause of the ticket's defect by the scientific method: reproduce, localise, falsifiable hypotheses, confirm cause, verify.
|
|
8
|
+
|
|
9
|
+
The full protocol is injected at spawn from the trailhead skill's single source (`_shared/techniques/debug.md`); follow that, not any embedded copy. Full tools by policy: it adds instrumentation and regression tests, so it needs Write. Model-agnostic by design: the engine passes the `config.models.debug` model as the spawn-time override, so config stays authoritative.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: trailhead-executor
|
|
3
|
+
description: "trailhead execute subagent: implements an approved PLAN with atomic conventional commits (each carrying a Refs: #<n> trailer)."
|
|
4
|
+
tools: Read, Write, Edit, Grep, Glob, Bash, WebFetch, WebSearch, TodoWrite
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
trailhead's execute subagent. Implement the approved PLAN for a build ticket with atomic conventional commits, each carrying a `Refs: #<n>` trailer for the ticket, following the repo's `trailhead:conventions`.
|
|
8
|
+
|
|
9
|
+
The full protocol is injected at spawn from the trailhead skill's single source (the Execute step in `trailhead-work/references/ticket-engines.md`); follow that, not any embedded copy. Full tools by policy: it can Edit/Write/commit. Model-agnostic by design: the engine passes the `config.models.execute` model as the spawn-time override, so config stays authoritative.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: trailhead-fix
|
|
3
|
+
description: "trailhead fix subagent: implements a bug fix with atomic commits (each carrying a Refs: #<n> trailer)."
|
|
4
|
+
tools: Read, Write, Edit, Grep, Glob, Bash, WebFetch, WebSearch, TodoWrite
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
trailhead's fix subagent. Implement the correction for a bug ticket with atomic conventional commits, each carrying a `Refs: #<n>` trailer for the ticket, following the repo's `trailhead:conventions`; where sensible, a test that fails before and passes after.
|
|
8
|
+
|
|
9
|
+
The full protocol is injected at spawn from the trailhead skill's single source (the Fix step in `trailhead-work/references/ticket-engines.md`); follow that, not any embedded copy. Full tools by policy: it can Edit/Write/commit. Model-agnostic by design: the engine passes the `config.models.execute` model as the spawn-time override (fix tracks the execute key), so config stays authoritative.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: trailhead-plan
|
|
3
|
+
description: "trailhead planning subagent: produces a build ticket's PLAN (steps, seams, files touched, verification criteria). Read-only, never implements."
|
|
4
|
+
tools: Read, Grep, Glob, Bash, WebFetch, WebSearch, TodoWrite
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
trailhead's planning subagent. Produce the PLAN for a build ticket: the steps, the seams, the files touched, and the verification criteria, applying TDD per the ticket's config.
|
|
8
|
+
|
|
9
|
+
The full protocol is injected at spawn from the trailhead skill's single source (the Plan step in `trailhead-work/references/ticket-engines.md`); follow that, not any embedded copy. Read-only by policy: plan, do not implement or commit. Model-agnostic by design: the engine passes the `config.models.plan` model as the spawn-time override, so config stays authoritative.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: trailhead-research
|
|
3
|
+
description: "trailhead research subagent: gathers a decision-ready fact from primary sources."
|
|
4
|
+
tools: Read, Write, Edit, Grep, Glob, Bash, WebFetch, WebSearch, TodoWrite
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
trailhead's research subagent. Gather the decision-ready fact a ticket waits on from primary sources, recording findings with citations to a throwaway `research/<name>` branch; change no product code beyond the research artifact.
|
|
8
|
+
|
|
9
|
+
The full protocol is injected at spawn from the trailhead skill's single source (`_shared/techniques/research.md`); follow that, not any embedded copy. Full tools by policy: it needs Write for the research artifact. Model-agnostic by design: the engine passes the `config.models.research` model as the spawn-time override, so config stays authoritative.
|
|
@@ -3,4 +3,4 @@ description: "Adopt an existing project: map the codebase once, then go lean"
|
|
|
3
3
|
argument-hint: ""
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Invoke the `trailhead` skill for the **adopt** action: call the Skill tool with skill name `trailhead` and arguments `adopt $ARGUMENTS`, then carry out the skill's instructions for that verb. Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
6
|
+
Invoke the `trailhead` skill for the **adopt** action: call the Skill tool with skill name `trailhead-chart` and arguments `adopt $ARGUMENTS`, then carry out the skill's instructions for that verb. Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
@@ -3,4 +3,4 @@ description: Capture a bug ticket (use --of <ticket> for a regression)
|
|
|
3
3
|
argument-hint: "[--of <ticket>] <text>"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Invoke the `trailhead` skill for the **bug** action: call the Skill tool with skill name `trailhead` and arguments `bug $ARGUMENTS`, then **read the skill's `references/capture.md` and follow it**: that file holds the full protocol for the capture verbs. Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
6
|
+
Invoke the `trailhead` skill for the **bug** action: call the Skill tool with skill name `trailhead-capture` and arguments `bug $ARGUMENTS`, then **read the skill's `references/capture.md` and follow it**: that file holds the full protocol for the capture verbs. Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
@@ -3,4 +3,4 @@ description: Guided menu setup, or get/set trailhead config
|
|
|
3
3
|
argument-hint: "[get|set …]"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Invoke the `trailhead` skill for the **config** action: call the Skill tool with skill name `trailhead` and arguments `config $ARGUMENTS`, then **read the skill's `references/configuration.md` and follow it**: that file holds the keys, their semantics, and the guided setup. Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
6
|
+
Invoke the `trailhead` skill for the **config** action: call the Skill tool with skill name `trailhead-manage` and arguments `config $ARGUMENTS`, then **read the skill's `references/configuration.md` and follow it**: that file holds the keys, their semantics, and the guided setup. Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
@@ -3,4 +3,4 @@ description: "Show the repo dashboard: the pinned index of every open map, the w
|
|
|
3
3
|
argument-hint: ""
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Invoke the `trailhead` skill for the **dashboard** action: call the Skill tool with skill name `trailhead` and arguments `dashboard $ARGUMENTS`, then carry out the skill's instructions for that verb (regenerate the pinned `trailhead:dashboard` index issue from the live tracker, creating and pinning it if missing, and show it). Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
6
|
+
Invoke the `trailhead` skill for the **dashboard** action: call the Skill tool with skill name `trailhead-view` and arguments `dashboard $ARGUMENTS`, then carry out the skill's instructions for that verb (regenerate the pinned `trailhead:dashboard` index issue from the live tracker, creating and pinning it if missing, and show it). Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
@@ -3,4 +3,4 @@ description: Run a standalone grilling session on a decision or topic
|
|
|
3
3
|
argument-hint: "[topic|ticket]"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Invoke the `trailhead` skill for the **grill** action: call the Skill tool with skill name `trailhead` and arguments `grill $ARGUMENTS`, then
|
|
6
|
+
Invoke the `trailhead` skill for the **grill** action: call the Skill tool with skill name `trailhead-chart` and arguments `grill $ARGUMENTS`, then carry out the skill's instructions for that verb (they run the Grilling and Domain vocabulary techniques). Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
@@ -3,4 +3,4 @@ description: Capture an idea into the fog (Not yet specified)
|
|
|
3
3
|
argument-hint: "<text>"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Invoke the `trailhead` skill for the **idea** action: call the Skill tool with skill name `trailhead` and arguments `idea $ARGUMENTS`, then **read the skill's `references/capture.md` and follow it**: that file holds the full protocol for the capture verbs. Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
6
|
+
Invoke the `trailhead` skill for the **idea** action: call the Skill tool with skill name `trailhead-capture` and arguments `idea $ARGUMENTS`, then **read the skill's `references/capture.md` and follow it**: that file holds the full protocol for the capture verbs. Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
@@ -3,4 +3,4 @@ description: Triage issues opened by others and integrate the worthwhile ones in
|
|
|
3
3
|
argument-hint: "[issue]"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Invoke the `trailhead` skill for the **inbox** action: call the Skill tool with skill name `trailhead` and arguments `inbox $ARGUMENTS`, then **read the skill's `references/inbox.md` and follow it**: that file holds the full protocol for this action. Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
6
|
+
Invoke the `trailhead` skill for the **inbox** action: call the Skill tool with skill name `trailhead-chart` and arguments `inbox $ARGUMENTS`, then **read the skill's `references/inbox.md` and follow it**: that file holds the full protocol for this action. Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
@@ -3,4 +3,4 @@ description: Show the low-res map (destination, decisions, frontier, fog)
|
|
|
3
3
|
argument-hint: ""
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Invoke the `trailhead` skill for the **map** action: call the Skill tool with skill name `trailhead` and arguments `map $ARGUMENTS`, then carry out the skill's instructions for that verb. Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
6
|
+
Invoke the `trailhead` skill for the **map** action: call the Skill tool with skill name `trailhead-view` and arguments `map $ARGUMENTS`, then carry out the skill's instructions for that verb. Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
@@ -3,4 +3,4 @@ description: Chart a new trailhead map from a loose idea
|
|
|
3
3
|
argument-hint: "[idea]"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Invoke the `trailhead` skill for the **new** action: call the Skill tool with skill name `trailhead` and arguments `new $ARGUMENTS`, then carry out the skill's instructions for that verb. Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
6
|
+
Invoke the `trailhead` skill for the **new** action: call the Skill tool with skill name `trailhead-chart` and arguments `new $ARGUMENTS`, then carry out the skill's instructions for that verb. Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
@@ -3,4 +3,4 @@ description: Capture a verbatim note
|
|
|
3
3
|
argument-hint: "<text>"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Invoke the `trailhead` skill for the **note** action: call the Skill tool with skill name `trailhead` and arguments `note $ARGUMENTS`, then **read the skill's `references/capture.md` and follow it**: that file holds the full protocol for the capture verbs. Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
6
|
+
Invoke the `trailhead` skill for the **note** action: call the Skill tool with skill name `trailhead-capture` and arguments `note $ARGUMENTS`, then **read the skill's `references/capture.md` and follow it**: that file holds the full protocol for the capture verbs. Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
@@ -3,4 +3,4 @@ description: Checkpoint the ticket in play so it can be resumed later
|
|
|
3
3
|
argument-hint: "[note]"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Invoke the `trailhead` skill for the **pause** action: call the Skill tool with skill name `trailhead` and arguments `pause $ARGUMENTS`, then **read the skill
|
|
6
|
+
Invoke the `trailhead` skill for the **pause** action: call the Skill tool with skill name `trailhead-work` and arguments `pause $ARGUMENTS`, then **read the teamwork reference the skill points to (`../_shared/teamwork.md`) and follow it**: that file holds the pausing & resuming protocol (including, under `isolation: worktree`/`clone`, committing the work-in-progress in the ticket's isolated workspace and recording its path in the checkpoint). Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
@@ -3,4 +3,6 @@ description: Work one ticket whole, off the map (whiteboard); grills only if nee
|
|
|
3
3
|
argument-hint: "[ticket | \"text\"]"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Invoke the `trailhead` skill for the **quick** action: call the Skill tool with skill name `trailhead` and arguments `quick $ARGUMENTS`, then carry out the skill's instructions for that verb (open a `trailhead:whiteboard` ticket from the `"text"`, or take the existing ticket you name, then run the full engine on it without splitting and without map book-keeping). Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
6
|
+
Invoke the `trailhead` skill for the **quick** action: call the Skill tool with skill name `trailhead-work` and arguments `quick $ARGUMENTS`, then carry out the skill's instructions for that verb (open a `trailhead:whiteboard` ticket from the `"text"`, or take the existing ticket you name, then run the full engine on it without splitting and without map book-keeping). Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
7
|
+
|
|
8
|
+
**When `quick "<text>"` opens a NEW whiteboard ticket, refresh the pinned `trailhead:dashboard` right after the ticket exists** (a structural event: the whiteboard has no native progress bar, so the dashboard is the only place the ticket shows). This is a required step of the create path, not optional; do not skip it. `quick <n>` on an existing *map* ticket does not refresh. See the skill's *quick* section and the dashboard freshness rule.
|
|
@@ -3,4 +3,4 @@ description: Resume a paused ticket from its checkpoint
|
|
|
3
3
|
argument-hint: "[ticket]"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Invoke the `trailhead` skill for the **resume** action: call the Skill tool with skill name `trailhead` and arguments `resume $ARGUMENTS`, then **read the skill
|
|
6
|
+
Invoke the `trailhead` skill for the **resume** action: call the Skill tool with skill name `trailhead-work` and arguments `resume $ARGUMENTS`, then **read the teamwork reference the skill points to (`../_shared/teamwork.md`) and follow it**: that file holds the pausing & resuming protocol (including, under `isolation: worktree`/`clone`, re-entering the ticket's isolated workspace recorded in its checkpoint). Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
@@ -3,4 +3,4 @@ description: "Capture a seed: work gated on a future trigger"
|
|
|
3
3
|
argument-hint: "<text>"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Invoke the `trailhead` skill for the **seed** action: call the Skill tool with skill name `trailhead` and arguments `seed $ARGUMENTS`, then **read the skill's `references/capture.md` and follow it**: that file holds the full protocol for the capture verbs. Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
6
|
+
Invoke the `trailhead` skill for the **seed** action: call the Skill tool with skill name `trailhead-capture` and arguments `seed $ARGUMENTS`, then **read the skill's `references/capture.md` and follow it**: that file holds the full protocol for the capture verbs. Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
@@ -3,4 +3,4 @@ description: Split an oversized ticket into children and supersede it
|
|
|
3
3
|
argument-hint: "[ticket]"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Invoke the `trailhead` skill for the **split** action: call the Skill tool with skill name `trailhead` and arguments `split $ARGUMENTS`, then **read the skill
|
|
6
|
+
Invoke the `trailhead` skill for the **split** action: call the Skill tool with skill name `trailhead-work` and arguments `split $ARGUMENTS`, then **read the teamwork reference the skill points to (`../_shared/teamwork.md`) and follow it**: that file holds the splitting protocol. Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
@@ -3,4 +3,4 @@ description: Open a ticket of any type on the fly (diverges briefly first)
|
|
|
3
3
|
argument-hint: "<type> <title>"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Invoke the `trailhead` skill for the **ticket** action: call the Skill tool with skill name `trailhead` and arguments `ticket $ARGUMENTS`, then carry out the skill's instructions for that verb. Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
6
|
+
Invoke the `trailhead` skill for the **ticket** action: call the Skill tool with skill name `trailhead-chart` and arguments `ticket $ARGUMENTS`, then carry out the skill's instructions for that verb. Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
@@ -3,4 +3,4 @@ description: "Capture a small build ticket: defined work you will do now"
|
|
|
3
3
|
argument-hint: "<text>"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Invoke the `trailhead` skill for the **todo** action: call the Skill tool with skill name `trailhead` and arguments `todo $ARGUMENTS`, then **read the skill's `references/capture.md` and follow it**: that file holds the full protocol for the capture verbs. Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
6
|
+
Invoke the `trailhead` skill for the **todo** action: call the Skill tool with skill name `trailhead-capture` and arguments `todo $ARGUMENTS`, then **read the skill's `references/capture.md` and follow it**: that file holds the full protocol for the capture verbs. Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
description: Check for a newer trailhead and install it
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
-
Invoke the `trailhead` skill for the **update** action: call the Skill tool with skill name `trailhead` and arguments `update $ARGUMENTS`, then **read the skill's `references/updating.md` and follow it**: that file holds the update protocol (detect the install channel, compare versions, install the newer one where it is safe). Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
5
|
+
Invoke the `trailhead` skill for the **update** action: call the Skill tool with skill name `trailhead-manage` and arguments `update $ARGUMENTS`, then **read the skill's `references/updating.md` and follow it**: that file holds the update protocol (detect the install channel, compare versions, install the newer one where it is safe). Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
@@ -3,4 +3,4 @@ description: "Show the whiteboard: loose (map-less) tickets, their frontier and
|
|
|
3
3
|
argument-hint: ""
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Invoke the `trailhead` skill for the **whiteboard** action: call the Skill tool with skill name `trailhead` and arguments `whiteboard $ARGUMENTS`, then carry out the skill's instructions for that verb (render the read-only whiteboard dashboard: the frontier, in-progress, and blocked `trailhead:whiteboard` tickets). Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
6
|
+
Invoke the `trailhead` skill for the **whiteboard** action: call the Skill tool with skill name `trailhead-view` and arguments `whiteboard $ARGUMENTS`, then carry out the skill's instructions for that verb (render the read-only whiteboard dashboard: the frontier, in-progress, and blocked `trailhead:whiteboard` tickets). Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
@@ -3,4 +3,4 @@ description: Work the next frontier ticket, or the one you name
|
|
|
3
3
|
argument-hint: "[ticket]"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Invoke the `trailhead` skill for the **work** action: call the Skill tool with skill name `trailhead` and arguments `work $ARGUMENTS`, then carry out the skill's instructions for that verb (under the repo's `trailhead:conventions` `isolation: worktree` or `clone`, work the ticket in its own isolated workspace on a `trailhead/t<n>` branch, set up at work-start, per
|
|
6
|
+
Invoke the `trailhead` skill for the **work** action: call the Skill tool with skill name `trailhead-work` and arguments `work $ARGUMENTS`, then carry out the skill's instructions for that verb (under the repo's `trailhead:conventions` `isolation: worktree` or `clone`, work the ticket in its own isolated workspace on a `trailhead/t<n>` branch, set up at work-start, per the skill's teamwork reference). Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
@@ -56,7 +56,7 @@ process.stdin.on('end', () => {
|
|
|
56
56
|
|
|
57
57
|
const msg = extractMessage(cmd);
|
|
58
58
|
if (msg) {
|
|
59
|
-
// stripComments:false
|
|
59
|
+
// stripComments:false, this is the literal `-m` value, where git keeps
|
|
60
60
|
// `#` lines as message text (cleanup=whitespace), so a `#…` first line is
|
|
61
61
|
// a real subject to validate, not a comment to skip.
|
|
62
62
|
const verdict = checkCommitMessage(msg, { stripComments: false });
|