@marcomigozzi/trailhead 0.17.0 → 0.18.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 +28 -18
- package/README.md +28 -18
- package/bin/base-preflight-docs.test.js +4 -1
- package/bin/bulk-cross-reference-docs.test.js +36 -0
- package/bin/engine-route-docs.test.js +7 -0
- package/bin/facet-stamp-docs.test.js +79 -0
- package/bin/forced-mutation-docs.test.js +38 -0
- package/bin/guard-axis-docs.test.js +69 -0
- package/bin/inbox-needs-info-docs.test.js +68 -0
- package/bin/lib/codex-projection.js +8 -8
- package/bin/lib/codex-projection.test.js +19 -3
- package/bin/needs-you-docs.test.js +9 -2
- package/bin/options-in-chat-docs.test.js +65 -0
- package/bin/pick-stress-test-docs.test.js +1 -1
- package/bin/pipeline-review-docs.test.js +118 -0
- package/bin/plan-review-budget-docs.test.js +63 -0
- package/bin/repo-target-docs.test.js +41 -0
- package/bin/seam-coverage-docs.test.js +46 -0
- package/bin/standards-brief-docs.test.js +112 -0
- package/bin/test-once-docs.test.js +101 -0
- package/bin/test-run-docs.test.js +47 -0
- package/bin/trailhead.js +9 -4
- package/bin/trailhead.test.js +18 -6
- package/bin/yes-accepts-recommended-docs.test.js +54 -0
- package/package.json +1 -1
- package/plugins/trailhead/.claude-plugin/plugin.json +1 -1
- package/plugins/trailhead/agents/trailhead-code-review.md +2 -1
- package/plugins/trailhead/agents/trailhead-grill.md +2 -2
- package/plugins/trailhead/agents/trailhead-plan.md +1 -1
- package/plugins/trailhead/agents/trailhead-verify.md +1 -0
- package/plugins/trailhead/hooks/lib/audit-walk.test.js +62 -0
- package/plugins/trailhead/hooks/lib/git-env-mod.test.js +55 -0
- package/plugins/trailhead/hooks/lib/git-env-spawn.test.js +73 -0
- package/plugins/trailhead/hooks/lib/git-env.test.js +51 -0
- package/plugins/trailhead/hooks/lib/global-config-dir.test.js +48 -0
- package/plugins/trailhead/hooks/lib/heredoc-mod.test.js +85 -0
- package/plugins/trailhead/hooks/lib/inbox-needs-info.test.js +179 -0
- package/plugins/trailhead/hooks/lib/issue-body.test.js +81 -0
- package/plugins/trailhead/hooks/lib/needs-you.test.js +45 -1
- package/plugins/trailhead/hooks/lib/plan-review-rounds.test.js +272 -0
- package/plugins/trailhead/hooks/lib/repo-target.test.js +256 -0
- package/plugins/trailhead/hooks/lib/statusline-git-env.test.js +47 -0
- package/plugins/trailhead/hooks/lib/test-run.test.js +757 -0
- package/plugins/trailhead/hooks/lib/trailhead-git-env.js +40 -0
- package/plugins/trailhead/hooks/mod/README.md +30 -14
- package/plugins/trailhead/hooks/mod/auto-run.ts +2 -1
- package/plugins/trailhead/hooks/mod/git-env.ts +31 -0
- package/plugins/trailhead/hooks/mod/heredoc.ts +79 -0
- package/plugins/trailhead/hooks/mod/map-pane-view.ts +3 -1
- package/plugins/trailhead/hooks/mod/map-pane.tsx +144 -45
- package/plugins/trailhead/hooks/mod/repo-pin.ts +105 -4
- package/plugins/trailhead/hooks/mod/work-state-locate.ts +2 -1
- package/plugins/trailhead/hooks/trailhead-bash-guards.js +8 -8
- package/plugins/trailhead/hooks/trailhead-bash-guards.test.js +28 -14
- package/plugins/trailhead/hooks/trailhead-check-update.js +3 -1
- package/plugins/trailhead/hooks/trailhead-labels-guard.js +2 -2
- package/plugins/trailhead/hooks/trailhead-labels-guard.test.js +1 -1
- package/plugins/trailhead/hooks/trailhead-test-guard.js +232 -0
- package/plugins/trailhead/hooks/trailhead-test-guard.test.js +172 -0
- package/plugins/trailhead/skills/_shared/capture-core.md +5 -0
- package/plugins/trailhead/skills/_shared/configuration-reference.md +12 -0
- package/plugins/trailhead/skills/_shared/load-first.md +2 -0
- package/plugins/trailhead/skills/_shared/substrate-commands.md +11 -2
- package/plugins/trailhead/skills/_shared/substrate.md +3 -2
- package/plugins/trailhead/skills/_shared/teamwork.md +5 -3
- package/plugins/trailhead/skills/_shared/techniques/code-review-pipeline.md +24 -0
- package/plugins/trailhead/skills/_shared/techniques/code-review.md +18 -5
- package/plugins/trailhead/skills/_shared/techniques/codebase-map.md +2 -2
- package/plugins/trailhead/skills/_shared/techniques/debug.md +1 -1
- package/plugins/trailhead/skills/_shared/techniques/grilling.md +3 -3
- package/plugins/trailhead/skills/_shared/techniques/plan-review.md +16 -4
- package/plugins/trailhead/skills/_shared/techniques/tdd.md +4 -2
- package/plugins/trailhead/skills/_shared/techniques/verify.md +2 -0
- package/plugins/trailhead/skills/_shared/techniques.md +3 -3
- package/plugins/trailhead/skills/trailhead-chart/SKILL.md +2 -2
- package/plugins/trailhead/skills/trailhead-chart/references/charting.md +1 -1
- package/plugins/trailhead/skills/trailhead-chart/references/inbox.md +8 -3
- package/plugins/trailhead/skills/trailhead-manage/references/auditing.md +17 -2
- package/plugins/trailhead/skills/trailhead-work/SKILL.md +1 -0
- package/plugins/trailhead/skills/trailhead-work/references/auto.md +2 -0
- package/plugins/trailhead/skills/trailhead-work/references/executor-brief.md +2 -0
- package/plugins/trailhead/skills/trailhead-work/references/ticket-engines.md +7 -7
- package/plugins/trailhead/templates/statusline-trailhead.sh +10 -3
- package/plugins/trailhead/templates/trailhead-audit-walk.js +56 -4
- package/plugins/trailhead/templates/trailhead-auto-merge.js +2 -1
- package/plugins/trailhead/templates/trailhead-auto-queue.js +2 -1
- package/plugins/trailhead/templates/trailhead-auto-slot.js +2 -1
- package/plugins/trailhead/templates/trailhead-base-preflight.js +10 -5
- package/plugins/trailhead/templates/trailhead-capture.js +2 -1
- package/plugins/trailhead/templates/trailhead-checkout-find.js +3 -2
- package/plugins/trailhead/templates/trailhead-checkout-in.js +2 -1
- package/plugins/trailhead/templates/trailhead-commit-msg-sync.js +2 -1
- package/plugins/trailhead/templates/trailhead-git-env.js +40 -0
- package/plugins/trailhead/templates/trailhead-inbox-needs-info.js +161 -0
- package/plugins/trailhead/templates/trailhead-isolation-answer.js +2 -1
- package/plugins/trailhead/templates/trailhead-isolation-copy.js +9 -4
- package/plugins/trailhead/templates/trailhead-issue-body.js +92 -6
- package/plugins/trailhead/templates/trailhead-merge-ticket.js +2 -1
- package/plugins/trailhead/templates/trailhead-needs-you.js +38 -4
- package/plugins/trailhead/templates/trailhead-plan-review-rounds.js +261 -7
- package/plugins/trailhead/templates/trailhead-repo-target.js +302 -0
- package/plugins/trailhead/templates/trailhead-test-run.js +752 -0
- package/plugins/trailhead/templates/trailhead-uat-backfill.js +2 -1
- package/plugins/trailhead/templates/trailhead-uat-followup.js +2 -1
- package/plugins/trailhead/templates/trailhead-uat-setup.js +9 -4
- package/plugins/trailhead/templates/trailhead-unblock.js +2 -1
- package/plugins/trailhead/tests/auto-run-ui.test.ts +2 -1
- package/plugins/trailhead/tests/git-env.test.ts +20 -0
- package/plugins/trailhead/tests/map-pane-ui.test.ts +268 -17
- package/plugins/trailhead/tests/map-pane-view.test.ts +6 -0
- package/plugins/trailhead/tests/map-pane-write.test.ts +57 -1
- package/plugins/trailhead/tests/repo-pin-late-repo.test.ts +3 -2
- package/plugins/trailhead/tests/repo-pin-relocate.test.ts +175 -0
- package/plugins/trailhead/tests/unscrub.ts +8 -0
- package/plugins/trailhead/tests/work-state-prompt-hook.test.ts +3 -2
- package/plugins/trailhead/tests/work-state-worktree.test.ts +2 -1
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Docs test for #351: the skill prose names the repo-target helper's check/record
|
|
3
|
+
// before the first tracker write, in load-first.md and in the capture cluster's
|
|
4
|
+
// compact core, and the marker stays gitignored.
|
|
5
|
+
// Run: node repo-target-docs.test.js
|
|
6
|
+
const assert = require('assert');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const root = path.join(__dirname, '..');
|
|
11
|
+
const shared = path.join(root, 'plugins', 'trailhead', 'skills', '_shared');
|
|
12
|
+
|
|
13
|
+
let passed = 0;
|
|
14
|
+
const ok = (name, cond) => { assert.ok(cond, name); passed++; };
|
|
15
|
+
|
|
16
|
+
const read = (f) => fs.readFileSync(path.join(shared, f), 'utf8');
|
|
17
|
+
const loadFirst = read('load-first.md');
|
|
18
|
+
const captureCore = read('capture-core.md');
|
|
19
|
+
const teamwork = read('teamwork.md');
|
|
20
|
+
const gitignore = fs.readFileSync(path.join(root, '.gitignore'), 'utf8');
|
|
21
|
+
|
|
22
|
+
const HEAD = '**Repo target check, once per session, before the first tracker write.**';
|
|
23
|
+
ok('load-first.md carries the repo target paragraph', loadFirst.includes(HEAD));
|
|
24
|
+
ok('capture-core.md carries a drift marker to it', captureCore.includes(`<!-- core: load-first.md | ${HEAD} |`));
|
|
25
|
+
|
|
26
|
+
for (const [name, text] of [['load-first.md', loadFirst], ['capture-core.md', captureCore], ['teamwork.md', teamwork]]) {
|
|
27
|
+
ok(`${name} has no em-dash`, !text.includes('—'));
|
|
28
|
+
}
|
|
29
|
+
for (const [name, text] of [['load-first.md', loadFirst], ['capture-core.md', captureCore]]) {
|
|
30
|
+
ok(`${name} names check`, text.includes('trailhead-repo-target.js check'));
|
|
31
|
+
ok(`${name} names record`, text.includes('trailhead-repo-target.js record'));
|
|
32
|
+
ok(`${name} says record gitignores the marker`, text.includes('`.gitignore` if absent'));
|
|
33
|
+
ok(`${name} names the marker`, text.includes('.trailhead/repo-target.json'));
|
|
34
|
+
}
|
|
35
|
+
ok('capture-core.md places the check before the create commands',
|
|
36
|
+
captureCore.indexOf('## Repo target check') !== -1 && captureCore.indexOf('## Repo target check') < captureCore.indexOf('gh issue create'));
|
|
37
|
+
ok('capture-core.md tells trailhead-capture.js callers to pass the target', captureCore.includes('as `--repo`'));
|
|
38
|
+
ok('teamwork.md lists the marker with its siblings', teamwork.includes('**Repo target record.**') && teamwork.includes('isolation-answer.json'));
|
|
39
|
+
ok('.gitignore carries the record', gitignore.split('\n').includes('.trailhead/repo-target.json'));
|
|
40
|
+
|
|
41
|
+
console.log(`repo-target-docs.test.js: ${passed} checks passed`);
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Docs test for #356: every seam a PLAN names carries one line saying what it
|
|
3
|
+
// catches and what it misses, stated by the TDD technique, the Plan step and
|
|
4
|
+
// the plan agent's return contract.
|
|
5
|
+
// Run: node seam-coverage-docs.test.js
|
|
6
|
+
const assert = require('assert');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const root = path.join(__dirname, '..');
|
|
11
|
+
const skills = path.join(root, 'plugins', 'trailhead', 'skills');
|
|
12
|
+
const read = (...p) => fs.readFileSync(path.join(...p), 'utf8');
|
|
13
|
+
|
|
14
|
+
let passed = 0;
|
|
15
|
+
const ok = (name, cond) => { assert.ok(cond, name); passed++; };
|
|
16
|
+
|
|
17
|
+
const LINE = '`<seam>: catches <what a test here fails on>; misses <what gets through it>`';
|
|
18
|
+
|
|
19
|
+
const tdd = read(skills, '_shared', 'techniques', 'tdd.md');
|
|
20
|
+
const engines = read(skills, 'trailhead-work', 'references', 'ticket-engines.md');
|
|
21
|
+
const blocks = read(root, 'scripts', 'agent-blocks.md');
|
|
22
|
+
const agent = read(root, 'plugins', 'trailhead', 'agents', 'trailhead-plan.md');
|
|
23
|
+
|
|
24
|
+
ok('tdd: states the seam line', tdd.includes(LINE));
|
|
25
|
+
ok('tdd: a seam line is required in the PLAN', tdd.includes('the PLAN lists each one with its one line'));
|
|
26
|
+
ok('tdd: misses is never empty', tdd.includes('`misses` is never empty'));
|
|
27
|
+
|
|
28
|
+
const planStep = engines.split('\n').find((l) => l.startsWith('2. **Plan**:')) || '';
|
|
29
|
+
ok('plan step: the PLAN comment carries the seam line',
|
|
30
|
+
planStep.includes('The step produces a `PLAN` comment: the steps, the seams (each with its one-line catches/misses, per **TDD**), the files touched, the verification criteria.'));
|
|
31
|
+
|
|
32
|
+
const contract = `\`ready\`: the rest of the report is the PLAN (the \`body\`): the steps, the seams (one line each, ${LINE}), the files touched and the verification criteria.`;
|
|
33
|
+
ok('agent blocks: the plan contract carries the seam line', blocks.includes(contract));
|
|
34
|
+
ok('plan agent: the contract carries the seam line', agent.includes(contract));
|
|
35
|
+
|
|
36
|
+
for (const [name, text] of Object.entries({
|
|
37
|
+
'tdd.md': tdd,
|
|
38
|
+
'ticket-engines.md': engines,
|
|
39
|
+
'agent-blocks.md': blocks,
|
|
40
|
+
'trailhead-plan.md': agent,
|
|
41
|
+
'seam-coverage-docs.test.js': read(__dirname, 'seam-coverage-docs.test.js'),
|
|
42
|
+
})) {
|
|
43
|
+
ok(`${name} has no em-dash`, !text.includes(String.fromCharCode(0x2014)));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log(`seam-coverage-docs.test.js: ${passed} checks passed`);
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Docs test for #359: before a code review the orchestrator lists the repo's
|
|
3
|
+
// standards files, the brief carries the paths, and the reviewer reads them and
|
|
4
|
+
// cites the file for each standards finding.
|
|
5
|
+
// Run: node standards-brief-docs.test.js
|
|
6
|
+
const assert = require('assert');
|
|
7
|
+
const { execFileSync } = require('child_process');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
const root = path.join(__dirname, '..');
|
|
13
|
+
const skills = path.join(root, 'plugins', 'trailhead', 'skills');
|
|
14
|
+
const agents = path.join(root, 'plugins', 'trailhead', 'agents');
|
|
15
|
+
const read = (...p) => fs.readFileSync(path.join(...p), 'utf8');
|
|
16
|
+
|
|
17
|
+
let passed = 0;
|
|
18
|
+
const ok = (name, cond) => { assert.ok(cond, name); passed++; };
|
|
19
|
+
|
|
20
|
+
const review = read(skills, '_shared', 'techniques', 'code-review.md');
|
|
21
|
+
const techniques = read(skills, '_shared', 'techniques.md');
|
|
22
|
+
const agent = read(agents, 'trailhead-code-review.md');
|
|
23
|
+
|
|
24
|
+
const lines = review.split('\n');
|
|
25
|
+
const route = lines.find((l) => l.startsWith('**First, route it.**')) || '';
|
|
26
|
+
const paraStart = lines.findIndex((l) => l.startsWith('**Standards files: list them before the review.**'));
|
|
27
|
+
const axesIdx = lines.indexOf('Review on five axes:');
|
|
28
|
+
const ticketDiffIdx = lines.findIndex((l) => l.startsWith('**Ticket diff:'));
|
|
29
|
+
const section = paraStart >= 0 ? lines.slice(paraStart, axesIdx).join('\n') : '';
|
|
30
|
+
const axis = lines.find((l) => l.startsWith('- **Standards & spec**')) || '';
|
|
31
|
+
|
|
32
|
+
// The brief: the spawn prompt carries the list, paths only.
|
|
33
|
+
ok('route: spawn prompt carries the standards files line', route.includes('`standards files: <list>`'));
|
|
34
|
+
ok('route: points at the listing', route.includes('**Standards files**'));
|
|
35
|
+
|
|
36
|
+
// The listing.
|
|
37
|
+
ok('section exists', section.length > 0);
|
|
38
|
+
ok('section sits after Ticket diff and before the axes', ticketDiffIdx >= 0 && paraStart > ticketDiffIdx && axesIdx > paraStart);
|
|
39
|
+
ok('axes count unchanged', review.includes('Review on five axes:'));
|
|
40
|
+
|
|
41
|
+
const command = (section.match(/```\n(git -C <repo> ls-files [^\n]+)\n```/) || [])[1] || '';
|
|
42
|
+
ok('section gives the exact listing command', command.length > 0);
|
|
43
|
+
for (const pattern of [
|
|
44
|
+
'CONTRIBUTING*', 'CODING_STANDARDS*', 'AGENTS.md', 'CLAUDE.md', '.editorconfig', '.eslintrc*', 'eslint.config.*',
|
|
45
|
+
'.prettierrc*', 'prettier.config.*', 'biome.json*', 'ruff.toml', 'pyproject.toml', '.golangci.*', 'rustfmt.toml',
|
|
46
|
+
]) {
|
|
47
|
+
ok(`command matches ${pattern} at any depth`, command.includes(`**/${pattern}'`));
|
|
48
|
+
}
|
|
49
|
+
ok('CONTRIBUTING matches in any case', command.includes("':(glob,icase)**/CONTRIBUTING*'"));
|
|
50
|
+
|
|
51
|
+
for (const sub of [
|
|
52
|
+
'trailhead:conventions',
|
|
53
|
+
'**Find a singleton issue** in `../substrate-commands.md`',
|
|
54
|
+
'the paths only, never their contents',
|
|
55
|
+
'`standards files: none`',
|
|
56
|
+
'Recompute the list on every review round',
|
|
57
|
+
'gh issue view <n> --json body',
|
|
58
|
+
'cites the file',
|
|
59
|
+
'not a standards finding',
|
|
60
|
+
'what tooling already enforces',
|
|
61
|
+
'**Review adversarially**',
|
|
62
|
+
'never a correctness or security finding',
|
|
63
|
+
'runs the same listing itself',
|
|
64
|
+
'on every pass including the lean inline one',
|
|
65
|
+
]) {
|
|
66
|
+
ok(`section: ${sub}`, section.includes(sub));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// The axis and the pointers.
|
|
70
|
+
ok('axis: reads the listed files', axis.includes('**Standards files**'));
|
|
71
|
+
ok('axis: cites the file', axis.includes('cite the file'));
|
|
72
|
+
ok('techniques.md: row names the listing', techniques.includes("the brief lists the repo's standards files"));
|
|
73
|
+
ok('techniques.md: 5 axes kept', techniques.includes('on 5 axes'));
|
|
74
|
+
ok('agent: reads the files the spawn names and cites them',
|
|
75
|
+
agent.includes('the repo\'s standards files, at the paths the spawn names') && agent.includes('cite the file'));
|
|
76
|
+
|
|
77
|
+
// The command does what it says on a real repo.
|
|
78
|
+
if (command) {
|
|
79
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'standards-brief-'));
|
|
80
|
+
try {
|
|
81
|
+
const git = (...args) => execFileSync('git', ['-C', dir, ...args], { encoding: 'utf8' });
|
|
82
|
+
git('init', '-q');
|
|
83
|
+
const wanted = [
|
|
84
|
+
'CONTRIBUTING.md', '.github/contributing.md', 'CODING_STANDARDS.md', 'AGENTS.md', 'pkg/a/CLAUDE.md',
|
|
85
|
+
'.editorconfig', 'eslint.config.mjs', 'pkg/b/.prettierrc.json', 'pyproject.toml',
|
|
86
|
+
];
|
|
87
|
+
const unwanted = ['README.md', 'src/index.js', 'docs/AGENTS.txt'];
|
|
88
|
+
for (const f of [...wanted, ...unwanted, 'ignored/CLAUDE.md']) {
|
|
89
|
+
fs.mkdirSync(path.dirname(path.join(dir, f)), { recursive: true });
|
|
90
|
+
fs.writeFileSync(path.join(dir, f), 'x\n');
|
|
91
|
+
}
|
|
92
|
+
fs.writeFileSync(path.join(dir, '.gitignore'), 'ignored/\n');
|
|
93
|
+
git('add', '-A');
|
|
94
|
+
const out = execFileSync('sh', ['-c', command.replace('<repo>', `'${dir}'`)], { encoding: 'utf8' })
|
|
95
|
+
.split('\n').filter(Boolean).sort();
|
|
96
|
+
assert.deepStrictEqual(out, [...wanted].sort(), 'listing finds exactly the tracked standards files');
|
|
97
|
+
passed++;
|
|
98
|
+
} finally {
|
|
99
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
for (const [name, text] of Object.entries({
|
|
104
|
+
'code-review.md': review,
|
|
105
|
+
'techniques.md': techniques,
|
|
106
|
+
'trailhead-code-review.md': agent,
|
|
107
|
+
'standards-brief-docs.test.js': read(__dirname, 'standards-brief-docs.test.js'),
|
|
108
|
+
})) {
|
|
109
|
+
ok(`${name} has no em-dash`, !text.includes(String.fromCharCode(0x2014)));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.log(`standards-brief-docs.test.js: ${passed} checks passed`);
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Docs test for #363: the engine runs the full test suite once per ticket.
|
|
3
|
+
// Execute/Fix run focused tests, Verify runs the full suite once on the synced
|
|
4
|
+
// tree and records it, the reviewing agents read that record, and no em-dash.
|
|
5
|
+
// Run: node test-once-docs.test.js
|
|
6
|
+
const assert = require('assert');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const root = path.join(__dirname, '..');
|
|
11
|
+
const skills = path.join(root, 'plugins', 'trailhead', 'skills');
|
|
12
|
+
const agents = path.join(root, 'plugins', 'trailhead', 'agents');
|
|
13
|
+
const read = (...p) => fs.readFileSync(path.join(...p), 'utf8');
|
|
14
|
+
|
|
15
|
+
let passed = 0;
|
|
16
|
+
const ok = (name, cond) => { assert.ok(cond, name); passed++; };
|
|
17
|
+
|
|
18
|
+
const docs = {
|
|
19
|
+
'executor-brief.md': read(skills, 'trailhead-work', 'references', 'executor-brief.md'),
|
|
20
|
+
'ticket-engines.md': read(skills, 'trailhead-work', 'references', 'ticket-engines.md'),
|
|
21
|
+
'teamwork.md': read(skills, '_shared', 'teamwork.md'),
|
|
22
|
+
'tdd.md': read(skills, '_shared', 'techniques', 'tdd.md'),
|
|
23
|
+
'debug.md': read(skills, '_shared', 'techniques', 'debug.md'),
|
|
24
|
+
'code-review.md': read(skills, '_shared', 'techniques', 'code-review.md'),
|
|
25
|
+
'verify.md': read(skills, '_shared', 'techniques', 'verify.md'),
|
|
26
|
+
'configuration-reference.md': read(skills, '_shared', 'configuration-reference.md'),
|
|
27
|
+
'agent-blocks.md': read(root, 'scripts', 'agent-blocks.md'),
|
|
28
|
+
'trailhead-code-review.md': read(agents, 'trailhead-code-review.md'),
|
|
29
|
+
'trailhead-verify.md': read(agents, 'trailhead-verify.md'),
|
|
30
|
+
'README.md': read(root, 'README.md'),
|
|
31
|
+
'README.it.md': read(root, 'README.it.md'),
|
|
32
|
+
'configuration.mdx': read(root, 'site', 'src', 'content', 'docs', 'docs', 'configuration.mdx'),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Execute/Fix: focused tests only, never the whole suite.
|
|
36
|
+
const brief = docs['executor-brief.md'];
|
|
37
|
+
ok('executor-brief.md: focused tests only', brief.includes('run only the focused tests'));
|
|
38
|
+
ok('executor-brief.md: never the whole suite', brief.includes('never the whole suite'));
|
|
39
|
+
ok('executor-brief.md: targeted run through the helper', brief.includes('--kind targeted --files <a,b>'));
|
|
40
|
+
ok('tdd.md: loop runs focused tests', docs['tdd.md'].includes('focused tests'));
|
|
41
|
+
ok('tdd.md: full suite once at Verify', docs['tdd.md'].includes('once per ticket, at Verify'));
|
|
42
|
+
|
|
43
|
+
// Verify: one full run on the synced tree, recorded, read by the reviewers.
|
|
44
|
+
const engines = docs['ticket-engines.md'];
|
|
45
|
+
ok('ticket-engines.md: one full run per tree', (engines.match(/runs the full suite once on the synced tree/g) || []).length === 2);
|
|
46
|
+
ok('ticket-engines.md: test record named in build and bug Verify', (engines.match(/\*\*test record\*\*/g) || []).length === 2);
|
|
47
|
+
ok('ticket-engines.md: no double run after the sync', !engines.includes('then sync, then re-run the tests'));
|
|
48
|
+
|
|
49
|
+
// Each Verify (build, bug) is its own line of ticket-engines.md.
|
|
50
|
+
const verifyLines = engines.split('\n').filter((l) => l.startsWith('4. **Verify**'));
|
|
51
|
+
ok('ticket-engines.md: build and bug Verify found', verifyLines.length === 2);
|
|
52
|
+
for (const [i, line] of verifyLines.entries()) {
|
|
53
|
+
const which = i === 0 ? 'build' : 'bug';
|
|
54
|
+
ok(`${which} Verify: exactly one full-run invocation`, (line.match(/trailhead-test-run\.js run --repo/g) || []).length === 1);
|
|
55
|
+
ok(`${which} Verify: failed record reused on the same tree`, line.includes('reuse it instead of invoking the helper'));
|
|
56
|
+
ok(`${which} Verify: reviewers never re-run, exactly once`, (line.match(/never re-run the full suite/g) || []).length === 1);
|
|
57
|
+
ok(`${which} Verify: no legacy second run`, !line.includes('then run the tests') && !line.includes('then re-run the tests'));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Scoping: the record and single-run rules apply only with testing.full.
|
|
61
|
+
ok('executor-brief.md: scoping names testing.full', /testing\.full[^\n]*focused/.test(brief));
|
|
62
|
+
ok('ticket-engines.md: unconfigured runs once, no record, reviewers get none', verifyLines.every((l) => l.includes('The helper\'s `unconfigured` falls back to the project\'s own command, run once on the synced tree; no test record is built and the reviewing agents get none, so they test as today (Execute and Fix focused runs still use `--kind targeted --files <a,b>`)')));
|
|
63
|
+
ok('ticket-engines.md: no garbled as-before sentence', verifyLines.every((l) => !l.includes('behave as before')));
|
|
64
|
+
ok('ticket-engines.md: Execute/Fix focused clause kept', verifyLines.every((l) => l.includes('--kind targeted --files <a,b>')));
|
|
65
|
+
ok('configuration-reference.md: scoping names testing.full', /without `testing\.full`/i.test(docs['configuration-reference.md']));
|
|
66
|
+
ok('README.md: scoping names testing.full', /without `testing\.full`/i.test(docs['README.md']));
|
|
67
|
+
ok('configuration.mdx: scoping names testing.full', /without `testing\.full`/i.test(docs['configuration.mdx']));
|
|
68
|
+
|
|
69
|
+
// The reviewers never invoke the helper.
|
|
70
|
+
for (const name of ['code-review.md', 'verify.md', 'trailhead-code-review.md', 'trailhead-verify.md']) {
|
|
71
|
+
ok(`${name}: no helper run invocation`, !docs[name].includes('trailhead-test-run.js run'));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Post-sync re-run reuses the cache.
|
|
75
|
+
ok('teamwork.md: post-sync run reuses the cached result', docs['teamwork.md'].includes('`source: cache`'));
|
|
76
|
+
const tw = docs['teamwork.md'];
|
|
77
|
+
ok('teamwork.md: preflight never runs the suite', tw.includes('The preflight itself never runs the suite'));
|
|
78
|
+
ok('teamwork.md: never an extra invocation', tw.includes('never an extra invocation'));
|
|
79
|
+
ok('teamwork.md: Resolve new tree gets one run, record in the resolution comment', tw.includes('its test record is added to the resolution comment, superseding the Verify record'));
|
|
80
|
+
ok('teamwork.md: no separate re-run instruction after synced', !tw.includes('re-run the test suite on the synced tree') && !tw.includes('re-runs the tests'));
|
|
81
|
+
ok('teamwork.md: current needs no re-run', docs['teamwork.md'].includes('`current` needs no re-run'));
|
|
82
|
+
|
|
83
|
+
// Reviewing agents read the record.
|
|
84
|
+
for (const name of ['code-review.md', 'verify.md', 'agent-blocks.md', 'trailhead-code-review.md', 'trailhead-verify.md']) {
|
|
85
|
+
ok(`${name}: no record without testing.full`, docs[name].includes('without `testing.full` no test record is passed'));
|
|
86
|
+
ok(`${name}: reads the test record`, docs[name].includes('test record'));
|
|
87
|
+
ok(`${name}: never re-runs the full suite`, docs[name].includes('never re-run the full suite'));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Debug step 9 through the helper.
|
|
91
|
+
ok('debug.md: full suite through the helper', /full suite green[^\n]*trailhead-test-run\.js/.test(docs['debug.md']));
|
|
92
|
+
|
|
93
|
+
// Config docs.
|
|
94
|
+
ok('configuration-reference.md: once per ticket', docs['configuration-reference.md'].includes('once per ticket'));
|
|
95
|
+
ok('README.md: once per ticket', docs['README.md'].includes('once per ticket'));
|
|
96
|
+
ok('README.it.md: una volta per ticket', docs['README.it.md'].includes('una volta per ticket'));
|
|
97
|
+
ok('configuration.mdx: once per ticket', docs['configuration.mdx'].includes('once per ticket'));
|
|
98
|
+
|
|
99
|
+
for (const [name, text] of Object.entries(docs)) ok(`${name} has no em-dash`, !text.includes('—'));
|
|
100
|
+
|
|
101
|
+
console.log(`test-once-docs.test.js: ${passed} checks passed`);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Docs test for #362: the docs name the test-run helper, its config keys, the
|
|
3
|
+
// queue and cache contract, and carry no em-dash.
|
|
4
|
+
// Run: node test-run-docs.test.js
|
|
5
|
+
const assert = require('assert');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
const root = path.join(__dirname, '..');
|
|
10
|
+
const skills = path.join(root, 'plugins', 'trailhead', 'skills');
|
|
11
|
+
const read = (...p) => fs.readFileSync(path.join(...p), 'utf8');
|
|
12
|
+
|
|
13
|
+
let passed = 0;
|
|
14
|
+
const ok = (name, cond) => { assert.ok(cond, name); passed++; };
|
|
15
|
+
|
|
16
|
+
const docs = {
|
|
17
|
+
'ticket-engines.md': read(skills, 'trailhead-work', 'references', 'ticket-engines.md'),
|
|
18
|
+
'executor-brief.md': read(skills, 'trailhead-work', 'references', 'executor-brief.md'),
|
|
19
|
+
'teamwork.md': read(skills, '_shared', 'teamwork.md'),
|
|
20
|
+
'configuration-reference.md': read(skills, '_shared', 'configuration-reference.md'),
|
|
21
|
+
'README.md': read(root, 'README.md'),
|
|
22
|
+
'README.it.md': read(root, 'README.it.md'),
|
|
23
|
+
'configuration.mdx': read(root, 'site', 'src', 'content', 'docs', 'docs', 'configuration.mdx'),
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
for (const name of ['ticket-engines.md', 'executor-brief.md', 'teamwork.md']) {
|
|
27
|
+
ok(`${name} names the helper`, docs[name].includes('trailhead-test-run.js'));
|
|
28
|
+
}
|
|
29
|
+
ok('ticket-engines.md names the full run command', docs['ticket-engines.md'].includes('trailhead-test-run.js run --repo <working-root> --config-root <project root>'));
|
|
30
|
+
ok('ticket-engines.md names the targeted run', docs['ticket-engines.md'].includes('--kind targeted --files'));
|
|
31
|
+
ok('ticket-engines.md names the unconfigured fallback', docs['ticket-engines.md'].includes('`unconfigured` falls back'));
|
|
32
|
+
|
|
33
|
+
for (const name of ['configuration-reference.md', 'README.md', 'README.it.md', 'configuration.mdx']) {
|
|
34
|
+
for (const key of ['testing.full', 'testing.targeted', 'testing.maxParallel']) {
|
|
35
|
+
ok(`${name} documents ${key}`, docs[name].includes(key));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const ref = docs['configuration-reference.md'];
|
|
39
|
+
for (const needle of ['**Test runs.**', 'TRAILHEAD_CACHE_DIR', 'nice -n 10', 'ionice -c 3', '--no-cache', 'tree SHA + command', '🧪 Test suite', 'safe to delete when no test run is active']) {
|
|
40
|
+
ok(`configuration-reference.md carries: ${needle}`, ref.includes(needle));
|
|
41
|
+
}
|
|
42
|
+
ok('README.md carries the Test runs paragraph', docs['README.md'].includes('**Test runs.**'));
|
|
43
|
+
ok('README.it.md carries the mirrored paragraph', docs['README.it.md'].includes('**Esecuzioni dei test.**'));
|
|
44
|
+
|
|
45
|
+
for (const [name, text] of Object.entries(docs)) ok(`${name} has no em-dash`, !text.includes('—'));
|
|
46
|
+
|
|
47
|
+
console.log(`test-run-docs.test.js: ${passed} checks passed`);
|
package/bin/trailhead.js
CHANGED
|
@@ -248,7 +248,7 @@ function claudePaths(configDir) {
|
|
|
248
248
|
// The six Bash guard modules. They are shipped, but only require()d by the
|
|
249
249
|
// merged trailhead-bash-guards.js (one process per Bash call), never registered
|
|
250
250
|
// on their own. Legacy installs registered each one; the installers strip those.
|
|
251
|
-
const BASH_GUARD_MODULES = ['trailhead-commit-guard.js', 'trailhead-secret-guard.js', 'trailhead-install-guard.js', 'trailhead-search-guard.js', 'trailhead-body-guard.js', 'trailhead-labels-guard.js'];
|
|
251
|
+
const BASH_GUARD_MODULES = ['trailhead-commit-guard.js', 'trailhead-secret-guard.js', 'trailhead-install-guard.js', 'trailhead-search-guard.js', 'trailhead-body-guard.js', 'trailhead-labels-guard.js', 'trailhead-test-guard.js'];
|
|
252
252
|
// Every hook script copied into the hooks dir (shipped runtime files).
|
|
253
253
|
const HOOK_FILES = [...BASH_GUARD_MODULES, 'trailhead-bash-guards.js', 'trailhead-issue-injection-scanner.js', 'trailhead-secret-read-guard.js', 'trailhead-mockup-link-guard.js', 'trailhead-mockup-link-stop.js', 'trailhead-check-update.js'];
|
|
254
254
|
// The registered entrypoints: the shipped files that must appear in
|
|
@@ -262,10 +262,12 @@ const ENTRYPOINT_FILES = HOOK_FILES.filter((f) => !BASH_GUARD_MODULES.includes(f
|
|
|
262
262
|
// also does require('./lib/body-sources.js'), #210; trailhead-check-update.js
|
|
263
263
|
// does require('./lib/model-defaults-review.js'), which in turn reads the
|
|
264
264
|
// sibling model-defaults.json, #186; both mockup-link hooks do
|
|
265
|
-
// require('./lib/mockup-link.js')
|
|
265
|
+
// require('./lib/mockup-link.js'); trailhead-check-update.js also does
|
|
266
|
+
// require('./lib/trailhead-git-env.js'), a byte-identical copy of
|
|
267
|
+
// templates/trailhead-git-env.js, #349). Copied by name, never a recursive
|
|
266
268
|
// sweep: the Claude hooks/lib dir is shared with other plugins, so we must
|
|
267
269
|
// not clobber a co-tenant's lib nor ship our own *.test.js files.
|
|
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'];
|
|
270
|
+
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', 'trailhead-git-env.js'];
|
|
269
271
|
// search-guard is Claude-Code-specific (#169), and so is mockup-link-guard
|
|
270
272
|
// (it gates the AskUserQuestion tool, which Codex lacks): excluded from both
|
|
271
273
|
// the Codex hook copy and the Codex verify spec's expected hookFiles, so a
|
|
@@ -615,7 +617,10 @@ function installCodex(configDir, { useSymlink }) {
|
|
|
615
617
|
for (const f of (fs.existsSync(L.promptsDir) ? fs.readdirSync(L.promptsDir) : [])) {
|
|
616
618
|
if (f.startsWith('trailhead-')) rmrf(path.join(L.promptsDir, f));
|
|
617
619
|
}
|
|
618
|
-
|
|
620
|
+
// La stessa cartella ospita la config globale di Codex: un aggiornamento non deve cancellarla.
|
|
621
|
+
for (const f of (fs.existsSync(L.legacyEngineDir) ? fs.readdirSync(L.legacyEngineDir) : [])) {
|
|
622
|
+
if (f !== 'config.json') rmrf(path.join(L.legacyEngineDir, f));
|
|
623
|
+
}
|
|
619
624
|
|
|
620
625
|
// Sweep the whole engine + any prior per-verb skills, then project fresh.
|
|
621
626
|
sweepTrailheadSkills(L.skillsRoot);
|
package/bin/trailhead.test.js
CHANGED
|
@@ -471,7 +471,7 @@ ok('both Verify step lines reference the Ticket diff', verifyStepLines181.every(
|
|
|
471
471
|
ok('ticket-engines.md does NOT contain the old range-scoped diff', !ticketEnginesSource181.includes('git diff <base>...HEAD'));
|
|
472
472
|
|
|
473
473
|
ok("techniques.md index row updates Code review to the ticket's own commits",
|
|
474
|
-
techniquesSource181.includes("review the ticket's own commits (its `Refs: #<n>` set) on
|
|
474
|
+
techniquesSource181.includes("review the ticket's own commits (its `Refs: #<n>` set) on 5 axes"));
|
|
475
475
|
|
|
476
476
|
for (const [label, src] of [
|
|
477
477
|
['code-review.md', codeReviewSource181],
|
|
@@ -920,7 +920,7 @@ ok('codex: hooks.json exists', fs.existsSync(codexHooksJsonPath));
|
|
|
920
920
|
const codexHooksJson = JSON.parse(fs.readFileSync(codexHooksJsonPath, 'utf8'));
|
|
921
921
|
const codexHooksJsonStr = JSON.stringify(codexHooksJson);
|
|
922
922
|
ok('codex: hooks.json registers the merged Bash guards under PreToolUse', (codexHooksJson.hooks.PreToolUse || []).some((g) => g.matcher === 'Bash' &&
|
|
923
|
-
(g.hooks || []).some((h) => h.command.includes('trailhead-bash-guards.js') && h.command.endsWith(' --guards=commit,secret,install,body,labels'))));
|
|
923
|
+
(g.hooks || []).some((h) => h.command.includes('trailhead-bash-guards.js') && h.command.endsWith(' --guards=commit,secret,install,body,labels,test'))));
|
|
924
924
|
ok('codex: hooks.json registers none of the per-guard scripts on their own', !/trailhead-(commit|secret|install|search|body|labels)-guard\.js/.test(codexHooksJsonStr));
|
|
925
925
|
ok('codex: the six guard modules the merged hook requires are still shipped (no search)',
|
|
926
926
|
['commit', 'secret', 'install', 'body', 'labels'].every((n) => fs.existsSync(path.join(codexDir, 'skills', 'trailhead', 'hooks', `trailhead-${n}-guard.js`))) &&
|
|
@@ -974,6 +974,8 @@ ok('codex: skills/trailhead/hooks/lib/mockup-link.js exists (mockup-link-stop re
|
|
|
974
974
|
fs.existsSync(path.join(codexDir, 'skills', 'trailhead', 'hooks', 'lib', 'mockup-link.js')));
|
|
975
975
|
ok('codex: skills/trailhead/hooks/lib/gh-auth-preflight.js exists (check-update require target)',
|
|
976
976
|
fs.existsSync(path.join(codexDir, 'skills', 'trailhead', 'hooks', 'lib', 'gh-auth-preflight.js')));
|
|
977
|
+
ok('codex: skills/trailhead/hooks/lib/trailhead-git-env.js exists (check-update require target)',
|
|
978
|
+
fs.existsSync(path.join(codexDir, 'skills', 'trailhead', 'hooks', 'lib', 'trailhead-git-env.js')));
|
|
977
979
|
ok('codex: skills/trailhead/hooks/trailhead-mockup-link-guard.js does NOT exist',
|
|
978
980
|
!fs.existsSync(path.join(codexDir, 'skills', 'trailhead', 'hooks', 'trailhead-mockup-link-guard.js')));
|
|
979
981
|
ok('codex: skills/trailhead/hooks/lib/shell-scan.js still exists (secret-read-guard require target)',
|
|
@@ -1086,6 +1088,15 @@ ok('codex migration: stale prompts/trailhead.md swept on install', !fs.existsSyn
|
|
|
1086
1088
|
ok('codex migration: stale prompts/trailhead-work.md swept on install', !fs.existsSync(path.join(migCodexDir, 'prompts', 'trailhead-work.md')));
|
|
1087
1089
|
ok('codex migration: per-verb skill projected in its place', fs.existsSync(path.join(migCodexDir, 'skills', 'trailhead-work', 'SKILL.md')));
|
|
1088
1090
|
|
|
1091
|
+
// La config globale di Codex sta nella cartella del vecchio motore: un aggiornamento non deve cancellarla.
|
|
1092
|
+
const keepCodexDir = mktmp();
|
|
1093
|
+
fs.mkdirSync(path.join(keepCodexDir, 'trailhead', 'references'), { recursive: true });
|
|
1094
|
+
fs.writeFileSync(path.join(keepCodexDir, 'trailhead', 'references', 'old.md'), 'stale engine\n');
|
|
1095
|
+
fs.writeFileSync(path.join(keepCodexDir, 'trailhead', 'config.json'), '{"testing":{"full":"npm test"}}\n');
|
|
1096
|
+
runInstaller([`--codex`, `--dir=${keepCodexDir}`]);
|
|
1097
|
+
ok('codex install: global config survives', fs.existsSync(path.join(keepCodexDir, 'trailhead', 'config.json')));
|
|
1098
|
+
ok('codex install: old engine files still swept', !fs.existsSync(path.join(keepCodexDir, 'trailhead', 'references')));
|
|
1099
|
+
|
|
1089
1100
|
// codex migration: a stale trailhead-owned skill dir (pre-split monolith /
|
|
1090
1101
|
// removed cluster) is swept on reinstall; a co-tenant skill is preserved.
|
|
1091
1102
|
const migCodexSkillDir = mktmp();
|
|
@@ -1335,13 +1346,14 @@ ok('claude: exactly one PreToolUse/Bash trailhead-bash-guards registration', (()
|
|
|
1335
1346
|
const s = JSON.parse(settingsContent);
|
|
1336
1347
|
return (s.hooks.PreToolUse || []).flatMap((g) => g.hooks || []).filter((h) => h.command.includes('trailhead-bash-guards.js')).length === 1;
|
|
1337
1348
|
})());
|
|
1338
|
-
ok('claude: hooks/trailhead-bash-guards.js and its
|
|
1349
|
+
ok('claude: hooks/trailhead-bash-guards.js and its seven modules exist', ['bash-guards', 'commit-guard', 'secret-guard', 'install-guard', 'search-guard', 'body-guard', 'labels-guard', 'test-guard']
|
|
1339
1350
|
.every((n) => fs.existsSync(path.join(claudeDir, 'hooks', `trailhead-${n}.js`))));
|
|
1340
1351
|
// mockup-link-guard: Claude-only PreToolUse(AskUserQuestion) backstop for the
|
|
1341
1352
|
// Prototype "link in the same message as the approval ask" rule.
|
|
1342
1353
|
ok('claude: hooks/trailhead-mockup-link-guard.js exists', fs.existsSync(path.join(claudeDir, 'hooks', 'trailhead-mockup-link-guard.js')));
|
|
1343
1354
|
ok('claude: hooks/lib/mockup-link.js exists (mockup-link-guard require target)', fs.existsSync(path.join(claudeDir, 'hooks', 'lib', 'mockup-link.js')));
|
|
1344
1355
|
ok('claude: hooks/lib/gh-auth-preflight.js exists (check-update require target)', fs.existsSync(path.join(claudeDir, 'hooks', 'lib', 'gh-auth-preflight.js')));
|
|
1356
|
+
ok('claude: hooks/lib/trailhead-git-env.js exists (check-update require target)', fs.existsSync(path.join(claudeDir, 'hooks', 'lib', 'trailhead-git-env.js')));
|
|
1345
1357
|
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')));
|
|
1346
1358
|
ok('claude: settings.json does NOT register mockup-link-stop', !settingsContent.includes('trailhead-mockup-link-stop.js'));
|
|
1347
1359
|
ok('claude: settings.json registers mockup-link-guard under PreToolUse/AskUserQuestion',
|
|
@@ -1732,7 +1744,7 @@ const bashGuardCommands = (registry) => (registry.hooks.PreToolUse || [])
|
|
|
1732
1744
|
ok('(#207) codex migration: exit 0', out.status === 0);
|
|
1733
1745
|
const cmds = bashGuardCommands(JSON.parse(fs.readFileSync(hooksJsonFile, 'utf8')));
|
|
1734
1746
|
ok('(#207) codex migration: only the merged entry remains', cmds.length === 1 &&
|
|
1735
|
-
cmds[0].endsWith(' --guards=commit,secret,install,body,labels'));
|
|
1747
|
+
cmds[0].endsWith(' --guards=commit,secret,install,body,labels,test'));
|
|
1736
1748
|
const once = fs.readFileSync(hooksJsonFile, 'utf8');
|
|
1737
1749
|
runInstaller([`--codex`, `--dir=${mDir}`]);
|
|
1738
1750
|
ok('(#207) codex install twice leaves the same hooks.json', fs.readFileSync(hooksJsonFile, 'utf8') === once);
|
|
@@ -2109,8 +2121,8 @@ ok('source: hooks.json keeps its command hooks next to the module (#202)',
|
|
|
2109
2121
|
|
|
2110
2122
|
// --- #259: `trailhead:paused` label marks a paused ticket (pause adds, resume/claim drop) ---
|
|
2111
2123
|
const firstUseLine = (substrateCommandsSource.match(/^1\. Create any missing labels.*$/m) || [''])[0];
|
|
2112
|
-
ok('source: first-use setup lists twenty-
|
|
2113
|
-
/all twenty-
|
|
2124
|
+
ok('source: first-use setup lists twenty-two labels including trailhead:paused, trailhead:uat, trailhead:needs-you and trailhead:needs-info',
|
|
2125
|
+
/all twenty-two:/.test(firstUseLine) && firstUseLine.includes('`trailhead:paused`') && firstUseLine.includes('`trailhead:uat`') && firstUseLine.includes('`trailhead:needs-you`') && firstUseLine.includes('`trailhead:needs-info`'));
|
|
2114
2126
|
ok('source: no stale "eighteen" or "nineteen" label count remains in the shared skills',
|
|
2115
2127
|
!/eighteen|nineteen/.test(substrateCommandsSource) && !/eighteen|nineteen/.test(substrateSource));
|
|
2116
2128
|
ok('source: substrate-commands.md ensures trailhead:paused idempotently',
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Docs test for #358: in grilling a plain "yes" always accepts the recommended answer.
|
|
3
|
+
// The rule is defined once (Grilling technique); the grill return contract states it for the agent.
|
|
4
|
+
// Run: node yes-accepts-recommended-docs.test.js
|
|
5
|
+
const assert = require('assert');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
const repo = path.join(__dirname, '..');
|
|
10
|
+
const plugin = path.join(repo, 'plugins', 'trailhead');
|
|
11
|
+
const read = (...rel) => fs.readFileSync(path.join(...rel), 'utf8');
|
|
12
|
+
const line = (text, start) => text.split('\n').find((l) => l.startsWith(start));
|
|
13
|
+
|
|
14
|
+
let passed = 0;
|
|
15
|
+
const ok = (name, cond) => { assert.ok(cond, name); passed++; };
|
|
16
|
+
|
|
17
|
+
const DEFINITION = '**A yes accepts the recommendation.**';
|
|
18
|
+
const walk = (dir) => fs.readdirSync(dir, { withFileTypes: true }).flatMap((e) =>
|
|
19
|
+
e.isDirectory() ? walk(path.join(dir, e.name)) : [path.join(dir, e.name)]);
|
|
20
|
+
|
|
21
|
+
// 1. defined exactly once, in the Grilling technique
|
|
22
|
+
const techniquePath = path.join(plugin, 'skills', '_shared', 'techniques', 'grilling.md');
|
|
23
|
+
const defs = walk(plugin).filter((f) => read(f).includes(DEFINITION));
|
|
24
|
+
ok('definition appears in exactly one file', defs.length === 1);
|
|
25
|
+
ok('definition lives in grilling.md', defs[0] === techniquePath);
|
|
26
|
+
|
|
27
|
+
// 2. the definition carries the normative fragments, on the one-question bullet
|
|
28
|
+
const technique = read(techniquePath);
|
|
29
|
+
const rule = line(technique, '- **One question at a time, with your pick.**');
|
|
30
|
+
ok('rule sits on the one-question bullet', rule.includes(DEFINITION));
|
|
31
|
+
for (const frag of [
|
|
32
|
+
'a plain "yes" accepts your pick',
|
|
33
|
+
'never an open "should we A or B?" followed by a recommendation',
|
|
34
|
+
'exactly one meaning',
|
|
35
|
+
'A play-back',
|
|
36
|
+
]) ok(`rule carries: ${frag}`, rule.includes(frag));
|
|
37
|
+
|
|
38
|
+
// 3. the relay never rewords the question out of the rule
|
|
39
|
+
const relay = line(technique, ' - `question`: relay');
|
|
40
|
+
ok('relay names the rule', relay.includes('**A yes accepts the recommendation**'));
|
|
41
|
+
|
|
42
|
+
// 4. the return contract states it, in the source block and in the synced agent
|
|
43
|
+
for (const [name, text] of [
|
|
44
|
+
['agent-blocks.md', read(repo, 'scripts', 'agent-blocks.md')],
|
|
45
|
+
['trailhead-grill.md', read(plugin, 'agents', 'trailhead-grill.md')],
|
|
46
|
+
]) {
|
|
47
|
+
const q = line(text, '- `question:` the one numbered question');
|
|
48
|
+
const r = line(text, '- `recommended:` your pick');
|
|
49
|
+
ok(`${name}: question is worded so a yes accepts the pick`, q.includes('a plain "yes" accepts `recommended:`'));
|
|
50
|
+
ok(`${name}: question bans the open form`, q.includes('never an open "should we A or B?"'));
|
|
51
|
+
ok(`${name}: recommended is what a yes accepts`, r.includes('what a plain "yes" accepts'));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.log(`yes-accepts-recommended-docs: ${passed} assertions passed`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marcomigozzi/trailhead",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.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.18.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 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.
|
|
9
9
|
|
|
10
|
-
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 returns the report (the orchestrator posts it as the `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.
|
|
10
|
+
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 returns the report (the orchestrator posts it as the `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. A pipeline-touching diff also loads `code-review-pipeline.md`, at the path the spawn names. Read the repo's standards files, at the paths the spawn names, for the Standards & spec axis, and cite the file for each standards finding.
|
|
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>`.
|
|
@@ -22,4 +22,5 @@ Never `cd` (or `pushd`/`env -C`) into a directory first; the full rule is in `_s
|
|
|
22
22
|
- trailhead-verify: `<token>` is one of `met` | `gaps_found` | `inconclusive`; each claim line carries `class: <met|unmet|partial|inconclusive>`, and `met` lists no `unmet`, `partial` or `inconclusive` claim.
|
|
23
23
|
- trailhead-code-review: `<token>` is one of `clean` | `issues_found`, and line 2 is the counts line `critical: <n> · warning: <n> · info: <n>`; `clean` with a non-zero `critical` count requires a ledger line closing every Critical (`fixed`, `deferred` or `accepted`), none `open`.
|
|
24
24
|
- Read-only: never Edit/Write, never commit.
|
|
25
|
+
- When the orchestrator passes a test record (`test record: ...`, the full-suite outcome of Verify), read it and never re-run the full suite (a focused test to probe one finding is fine); with no record passed, test as today (without `testing.full` no test record is passed).
|
|
25
26
|
<!-- /trailhead:agent-block reviewing -->
|
|
@@ -33,8 +33,8 @@ Never `cd` (or `pushd`/`env -C`) into a directory first; the full rule is in `_s
|
|
|
33
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
34
|
- `settled:` every settled choice so far, one per line by question number, with its rationale; `none` before the first answer.
|
|
35
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
|
|
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.
|
|
36
|
+
- `question:` the one numbered question to ask now, worded around your pick so a plain "yes" accepts `recommended:` and has no other meaning; never an open "should we A or B?" followed by a recommendation. Illustrate each option (its label, a one-line summary, its trade-off) so the relay can render them per **The ask is answerable from the chat alone**; never a reference to the ticket in place of the options. 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, what a plain "yes" accepts, 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
38
|
- `areas:` for a multi-part space, every functional area, each marked visited or open.
|
|
39
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
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.
|
|
@@ -26,6 +26,6 @@ Never `cd` (or `pushd`/`env -C`) into a directory first; the full rule is in `_s
|
|
|
26
26
|
- invariant: `needs_input` => `questions` non-empty.
|
|
27
27
|
- 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.
|
|
28
28
|
- With `needs_input`, fields are the end of the report: nothing after the last field.
|
|
29
|
-
- `ready`: the rest of the report is the PLAN (the `body`): the steps, the seams, the files touched and the verification criteria. It is posted verbatim, so write nothing in it that is not the PLAN.
|
|
29
|
+
- `ready`: the rest of the report is the PLAN (the `body`): the steps, the seams (one line each, `<seam>: catches <what a test here fails on>; misses <what gets through it>`), the files touched and the verification criteria. It is posted verbatim, so write nothing in it that is not the PLAN.
|
|
30
30
|
- `needs_input`: `questions:` holds the blocking questions only; no PLAN.
|
|
31
31
|
<!-- /trailhead:agent-block plan -->
|
|
@@ -22,4 +22,5 @@ Never `cd` (or `pushd`/`env -C`) into a directory first; the full rule is in `_s
|
|
|
22
22
|
- trailhead-verify: `<token>` is one of `met` | `gaps_found` | `inconclusive`; each claim line carries `class: <met|unmet|partial|inconclusive>`, and `met` lists no `unmet`, `partial` or `inconclusive` claim.
|
|
23
23
|
- trailhead-code-review: `<token>` is one of `clean` | `issues_found`, and line 2 is the counts line `critical: <n> · warning: <n> · info: <n>`; `clean` with a non-zero `critical` count requires a ledger line closing every Critical (`fixed`, `deferred` or `accepted`), none `open`.
|
|
24
24
|
- Read-only: never Edit/Write, never commit.
|
|
25
|
+
- When the orchestrator passes a test record (`test record: ...`, the full-suite outcome of Verify), read it and never re-run the full suite (a focused test to probe one finding is fine); with no record passed, test as today (without `testing.full` no test record is passed).
|
|
25
26
|
<!-- /trailhead:agent-block reviewing -->
|