@marcomigozzi/trailhead 0.16.1 → 0.17.0
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 +57 -10
- package/README.md +57 -10
- package/bin/capture-action.test.js +167 -0
- package/bin/capture-docs.test.js +32 -0
- package/bin/engine-route-docs.test.js +148 -0
- package/bin/grill-model-docs.test.js +155 -0
- package/bin/grilling-holistic-review-docs.test.js +7 -3
- package/bin/lib/codex-projection.js +22 -13
- package/bin/lib/codex-projection.test.js +58 -17
- package/bin/lib/host-descriptor.js +1 -1
- package/bin/lib/host-descriptor.test.js +6 -0
- package/bin/report-status-docs.test.js +10 -2
- package/bin/session-trust-docs.test.js +37 -0
- package/bin/surfaced-scope-docs.test.js +57 -0
- package/bin/trailhead.js +7 -5
- package/bin/trailhead.test.js +53 -11
- package/bin/unblock-docs.test.js +56 -0
- package/package.json +1 -1
- package/plugins/trailhead/.claude-plugin/plugin.json +1 -1
- package/plugins/trailhead/agents/trailhead-codebase-map.md +1 -1
- package/plugins/trailhead/agents/trailhead-grill.md +41 -0
- package/plugins/trailhead/hooks/lib/capture.test.js +439 -0
- package/plugins/trailhead/hooks/lib/checkout-find.test.js +195 -0
- package/plugins/trailhead/hooks/lib/checkout-in.test.js +83 -7
- package/plugins/trailhead/hooks/lib/dashboard.test.js +19 -0
- package/plugins/trailhead/hooks/lib/gh-auth-preflight.js +89 -0
- package/plugins/trailhead/hooks/lib/gh-auth-preflight.test.js +132 -0
- package/plugins/trailhead/hooks/lib/inherit-session.js +17 -0
- package/plugins/trailhead/hooks/lib/issue-body.test.js +84 -0
- package/plugins/trailhead/hooks/lib/model-defaults-review.js +22 -1
- package/plugins/trailhead/hooks/lib/model-defaults-review.test.js +64 -1
- package/plugins/trailhead/hooks/lib/model-route.js +207 -0
- package/plugins/trailhead/hooks/lib/model-route.test.js +231 -0
- package/plugins/trailhead/hooks/lib/report-status.test.js +56 -1
- package/plugins/trailhead/hooks/mod/README.md +16 -7
- package/plugins/trailhead/hooks/mod/map-pane.tsx +72 -16
- package/plugins/trailhead/hooks/mod/my-tickets.ts +76 -0
- package/plugins/trailhead/hooks/mod/repo-pin.ts +8 -5
- package/plugins/trailhead/hooks/trailhead-check-update.js +17 -1
- package/plugins/trailhead/hooks/trailhead-check-update.test.js +60 -0
- package/plugins/trailhead/skills/_shared/capture-core.md +118 -0
- package/plugins/trailhead/skills/_shared/choices.md +2 -2
- package/plugins/trailhead/skills/_shared/configuration-reference.md +18 -16
- package/plugins/trailhead/skills/_shared/configuration.md +1 -1
- package/plugins/trailhead/skills/_shared/session-handoff.md +1 -1
- package/plugins/trailhead/skills/_shared/substrate-commands.md +8 -4
- package/plugins/trailhead/skills/_shared/substrate.md +1 -1
- package/plugins/trailhead/skills/_shared/surfaced-scope.md +23 -0
- package/plugins/trailhead/skills/_shared/teamwork.md +6 -2
- package/plugins/trailhead/skills/_shared/techniques/acceptance-testing.md +1 -1
- package/plugins/trailhead/skills/_shared/techniques/code-review.md +5 -2
- package/plugins/trailhead/skills/_shared/techniques/codebase-map.md +4 -1
- package/plugins/trailhead/skills/_shared/techniques/debug.md +4 -1
- package/plugins/trailhead/skills/_shared/techniques/grilling.md +26 -1
- package/plugins/trailhead/skills/_shared/techniques/research.md +4 -1
- package/plugins/trailhead/skills/_shared/techniques/verify.md +3 -1
- package/plugins/trailhead/skills/_shared/techniques.md +6 -4
- package/plugins/trailhead/skills/trailhead-capture/SKILL.md +2 -4
- package/plugins/trailhead/skills/trailhead-capture/references/capture.md +29 -4
- package/plugins/trailhead/skills/trailhead-chart/SKILL.md +1 -1
- package/plugins/trailhead/skills/trailhead-chart/references/charting.md +4 -4
- package/plugins/trailhead/skills/trailhead-chart/references/inbox.md +3 -3
- package/plugins/trailhead/skills/trailhead-manage/references/auditing.md +1 -1
- package/plugins/trailhead/skills/trailhead-work/SKILL.md +11 -9
- package/plugins/trailhead/skills/trailhead-work/references/auto.md +1 -1
- package/plugins/trailhead/skills/trailhead-work/references/ticket-engines.md +10 -10
- package/plugins/trailhead/templates/trailhead-capture.js +418 -0
- package/plugins/trailhead/templates/trailhead-checkout-find.js +256 -0
- package/plugins/trailhead/templates/trailhead-checkout-in.js +76 -20
- package/plugins/trailhead/templates/trailhead-dashboard.js +15 -2
- package/plugins/trailhead/templates/trailhead-issue-body.js +69 -6
- package/plugins/trailhead/templates/trailhead-report-status.js +57 -9
- package/plugins/trailhead/tests/map-pane-ui.test.ts +122 -37
- package/plugins/trailhead/tests/my-tickets.test.ts +66 -0
- package/plugins/trailhead/tests/repo-pin.test.ts +20 -5
- package/plugins/trailhead/types/index.d.ts +7 -0
|
@@ -35,16 +35,18 @@ ok('the paragraph sits next to "Reviews never block the session"',
|
|
|
35
35
|
start !== -1 && neverBlock !== -1 && Math.abs(start - neverBlock) < 2500 && start > neverBlock);
|
|
36
36
|
const para = start === -1 ? '' : techniques.slice(start, techniques.indexOf('\n| Technique |', start));
|
|
37
37
|
const lc = para.toLowerCase();
|
|
38
|
+
ok('an inline grill round writes no report, so it is not checked',
|
|
39
|
+
para.includes('An inline `grill` round writes no report') && lc.includes('only a dispatched `trailhead-grill` report is checked'));
|
|
38
40
|
ok('the paragraph section was found and is bounded', para.length > 1000 && !para.includes('| **Grilling**'));
|
|
39
41
|
|
|
40
42
|
ok('it MUST run on every engine-agent report, the two reviews included', /MUST run on every/.test(para)
|
|
41
43
|
&& para.includes('Code review') && para.includes('Goal-backward verification'));
|
|
42
44
|
ok('it names every technique it covers',
|
|
43
|
-
['plan', 'executor', 'fix', 'debug', 'research', 'codebase-map'].every((t) => para.includes(t)) && lc.includes('every engine-agent report'));
|
|
45
|
+
['plan', 'executor', 'fix', 'debug', 'research', 'codebase-map', 'grill'].every((t) => para.includes(t)) && lc.includes('every engine-agent report'));
|
|
44
46
|
ok('the checker line carries fields and body', para.includes('{"action","verdict","problem","report","fields","body"}'));
|
|
45
47
|
ok('it covers subagent, inline and the lean inline pass', lc.includes('subagent') && lc.includes('inline') && lc.includes('lean inline pass'));
|
|
46
48
|
ok('the checker command with its technique flag and a report file',
|
|
47
|
-
para.includes('node <templates>/trailhead-report-status.js --technique <verify|code-review|plan|executor|fix|debug|research|codebase-map> --file <that dir>/report.md'));
|
|
49
|
+
para.includes('node <templates>/trailhead-report-status.js --technique <verify|code-review|plan|executor|fix|debug|research|codebase-map|grill> --file <that dir>/report.md'));
|
|
48
50
|
ok('the three templates dirs, in order',
|
|
49
51
|
(() => {
|
|
50
52
|
const a = para.indexOf('${CLAUDE_PLUGIN_ROOT}/templates/');
|
|
@@ -52,6 +54,12 @@ ok('the three templates dirs, in order',
|
|
|
52
54
|
const c = para.indexOf('~/.codex/skills/trailhead/templates/');
|
|
53
55
|
return a !== -1 && b > a && c > b;
|
|
54
56
|
})());
|
|
57
|
+
ok('a background agent is checked straight from its task output file, no temp dir',
|
|
58
|
+
para.includes('--task-output <that output file>') && lc.includes('once the agent has finished') && lc.includes('no temp dir, no copy and no cleanup'));
|
|
59
|
+
ok('the temp-dir route is for any other report', lc.includes('any other report (inline, foreground, or a host without that file)'));
|
|
60
|
+
ok('each step is its own Bash call, never bash -c, never chained',
|
|
61
|
+
lc.includes('its own bash call') && para.includes('never wrapped in `bash -c` / `sh -c`') && lc.includes('never chained')
|
|
62
|
+
&& lc.includes('cannot statically check'));
|
|
55
63
|
ok('the plugin root dir is skipped when unset', lc.includes('skipped when unset'));
|
|
56
64
|
ok('a private temp dir from mktemp -d, once per report', para.includes('`mktemp -d`') && lc.includes('once') && lc.includes('0700'));
|
|
57
65
|
ok('the raw report is written to report.md at the literal printed path with the Write tool',
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Docs test for #333: the in-session trust rule accepts the same labellers as the label guard
|
|
3
|
+
// (github-actions[bot], TRAILHEAD_LABEL_ALLOWLIST logins, write+ collaborators). Plain asserts.
|
|
4
|
+
// Run: node bin/session-trust-docs.test.js
|
|
5
|
+
const assert = require('assert');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
const root = path.resolve(__dirname, '..');
|
|
10
|
+
const skills = path.join(root, 'plugins', 'trailhead', 'skills');
|
|
11
|
+
const tw = fs.readFileSync(path.join(skills, '_shared', 'teamwork.md'), 'utf8');
|
|
12
|
+
const inbox = fs.readFileSync(path.join(skills, 'trailhead-chart', 'references', 'inbox.md'), 'utf8');
|
|
13
|
+
const s = tw.indexOf('## Trust & provenance');
|
|
14
|
+
const e = tw.indexOf('**Repo-side enforcement');
|
|
15
|
+
const trust = s < 0 || e < s ? '' : tw.slice(s, e);
|
|
16
|
+
let passed = 0;
|
|
17
|
+
const ok = (name, cond) => { assert.ok(cond, name); passed++; };
|
|
18
|
+
|
|
19
|
+
ok('teamwork.md has the trust section', trust !== '');
|
|
20
|
+
ok('bot is a trusted labeller', /`github-actions\[bot\]`[^]*trusted/i.test(trust));
|
|
21
|
+
ok('bot why: GITHUB_TOKEN events trigger no workflow', /GITHUB_TOKEN[^]*(trigger|run)s? no workflow[^]*guard/i.test(trust));
|
|
22
|
+
ok('allowlist read command', trust.includes('gh variable get TRAILHEAD_LABEL_ALLOWLIST'));
|
|
23
|
+
ok('allowlist: not found means empty', /not found[^]*(means|->|is) empty/i.test(trust));
|
|
24
|
+
ok('allowlist: other failure means unknown, not empty', /any other failure[^]*unknown/i.test(trust) && /unknown, not empty/i.test(trust));
|
|
25
|
+
ok('allowlist compare: comma-separated, trimmed, case-insensitive', /comma-separated[^]*trimmed[^]*case-insensitive/i.test(trust));
|
|
26
|
+
ok('unknown allowlist: held off frontier, provenance not checkable', /unknown[^]*held? off the frontier[^]*provenance not checkable/i.test(trust));
|
|
27
|
+
ok('never labelled unverified on a guess', /never[^.]*`trailhead:unverified`[^.]*guess/i.test(trust));
|
|
28
|
+
ok('failed permission lookup is unresolved too', /permission lookup (failed|fails)/i.test(trust) && /`404`[^]*real answer/i.test(trust));
|
|
29
|
+
ok('per-label provenance: latest labeled event of every label', /every `trailhead:\*` label[^]*latest `labeled` event[^]*all[^.]*trusted/i.test(trust));
|
|
30
|
+
ok('one trusted label does not vouch for another', /does not vouch for another/i.test(trust));
|
|
31
|
+
ok('still quarantines other non-write labellers', /`read`\/`none`[^]*not trusted/i.test(trust) && trust.includes('`trailhead:unverified`'));
|
|
32
|
+
ok('session and guard agree', /never strips/i.test(trust));
|
|
33
|
+
ok('inbox: session applies unverified, guard strips', /`trailhead:unverified`[^.]*applied by a session[^]*guard Action strips/i.test(inbox));
|
|
34
|
+
ok('inbox: rogue means outside the trusted labellers', /trusted labellers[^]*Trust rule|Trust rule[^]*trusted labellers/i.test(inbox) && /teamwork\.md#trust--provenance/.test(inbox));
|
|
35
|
+
ok('inbox: jq includes quarantined issues', /\| any\(startswith\("trailhead:"\)\) \| not\) or \(\[\.labels\[\]\.name\] \| any\(\. == "trailhead:unverified"\)\)/.test(inbox));
|
|
36
|
+
ok('no em-dash', !tw.includes(String.fromCharCode(0x2014)) && !inbox.includes(String.fromCharCode(0x2014)));
|
|
37
|
+
console.log(`session-trust-docs.test.js: ${passed} checks passed`);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Docs test: un ticket aperto a metà lavoro va sulla mappa corrente (o su quella giusta)
|
|
3
|
+
// se il tema è suo; altrimenti si chiede fra mappe aperte e whiteboard, mai whiteboard
|
|
4
|
+
// in silenzio; dopo la creazione si dice dove è stato aperto. I rimandi puntano al file.
|
|
5
|
+
// Plain asserts, no framework. Run: node bin/surfaced-scope-docs.test.js
|
|
6
|
+
const assert = require('assert');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const root = path.resolve(__dirname, '..');
|
|
11
|
+
const read = (rel) => fs.readFileSync(path.join(root, rel), 'utf8');
|
|
12
|
+
let passed = 0;
|
|
13
|
+
const ok = (name, cond) => { assert.ok(cond, name); passed++; };
|
|
14
|
+
const EM = String.fromCharCode(0x2014);
|
|
15
|
+
|
|
16
|
+
const SK = 'plugins/trailhead/skills';
|
|
17
|
+
const REF = 'Scope that surfaces while working a ticket';
|
|
18
|
+
|
|
19
|
+
// La regola condivisa
|
|
20
|
+
const rule = read(`${SK}/_shared/surfaced-scope.md`);
|
|
21
|
+
ok('rule: titled after the referenced name', rule.startsWith(`# ${REF}`));
|
|
22
|
+
ok('rule: do it, split it, or capture it', /\*\*`split`\*\* X/.test(rule) && /capture and continue/.test(rule));
|
|
23
|
+
ok('rule: Surfaced from lineage line', /`Surfaced from: /.test(rule));
|
|
24
|
+
ok('rule: current map first when the topic is its own', /1\. \*\*The current map[^\n]*no ask/.test(rule));
|
|
25
|
+
ok('rule: another open map when the topic clearly is its', /2\. \*\*Another open map[^\n]*no ask/.test(rule));
|
|
26
|
+
ok('rule: otherwise ask, listing every open map and the whiteboard', /3\. \*\*Otherwise, ask\.\*\*[^\n]*AskUserQuestion[^\n]*every open map[^\n]*whiteboard/.test(rule));
|
|
27
|
+
ok('rule: never the whiteboard silently while a map is open', /Never route to the whiteboard without this ask while any map is open/.test(rule));
|
|
28
|
+
ok('rule: no map open falls to the whiteboard', /4\. \*\*No map open → whiteboard, no ask\*\*/.test(rule));
|
|
29
|
+
ok('rule: auto defers an ask case instead of guessing', /autonomous run[^\n]*rule 3[^\n]*Pending follow-up guard/.test(rule));
|
|
30
|
+
ok('rule: announce the ticket and where it landed', /## Say what you opened, and where/.test(rule) && /\*\*where it landed\*\*/.test(rule));
|
|
31
|
+
ok('rule: no em-dash', !rule.includes(EM));
|
|
32
|
+
|
|
33
|
+
// Ogni rimando al nome porta al file (nessun rimando orfano)
|
|
34
|
+
const referrers = [
|
|
35
|
+
`${SK}/_shared/session-handoff.md`,
|
|
36
|
+
`${SK}/_shared/techniques/code-review.md`,
|
|
37
|
+
`${SK}/trailhead-work/SKILL.md`,
|
|
38
|
+
`${SK}/trailhead-work/references/ticket-engines.md`,
|
|
39
|
+
];
|
|
40
|
+
for (const rel of referrers) {
|
|
41
|
+
const text = read(rel);
|
|
42
|
+
const lines = text.split('\n').filter((l) => l.includes(REF));
|
|
43
|
+
ok(`${rel}: mentions the rule`, lines.length > 0);
|
|
44
|
+
ok(`${rel}: every mention links surfaced-scope.md`, lines.every((l) => /surfaced-scope\.md/.test(l)));
|
|
45
|
+
ok(`${rel}: no "served by the capture cluster" dangling pointer`, lines.every((l) => !/served by the capture cluster/.test(l)));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Capture e UAT seguono la stessa regola
|
|
49
|
+
const capture = read(`${SK}/trailhead-capture/references/capture.md`);
|
|
50
|
+
// #330: capture.md no longer links the full core; it states the routing inline and
|
|
51
|
+
// capture-core.md carries drift markers on the surfaced-scope.md rules it restates.
|
|
52
|
+
ok('capture: a mid-ticket capture states the surfaced-scope routing inline', /\*\*A capture fired mid-ticket\*\*[^\n]*the current map[^\n]*Surfaced from:/.test(capture));
|
|
53
|
+
ok('capture-core: surfaced-scope.md rules are drift-stamped', /<!-- core: surfaced-scope\.md \|/.test(read(`${SK}/_shared/capture-core.md`)));
|
|
54
|
+
const uat = read(`${SK}/_shared/techniques/acceptance-testing.md`);
|
|
55
|
+
ok('uat: the failing-step follow-up lands per surfaced-scope.md', /On a failing step[^\n]*surfaced-scope\.md/.test(uat));
|
|
56
|
+
|
|
57
|
+
console.log(`surfaced-scope-docs.test.js: ${passed} checks passed`);
|
package/bin/trailhead.js
CHANGED
|
@@ -160,7 +160,9 @@ function readCodexModels(codexHome) {
|
|
|
160
160
|
const globalCfg = readJSON(path.join(codexHome, 'trailhead', 'config.json')) || {};
|
|
161
161
|
const globalCodex = globalCfg.models && typeof globalCfg.models === 'object' && typeof globalCfg.models.codex === 'object' && globalCfg.models.codex ? globalCfg.models.codex : {};
|
|
162
162
|
const projectCodex = projectCfg.models && typeof projectCfg.models === 'object' && typeof projectCfg.models.codex === 'object' && projectCfg.models.codex ? projectCfg.models.codex : {};
|
|
163
|
-
|
|
163
|
+
// #323: una layer con plan ma senza grill implica grill = plan, prima del merge.
|
|
164
|
+
const { withImplicitGrill } = require(path.join(SRC, 'hooks', 'lib', 'model-defaults-review.js'));
|
|
165
|
+
return { ...withImplicitGrill(globalCodex), ...withImplicitGrill(projectCodex) };
|
|
164
166
|
} catch {
|
|
165
167
|
return {};
|
|
166
168
|
}
|
|
@@ -263,7 +265,7 @@ const ENTRYPOINT_FILES = HOOK_FILES.filter((f) => !BASH_GUARD_MODULES.includes(f
|
|
|
263
265
|
// require('./lib/mockup-link.js')). Copied by name, never a recursive
|
|
264
266
|
// sweep: the Claude hooks/lib dir is shared with other plugins, so we must
|
|
265
267
|
// not clobber a co-tenant's lib nor ship our own *.test.js files.
|
|
266
|
-
const HOOK_LIB_FILES = ['commit-message-check.js', 'shell-scan.js', 'gh-subcommand.js', 'body-sources.js', 'unmodelled.js', 'model-defaults-review.js', 'model-defaults.json', 'mockup-link.js'];
|
|
268
|
+
const HOOK_LIB_FILES = ['commit-message-check.js', 'shell-scan.js', 'gh-subcommand.js', 'body-sources.js', 'unmodelled.js', 'model-defaults-review.js', 'model-defaults.json', 'inherit-session.js', 'model-route.js', 'mockup-link.js', 'gh-auth-preflight.js'];
|
|
267
269
|
// search-guard is Claude-Code-specific (#169), and so is mockup-link-guard
|
|
268
270
|
// (it gates the AskUserQuestion tool, which Codex lacks): excluded from both
|
|
269
271
|
// the Codex hook copy and the Codex verify spec's expected hookFiles, so a
|
|
@@ -686,12 +688,12 @@ function installCodex(configDir, { useSymlink }) {
|
|
|
686
688
|
featureManual = true;
|
|
687
689
|
}
|
|
688
690
|
|
|
689
|
-
// Per-technique agent registry: project ALL
|
|
691
|
+
// Per-technique agent registry: project ALL 9 committed agents
|
|
690
692
|
// (readAgentDefs, sourced from plugins/trailhead/agents/*.md) as
|
|
691
693
|
// trailhead-<technique>.toml under the real ~/.codex/agents/ dir, uniformly.
|
|
692
|
-
// A keyed technique (plan/execute/research/review/debug/verify/codebase-map)
|
|
694
|
+
// A keyed technique (plan/execute/research/review/debug/verify/codebase-map/grill)
|
|
693
695
|
// carries its models.codex.<key> pin (merged project + global config) when
|
|
694
|
-
// set, else is pin-less;
|
|
696
|
+
// set, else is pin-less; fix tracks the models.codex.execute pin.
|
|
695
697
|
// Sweep only trailhead's own files first, so a stale pin from a prior
|
|
696
698
|
// install (or a removed technique) never lingers, without touching the
|
|
697
699
|
// user's other custom-agent TOMLs.
|
package/bin/trailhead.test.js
CHANGED
|
@@ -60,6 +60,11 @@ const MODEL_DEFAULTS_LIB_CANDIDATES = [
|
|
|
60
60
|
'../trailhead/hooks/lib/model-defaults-review.js',
|
|
61
61
|
];
|
|
62
62
|
function assertModelDefaultsLibLayout(label, sharedDir) {
|
|
63
|
+
// #323: model-route.js is resolved with the same two-path lookup (grilling.md).
|
|
64
|
+
const routeResolved = MODEL_DEFAULTS_LIB_CANDIDATES
|
|
65
|
+
.map((c) => path.join(sharedDir, c.replace('model-defaults-review.js', 'model-route.js')))
|
|
66
|
+
.find((p) => fs.existsSync(p));
|
|
67
|
+
ok(`${label}: documented model-route.js lib path resolves`, !!routeResolved);
|
|
63
68
|
const resolved = MODEL_DEFAULTS_LIB_CANDIDATES
|
|
64
69
|
.map((c) => path.join(sharedDir, c))
|
|
65
70
|
.find((p) => fs.existsSync(p));
|
|
@@ -234,7 +239,9 @@ ok('codex: trailhead-work/../_shared/substrate.md resolves', fs.existsSync(path.
|
|
|
234
239
|
ok('codex: _shared/load-first.md projected', fs.existsSync(path.join(codexDir, 'skills', '_shared', 'load-first.md')));
|
|
235
240
|
for (const cl of ['trailhead', 'trailhead-chart', 'trailhead-work', 'trailhead-view', 'trailhead-capture', 'trailhead-manage']) {
|
|
236
241
|
ok(`codex: ${cl}/../_shared/load-first.md resolves as a sibling`, fs.existsSync(path.join(codexDir, 'skills', cl, '..', '_shared', 'load-first.md')));
|
|
237
|
-
|
|
242
|
+
// #330: the capture cluster loads the compact capture-core.md instead.
|
|
243
|
+
const want = cl === 'trailhead-capture' ? '../_shared/capture-core.md' : '../_shared/load-first.md';
|
|
244
|
+
ok(`codex: ${cl} SKILL.md references ${want}`, fs.readFileSync(path.join(codexDir, 'skills', cl, 'SKILL.md'), 'utf8').includes(want));
|
|
238
245
|
}
|
|
239
246
|
// Source-level anti-drift: the shared core-list signature line lives ONLY in
|
|
240
247
|
// _shared/load-first.md, never duplicated back into a cluster SKILL.md.
|
|
@@ -293,7 +300,8 @@ ok('source: load-first.md references ../_shared/ticket-language.md', loadFirstSo
|
|
|
293
300
|
const ticketLangAutoSource = fs.readFileSync(path.join(sourceSkillsDir, 'trailhead-work', 'references', 'auto.md'), 'utf8');
|
|
294
301
|
const ticketLangCaptureSource = fs.readFileSync(path.join(sourceSkillsDir, 'trailhead-capture', 'references', 'capture.md'), 'utf8');
|
|
295
302
|
ok('source: auto.md references ticket-language.md', ticketLangAutoSource.includes('ticket-language.md'));
|
|
296
|
-
|
|
303
|
+
// #330: capture.md points at the compact capture-core.md, which is drift-stamped against ticket-language.md.
|
|
304
|
+
ok('source: capture.md references capture-core.md for the ticket language', ticketLangCaptureSource.includes('ticket language rule in `capture-core.md`') && /<!-- core: ticket-language\.md \|/.test(fs.readFileSync(path.join(sourceSkillsDir, '_shared', 'capture-core.md'), 'utf8')));
|
|
297
305
|
// Source-level anti-drift: the substantive decoupling clause lives in EXACTLY
|
|
298
306
|
// one file across the whole skills tree, _shared/ticket-language.md. A global
|
|
299
307
|
// single-occurrence scan (not an enumerated per-file list) so any future doc
|
|
@@ -941,6 +949,10 @@ ok('codex: skills/trailhead/hooks/lib/body-sources.js exists (secret-guard requi
|
|
|
941
949
|
fs.existsSync(path.join(codexDir, 'skills', 'trailhead', 'hooks', 'lib', 'body-sources.js')));
|
|
942
950
|
ok('codex: skills/trailhead/hooks/lib/unmodelled.js exists (guards require target, #227)',
|
|
943
951
|
fs.existsSync(path.join(codexDir, 'skills', 'trailhead', 'hooks', 'lib', 'unmodelled.js')));
|
|
952
|
+
ok('codex: skills/trailhead/hooks/lib/inherit-session.js exists (required by model-route and model-defaults-review, #323)',
|
|
953
|
+
fs.existsSync(path.join(codexDir, 'skills', 'trailhead', 'hooks', 'lib', 'inherit-session.js')));
|
|
954
|
+
ok('codex: skills/trailhead/hooks/lib/model-route.js exists (grilling routing helper, #323)',
|
|
955
|
+
fs.existsSync(path.join(codexDir, 'skills', 'trailhead', 'hooks', 'lib', 'model-route.js')));
|
|
944
956
|
ok('codex: skills/trailhead/hooks/lib/model-defaults-review.js exists (check-update require target, #186)',
|
|
945
957
|
fs.existsSync(path.join(codexDir, 'skills', 'trailhead', 'hooks', 'lib', 'model-defaults-review.js')));
|
|
946
958
|
ok('codex: skills/trailhead/hooks/lib/model-defaults.json exists (#186)',
|
|
@@ -960,6 +972,8 @@ ok('codex: skills/trailhead/hooks/trailhead-mockup-link-stop.js exists',
|
|
|
960
972
|
fs.existsSync(path.join(codexDir, 'skills', 'trailhead', 'hooks', 'trailhead-mockup-link-stop.js')));
|
|
961
973
|
ok('codex: skills/trailhead/hooks/lib/mockup-link.js exists (mockup-link-stop require target)',
|
|
962
974
|
fs.existsSync(path.join(codexDir, 'skills', 'trailhead', 'hooks', 'lib', 'mockup-link.js')));
|
|
975
|
+
ok('codex: skills/trailhead/hooks/lib/gh-auth-preflight.js exists (check-update require target)',
|
|
976
|
+
fs.existsSync(path.join(codexDir, 'skills', 'trailhead', 'hooks', 'lib', 'gh-auth-preflight.js')));
|
|
963
977
|
ok('codex: skills/trailhead/hooks/trailhead-mockup-link-guard.js does NOT exist',
|
|
964
978
|
!fs.existsSync(path.join(codexDir, 'skills', 'trailhead', 'hooks', 'trailhead-mockup-link-guard.js')));
|
|
965
979
|
ok('codex: skills/trailhead/hooks/lib/shell-scan.js still exists (secret-read-guard require target)',
|
|
@@ -979,8 +993,15 @@ const noPinFixTomlPath = path.join(codexAgentsDirPath, 'trailhead-fix.toml');
|
|
|
979
993
|
const noPinMapTomlPath = path.join(codexAgentsDirPath, 'trailhead-codebase-map.toml');
|
|
980
994
|
ok('codex: trailhead-fix.toml is projected with no models.codex.* set', fs.existsSync(noPinFixTomlPath));
|
|
981
995
|
ok('codex: trailhead-codebase-map.toml is projected with no models.codex.* set', fs.existsSync(noPinMapTomlPath));
|
|
982
|
-
|
|
983
|
-
|
|
996
|
+
// #325: fix non ha chiave propria, porta lo stesso pin di execute (la config del
|
|
997
|
+
// repo puo' impostarlo, quindi si confrontano le due righe model, qualunque sia).
|
|
998
|
+
const modelLine325 = (f) => (fs.readFileSync(f, 'utf8').match(/^model = .*$/m) || [''])[0];
|
|
999
|
+
const effortLine325 = (f) => (fs.readFileSync(f, 'utf8').match(/^model_reasoning_effort = .*$/m) || [''])[0];
|
|
1000
|
+
const executeTomlPath325 = path.join(codexAgentsDirPath, 'trailhead-execute.toml');
|
|
1001
|
+
ok('codex: trailhead-fix.toml carries the same model pin as trailhead-execute.toml (fix tracks execute, #325)',
|
|
1002
|
+
fs.existsSync(noPinFixTomlPath) && fs.existsSync(executeTomlPath325) &&
|
|
1003
|
+
modelLine325(noPinFixTomlPath) === modelLine325(executeTomlPath325) &&
|
|
1004
|
+
effortLine325(noPinFixTomlPath) === effortLine325(executeTomlPath325));
|
|
984
1005
|
|
|
985
1006
|
assertModelDefaultsLibLayout('codex copy', path.join(codexDir, 'skills', '_shared'));
|
|
986
1007
|
|
|
@@ -1011,7 +1032,7 @@ ok('codex copy: templates is a regular dir (not a symlink)',
|
|
|
1011
1032
|
assertModelDefaultsLibLayout('codex symlink', path.join(codexSymDir, 'skills', '_shared'));
|
|
1012
1033
|
|
|
1013
1034
|
// --- codex agent TOML projection (#38, #88) -------------------------------------
|
|
1014
|
-
// With NO models.codex.* set, a codex install still projects all
|
|
1035
|
+
// With NO models.codex.* set, a codex install still projects all 9 agents
|
|
1015
1036
|
// uniformly, pin-less (uniform projection, #88), never zero. Install from an
|
|
1016
1037
|
// isolated cwd whose .trailhead/config.json sets no models.codex.*, not
|
|
1017
1038
|
// repoRoot (whose own config now pins models.codex.*, so it would project
|
|
@@ -1024,11 +1045,11 @@ fs.writeFileSync(
|
|
|
1024
1045
|
);
|
|
1025
1046
|
const unsetCodexHomeDir = mktmp();
|
|
1026
1047
|
runInstaller([`--codex`, `--dir=${unsetCodexHomeDir}`], { cwd: unsetProjectDir });
|
|
1027
|
-
ok('codex: all
|
|
1048
|
+
ok('codex: all 9 trailhead-*.toml exist under agents/ even with models.codex.* unset', (() => {
|
|
1028
1049
|
const dir = path.join(unsetCodexHomeDir, 'agents');
|
|
1029
1050
|
if (!fs.existsSync(dir)) return false;
|
|
1030
1051
|
const tomls = fs.readdirSync(dir).filter((f) => /^trailhead-.*\.toml$/.test(f));
|
|
1031
|
-
return tomls.length ===
|
|
1052
|
+
return tomls.length === 9;
|
|
1032
1053
|
})());
|
|
1033
1054
|
ok('codex: every trailhead-*.toml is pin-less when models.codex.* is unset', (() => {
|
|
1034
1055
|
const dir = path.join(unsetCodexHomeDir, 'agents');
|
|
@@ -1037,7 +1058,7 @@ ok('codex: every trailhead-*.toml is pin-less when models.codex.* is unset', (()
|
|
|
1037
1058
|
})());
|
|
1038
1059
|
|
|
1039
1060
|
// #250: the reviewing block (single source scripts/agent-blocks.md) is projected
|
|
1040
|
-
// verbatim into the two reviewing agents' TOMLs and into none of the other
|
|
1061
|
+
// verbatim into the two reviewing agents' TOMLs and into none of the other seven.
|
|
1041
1062
|
{
|
|
1042
1063
|
const sync250 = require(path.join(repoRoot, 'scripts', 'sync-agent-blocks.js'));
|
|
1043
1064
|
const reviewingBlock250 = sync250.parseSource(fs.readFileSync(path.join(repoRoot, 'scripts', 'agent-blocks.md'), 'utf8')).reviewing;
|
|
@@ -1045,7 +1066,7 @@ ok('codex: every trailhead-*.toml is pin-less when models.codex.* is unset', (()
|
|
|
1045
1066
|
const agentsDir250 = path.join(unsetCodexHomeDir, 'agents');
|
|
1046
1067
|
const reviewingNames250 = new Set(['trailhead-verify', 'trailhead-review']); // the code-review agent projects as trailhead-review.toml
|
|
1047
1068
|
const tomls250 = fs.readdirSync(agentsDir250).filter((f) => /^trailhead-.*\.toml$/.test(f));
|
|
1048
|
-
ok('#250: all
|
|
1069
|
+
ok('#250: all 9 TOMLs are checked', tomls250.length === 9);
|
|
1049
1070
|
for (const f of tomls250) {
|
|
1050
1071
|
const name = f.replace(/\.toml$/, '');
|
|
1051
1072
|
const text = fs.readFileSync(path.join(agentsDir250, f), 'utf8');
|
|
@@ -1108,7 +1129,8 @@ ok('claude: cluster ../_shared/ reference resolves', fs.existsSync(path.join(cla
|
|
|
1108
1129
|
ok('claude: skills/_shared/load-first.md exists', fs.existsSync(path.join(claudeDir, 'skills', '_shared', 'load-first.md')));
|
|
1109
1130
|
ok('claude: trailhead-work/../_shared/load-first.md resolves', fs.existsSync(path.join(claudeDir, 'skills', 'trailhead-work', '..', '_shared', 'load-first.md')));
|
|
1110
1131
|
for (const cl of ['trailhead', 'trailhead-chart', 'trailhead-work', 'trailhead-view', 'trailhead-capture', 'trailhead-manage']) {
|
|
1111
|
-
|
|
1132
|
+
const want = cl === 'trailhead-capture' ? '../_shared/capture-core.md' : '../_shared/load-first.md';
|
|
1133
|
+
ok(`claude: ${cl} SKILL.md references ${want}`, fs.readFileSync(path.join(claudeDir, 'skills', cl, 'SKILL.md'), 'utf8').includes(want));
|
|
1112
1134
|
}
|
|
1113
1135
|
|
|
1114
1136
|
// #146: single-sourced ticket-language directive, projected on Claude too.
|
|
@@ -1128,6 +1150,10 @@ ok('claude: hooks/lib/body-sources.js exists (secret-guard require target, #210)
|
|
|
1128
1150
|
fs.existsSync(path.join(claudeDir, 'hooks', 'lib', 'body-sources.js')));
|
|
1129
1151
|
ok('claude: hooks/lib/unmodelled.js exists (guards require target, #227)',
|
|
1130
1152
|
fs.existsSync(path.join(claudeDir, 'hooks', 'lib', 'unmodelled.js')));
|
|
1153
|
+
ok('claude: hooks/lib/inherit-session.js exists (required by model-route and model-defaults-review, #323)',
|
|
1154
|
+
fs.existsSync(path.join(claudeDir, 'hooks', 'lib', 'inherit-session.js')));
|
|
1155
|
+
ok('claude: hooks/lib/model-route.js exists (grilling routing helper, #323)',
|
|
1156
|
+
fs.existsSync(path.join(claudeDir, 'hooks', 'lib', 'model-route.js')));
|
|
1131
1157
|
ok('claude: hooks/lib/model-defaults-review.js exists (check-update require target, #186)',
|
|
1132
1158
|
fs.existsSync(path.join(claudeDir, 'hooks', 'lib', 'model-defaults-review.js')));
|
|
1133
1159
|
ok('claude: hooks/lib/model-defaults.json exists (#186)',
|
|
@@ -1185,7 +1211,7 @@ ok('claude: SKILL.md does not start with the codex adapter header', !fs.readFile
|
|
|
1185
1211
|
// falls back to running inline on the session model, so the per-technique
|
|
1186
1212
|
// config.models.* split silently never applies.
|
|
1187
1213
|
const claudeAgentsDir = path.join(claudeDir, 'agents');
|
|
1188
|
-
const expectedAgents = ['trailhead-plan', 'trailhead-executor', 'trailhead-research', 'trailhead-code-review', 'trailhead-debug', 'trailhead-fix', 'trailhead-codebase-map'];
|
|
1214
|
+
const expectedAgents = ['trailhead-plan', 'trailhead-executor', 'trailhead-research', 'trailhead-code-review', 'trailhead-debug', 'trailhead-fix', 'trailhead-codebase-map', 'trailhead-grill'];
|
|
1189
1215
|
for (const a of expectedAgents) {
|
|
1190
1216
|
ok(`claude: agents/${a}.md placed`, fs.existsSync(path.join(claudeAgentsDir, `${a}.md`)));
|
|
1191
1217
|
}
|
|
@@ -1315,6 +1341,7 @@ ok('claude: hooks/trailhead-bash-guards.js and its six modules exist', ['bash-gu
|
|
|
1315
1341
|
// Prototype "link in the same message as the approval ask" rule.
|
|
1316
1342
|
ok('claude: hooks/trailhead-mockup-link-guard.js exists', fs.existsSync(path.join(claudeDir, 'hooks', 'trailhead-mockup-link-guard.js')));
|
|
1317
1343
|
ok('claude: hooks/lib/mockup-link.js exists (mockup-link-guard require target)', fs.existsSync(path.join(claudeDir, 'hooks', 'lib', 'mockup-link.js')));
|
|
1344
|
+
ok('claude: hooks/lib/gh-auth-preflight.js exists (check-update require target)', fs.existsSync(path.join(claudeDir, 'hooks', 'lib', 'gh-auth-preflight.js')));
|
|
1318
1345
|
ok('claude: hooks/trailhead-mockup-link-stop.js does NOT exist (Codex-only twin)', !fs.existsSync(path.join(claudeDir, 'hooks', 'trailhead-mockup-link-stop.js')));
|
|
1319
1346
|
ok('claude: settings.json does NOT register mockup-link-stop', !settingsContent.includes('trailhead-mockup-link-stop.js'));
|
|
1320
1347
|
ok('claude: settings.json registers mockup-link-guard under PreToolUse/AskUserQuestion',
|
|
@@ -1390,6 +1417,21 @@ const pinConfigTomlPath = path.join(pinCodexHomeDir, 'config.toml');
|
|
|
1390
1417
|
ok('codex: config.toml enables multi_agent_v2 when a models.codex.* pin is projected',
|
|
1391
1418
|
fs.existsSync(pinConfigTomlPath) && fs.readFileSync(pinConfigTomlPath, 'utf8').includes('multi_agent_v2 = true'));
|
|
1392
1419
|
|
|
1420
|
+
// #323: a project layer that sets plan but not grill grills on that plan, even when
|
|
1421
|
+
// the global layer pins grill (the implicit-grill rule applies per layer, before the merge).
|
|
1422
|
+
{
|
|
1423
|
+
const pd = mktmp();
|
|
1424
|
+
fs.mkdirSync(path.join(pd, '.trailhead'), { recursive: true });
|
|
1425
|
+
fs.writeFileSync(path.join(pd, '.trailhead', 'config.json'), JSON.stringify({ models: { codex: { plan: 'gpt-5.6-sol' } } }));
|
|
1426
|
+
const ch = mktmp();
|
|
1427
|
+
fs.mkdirSync(path.join(ch, 'trailhead'), { recursive: true });
|
|
1428
|
+
fs.writeFileSync(path.join(ch, 'trailhead', 'config.json'), JSON.stringify({ models: { codex: { grill: 'gpt-5.6-luna' } } }));
|
|
1429
|
+
runInstaller([`--codex`, `--dir=${ch}`], { cwd: pd });
|
|
1430
|
+
const grillToml = fs.readFileSync(path.join(ch, 'agents', 'trailhead-grill.toml'), 'utf8');
|
|
1431
|
+
ok('codex: project plan with no project grill beats a global grill pin in trailhead-grill.toml',
|
|
1432
|
+
grillToml.includes('model = "gpt-5.6-sol"') && !grillToml.includes('gpt-5.6-luna'));
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1393
1435
|
// Uninstall must sweep the projected TOML too, without a --dir= cwd dependency.
|
|
1394
1436
|
runInstaller([`--codex`, `--dir=${pinCodexHomeDir}`, '--uninstall']);
|
|
1395
1437
|
ok('codex uninstall: trailhead-execute.toml is gone', !fs.existsSync(pinnedTomlPath));
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Docs test for #322: resolving any ticket (map or whiteboard, via work or quick) unblocks
|
|
3
|
+
// its dependents, as a step independent of the map-only `Decisions so far` book-keeping.
|
|
4
|
+
// Plain asserts, no framework. Run: node bin/unblock-docs.test.js
|
|
5
|
+
const assert = require('assert');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
const skills = path.resolve(__dirname, '..', 'plugins', 'trailhead', 'skills');
|
|
10
|
+
const read = (p) => fs.readFileSync(path.join(skills, p), 'utf8');
|
|
11
|
+
const engines = read('trailhead-work/references/ticket-engines.md');
|
|
12
|
+
const work = read('trailhead-work/SKILL.md');
|
|
13
|
+
const commands = read('_shared/substrate-commands.md');
|
|
14
|
+
const HELPER = 'trailhead-unblock.js --issue <n> --repo <owner/name>';
|
|
15
|
+
let passed = 0;
|
|
16
|
+
const ok = (name, cond) => { assert.ok(cond, name); passed++; };
|
|
17
|
+
|
|
18
|
+
const resolveLine = (section) => {
|
|
19
|
+
const start = engines.indexOf(section);
|
|
20
|
+
const m = /^5\. \*\*Resolve\*\*.*$/m.exec(engines.slice(start));
|
|
21
|
+
return m ? m[0] : '';
|
|
22
|
+
};
|
|
23
|
+
const unblockClause = (line) => {
|
|
24
|
+
const i = line.indexOf('**Unblock dependents');
|
|
25
|
+
if (i < 0) return '';
|
|
26
|
+
// the clause ends at the next bold step (the handoff), not at a fixed window
|
|
27
|
+
const end = line.indexOf('**Session handoff**', i);
|
|
28
|
+
return line.slice(i, end < 0 ? undefined : end);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
for (const section of ['## `build`', '## `bug`']) {
|
|
32
|
+
const line = resolveLine(section);
|
|
33
|
+
const clause = unblockClause(line);
|
|
34
|
+
ok(`engines ${section}: Resolve has an Unblock dependents clause`, clause !== '');
|
|
35
|
+
ok(`engines ${section}: names the helper`, clause.includes(HELPER));
|
|
36
|
+
ok(`engines ${section}: applies to map and whiteboard tickets alike`, /whiteboard/.test(clause) && /map/.test(clause));
|
|
37
|
+
ok(`engines ${section}: keeps the by-hand fallback`, clause.includes('--remove-label "trailhead:blocked"'));
|
|
38
|
+
ok(`engines ${section}: independent of the map-only Decisions so far update`, /independent of the map-only `Decisions so far` update/.test(clause));
|
|
39
|
+
ok(`engines ${section}: runs right after the close`, /^\*\*Unblock dependents[^*]*\*\*\s*Right after the close/.test(clause) && line.indexOf('gh issue close') < line.indexOf('**Unblock dependents'));
|
|
40
|
+
ok(`engines ${section}: fallback names the blocked_by endpoint`, clause.includes('dependencies/blocked_by'));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const step5 = (/^5\. Record the resolution.*$/m.exec(work) || [''])[0];
|
|
44
|
+
ok('SKILL.md step 5: unblock is its own sentence naming the helper', /\.\s+Unblocking is a separate step[^]*\*\*unblock dependents\*\*/.test(step5) && step5.includes('trailhead-unblock.js'));
|
|
45
|
+
ok('SKILL.md step 5: holds for whiteboard tickets too', /whiteboard/.test(step5));
|
|
46
|
+
|
|
47
|
+
const bullet = (/^- \*\*No map narrative book-keeping\.\*\*.*$/m.exec(work) || [''])[0];
|
|
48
|
+
ok('SKILL.md quick bullet exists', bullet !== '');
|
|
49
|
+
ok('SKILL.md quick bullet: unblocking is among the steps that still hold', /Everything else holds:[^.]*unblocking dependents/.test(bullet) && bullet.includes('trailhead-unblock.js'));
|
|
50
|
+
ok('SKILL.md quick bullet: no map-only exception', !bullet.includes('Exception, `quick <n>` on a *map* ticket'));
|
|
51
|
+
|
|
52
|
+
ok('substrate-commands.md: unblock block names the helper as default', /Unblock:[\s\S]{0,400}trailhead-unblock\.js --issue <n> --repo <owner\/name>/.test(commands));
|
|
53
|
+
ok('substrate-commands.md: keeps the raw gh command as fallback', /Unblock:[\s\S]{0,700}gh issue edit <(?:n|dep)> --remove-label "trailhead:blocked"/.test(commands));
|
|
54
|
+
ok('substrate-commands.md: cookbook comment names the helper', /# unblock[^\n]*trailhead-unblock\.js/.test(commands));
|
|
55
|
+
ok('no em-dash', ![engines, work, commands].some((s) => s.includes(String.fromCharCode(0x2014))));
|
|
56
|
+
console.log(`unblock-docs.test.js: ${passed} checks passed`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marcomigozzi/trailhead",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trailhead",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
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 +7,7 @@ tools: Read, Grep, Glob, Bash, WebFetch, WebSearch, TodoWrite
|
|
|
7
7
|
|
|
8
8
|
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.
|
|
9
9
|
|
|
10
|
-
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
|
|
10
|
+
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 `config.models.codebase-map` model as the spawn-time override, so config stays authoritative.
|
|
11
11
|
|
|
12
12
|
<!-- trailhead:agent-block common -->
|
|
13
13
|
**Search-command hygiene.** Prefer the Grep and Read tools; use absolute paths and `git -C <abs>`.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: trailhead-grill
|
|
3
|
+
color: yellow
|
|
4
|
+
description: "trailhead grill subagent: one read-only Grilling round, returned as a question or a holistic review."
|
|
5
|
+
tools: Read, Grep, Glob, WebFetch, WebSearch, TodoWrite
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
trailhead's grill subagent. Run one round of Grilling for the orchestrator: read the code facts the question needs, then return either the next numbered question with your recommended answer, or the holistic review of the settled choices. You are read-only: you never talk to the user and never answer on their behalf; the orchestrator relays your question and re-dispatches you with the answer.
|
|
9
|
+
|
|
10
|
+
The full protocol is injected at spawn from the trailhead skill's single source (`_shared/techniques/grilling.md`); follow its round bullets and the `Return contract` below, not any embedded copy. The orchestrator-facing section of that file is not yours. The orchestrator passes the ticket body, the relevant decisions, the tickets the subject names and the Q&A so far; a fact you cannot reach goes under `facts` as `not looked up:`. Model-agnostic by design: the engine passes the `config.models.grill` model as the spawn-time override, so config stays authoritative.
|
|
11
|
+
|
|
12
|
+
<!-- trailhead:agent-block common -->
|
|
13
|
+
**Search-command hygiene.** Prefer the Grep and Read tools; use absolute paths and `git -C <abs>`.
|
|
14
|
+
Never `cd` (or `pushd`/`env -C`) into a directory first; the full rule is in `_shared/techniques.md`.
|
|
15
|
+
|
|
16
|
+
**Boundaries.** Never `git push`. Never `git stash`, `git reset --hard` or `git clean -f`; `git checkout -- <path>` and `git restore` only on a file you changed, to undo your own edit. Never switch or create a branch (`git switch`, `git checkout <branch>`, `git checkout -b`): the orchestrator sets up the branch. Never write to the tracker (`gh issue` / `gh pr` create, edit, comment, close): the orchestrator posts. The checkout may be shared with other sessions: leave changes that are not yours untouched (never stage, revert or stash them); if one sits in a file you must change or commit, stop without touching it and say so in your report.
|
|
17
|
+
<!-- /trailhead:agent-block common -->
|
|
18
|
+
|
|
19
|
+
<!-- trailhead:agent-block grilling -->
|
|
20
|
+
**Return contract.** Return the complete report as your final message. Do not post it anywhere (no issue comment, no PR comment): the orchestrator posts what it records. Never commit. Read-only: never Edit/Write.
|
|
21
|
+
- The first line of the report is `status: <token>`, verbatim, with nothing before it. It is the only line starting with `status:`: status text anywhere in the body is quoted in a code fence or a blockquote, or reworded.
|
|
22
|
+
- statuses: `question` | `review`.
|
|
23
|
+
- required fields: `settled`, `frontier`, `amendments`, `conflicts`.
|
|
24
|
+
- optional fields: `question`, `recommended`, `areas`, `facts`.
|
|
25
|
+
- invariant: `question` => `question` non-empty.
|
|
26
|
+
- invariant: `question` => `recommended` non-empty.
|
|
27
|
+
- invariant: `question` => `amendments` none.
|
|
28
|
+
- invariant: `question` => `conflicts` none.
|
|
29
|
+
- invariant: `review` => `frontier` none.
|
|
30
|
+
- invariant: `review` => `question` none.
|
|
31
|
+
- A field is a top-level line `<label>:` (outside code fences and blockquotes); its value is the rest of that line plus every following line up to the next field label. An empty value or `none` means none.
|
|
32
|
+
- Fields are the end of the report: nothing after the last field.
|
|
33
|
+
- You never talk to the user and never answer on their behalf: the orchestrator relays the question and collects the answer. The recommended answer is only your pick.
|
|
34
|
+
- `settled:` every settled choice so far, one per line by question number, with its rationale; `none` before the first answer.
|
|
35
|
+
- `frontier:` the open questions whose prerequisites are settled; `none` once it is empty.
|
|
36
|
+
- `question:` the one numbered question to ask now. Illustrate each option: what it optimizes and what it sacrifices. Offer defer in prose when the question is blocked on a prerequisite. Play back the user's understanding when it closes a branch.
|
|
37
|
+
- `recommended:` your pick, stress-tested, ending with a `Weak point:` line (not a field label, so a review may carry one per fix). Never the delegate option.
|
|
38
|
+
- `areas:` for a multi-part space, every functional area, each marked visited or open.
|
|
39
|
+
- `facts:` what you looked up, with `file:line` or a URL. A fact you could not reach is written as `not looked up: <what>`; the orchestrator fetches it for the next round.
|
|
40
|
+
- `amendments:` / `conflicts:` the holistic review. Each item names its question numbers; each conflict carries its fix, or one line saying where it goes. Both are `none` when nothing is found, and always `none` on a `question` round.
|
|
41
|
+
<!-- /trailhead:agent-block grilling -->
|