@marcomigozzi/trailhead 0.16.1 โ 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 +82 -25
- package/README.md +82 -25
- package/bin/base-preflight-docs.test.js +4 -1
- package/bin/bulk-cross-reference-docs.test.js +36 -0
- package/bin/capture-action.test.js +167 -0
- package/bin/capture-docs.test.js +32 -0
- package/bin/engine-route-docs.test.js +155 -0
- package/bin/facet-stamp-docs.test.js +79 -0
- package/bin/forced-mutation-docs.test.js +38 -0
- package/bin/grill-model-docs.test.js +155 -0
- package/bin/grilling-holistic-review-docs.test.js +7 -3
- 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 +28 -19
- package/bin/lib/codex-projection.test.js +77 -20
- package/bin/lib/host-descriptor.js +1 -1
- package/bin/lib/host-descriptor.test.js +6 -0
- 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/report-status-docs.test.js +10 -2
- package/bin/seam-coverage-docs.test.js +46 -0
- package/bin/session-trust-docs.test.js +37 -0
- package/bin/standards-brief-docs.test.js +112 -0
- package/bin/surfaced-scope-docs.test.js +57 -0
- package/bin/test-once-docs.test.js +101 -0
- package/bin/test-run-docs.test.js +47 -0
- package/bin/trailhead.js +15 -8
- package/bin/trailhead.test.js +71 -17
- package/bin/unblock-docs.test.js +56 -0
- 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-codebase-map.md +1 -1
- package/plugins/trailhead/agents/trailhead-grill.md +41 -0
- 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/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/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/inherit-session.js +17 -0
- package/plugins/trailhead/hooks/lib/issue-body.test.js +165 -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/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/report-status.test.js +56 -1
- 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 +42 -17
- 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 +204 -49
- package/plugins/trailhead/hooks/mod/my-tickets.ts +76 -0
- package/plugins/trailhead/hooks/mod/repo-pin.ts +113 -9
- 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 +20 -2
- package/plugins/trailhead/hooks/trailhead-check-update.test.js +60 -0
- 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 +123 -0
- package/plugins/trailhead/skills/_shared/choices.md +2 -2
- package/plugins/trailhead/skills/_shared/configuration-reference.md +30 -16
- package/plugins/trailhead/skills/_shared/configuration.md +1 -1
- package/plugins/trailhead/skills/_shared/load-first.md +2 -0
- package/plugins/trailhead/skills/_shared/session-handoff.md +1 -1
- package/plugins/trailhead/skills/_shared/substrate-commands.md +19 -6
- package/plugins/trailhead/skills/_shared/substrate.md +4 -3
- package/plugins/trailhead/skills/_shared/surfaced-scope.md +23 -0
- package/plugins/trailhead/skills/_shared/teamwork.md +11 -5
- package/plugins/trailhead/skills/_shared/techniques/acceptance-testing.md +1 -1
- package/plugins/trailhead/skills/_shared/techniques/code-review-pipeline.md +24 -0
- package/plugins/trailhead/skills/_shared/techniques/code-review.md +22 -6
- package/plugins/trailhead/skills/_shared/techniques/codebase-map.md +6 -3
- package/plugins/trailhead/skills/_shared/techniques/debug.md +5 -2
- package/plugins/trailhead/skills/_shared/techniques/grilling.md +28 -3
- package/plugins/trailhead/skills/_shared/techniques/plan-review.md +16 -4
- package/plugins/trailhead/skills/_shared/techniques/research.md +4 -1
- package/plugins/trailhead/skills/_shared/techniques/tdd.md +4 -2
- package/plugins/trailhead/skills/_shared/techniques/verify.md +5 -1
- package/plugins/trailhead/skills/_shared/techniques.md +8 -6
- 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 +2 -2
- package/plugins/trailhead/skills/trailhead-chart/references/charting.md +5 -5
- package/plugins/trailhead/skills/trailhead-chart/references/inbox.md +11 -6
- package/plugins/trailhead/skills/trailhead-manage/references/auditing.md +18 -3
- package/plugins/trailhead/skills/trailhead-work/SKILL.md +12 -9
- package/plugins/trailhead/skills/trailhead-work/references/auto.md +3 -1
- package/plugins/trailhead/skills/trailhead-work/references/executor-brief.md +2 -0
- package/plugins/trailhead/skills/trailhead-work/references/ticket-engines.md +13 -13
- 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 +419 -0
- package/plugins/trailhead/templates/trailhead-checkout-find.js +257 -0
- package/plugins/trailhead/templates/trailhead-checkout-in.js +78 -21
- package/plugins/trailhead/templates/trailhead-commit-msg-sync.js +2 -1
- package/plugins/trailhead/templates/trailhead-dashboard.js +15 -2
- 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 +161 -12
- 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-report-status.js +57 -9
- 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 +386 -50
- 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/my-tickets.test.ts +66 -0
- 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/repo-pin.test.ts +20 -5
- 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
- package/plugins/trailhead/types/index.d.ts +7 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Docs test for #353: plan review skips a reviewer whose prompt exceeds its context.
|
|
3
|
+
// Plain asserts, no framework. Run: node bin/plan-review-budget-docs.test.js
|
|
4
|
+
const assert = require('assert');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
const plugin = path.resolve(__dirname, '..', 'plugins', 'trailhead');
|
|
9
|
+
const read = (rel) => fs.readFileSync(path.join(plugin, rel), 'utf8');
|
|
10
|
+
let passed = 0;
|
|
11
|
+
const ok = (name, cond) => { assert.ok(cond, name); passed++; };
|
|
12
|
+
|
|
13
|
+
const review = read('skills/_shared/techniques/plan-review.md');
|
|
14
|
+
const config = read('skills/_shared/configuration-reference.md');
|
|
15
|
+
|
|
16
|
+
ok('plan-review.md: runs the budget call before each reviewer call', /trailhead-plan-review-rounds\.js budget --repo[^\n]*--reviewer[^\n]*--prompt/.test(review));
|
|
17
|
+
ok('plan-review.md: names the three decisions arg, file, skip', /`arg`:[\s\S]*`file`:[\s\S]*`skip`:/.test(review));
|
|
18
|
+
ok('plan-review.md: file passes the pointer, never the brief as one argument', /`file`:[^\n]*`pointer`[^\n]*never the brief itself as one argument/.test(review));
|
|
19
|
+
ok('plan-review.md: notes the E2BIG limit of a single argument', /128 KiB[^\n]*E2BIG/.test(review));
|
|
20
|
+
ok('plan-review.md: skip does not invoke the reviewer and records the reason', /`skip`:\s*do not invoke[^\n]*`reason`/.test(review));
|
|
21
|
+
ok('plan-review.md: captures stdout and stderr together into one output file', /> <dir>\/<cli>\.out 2>&1/.test(review));
|
|
22
|
+
ok('plan-review.md: a shell-tool launch failure is written into the same file', /shell tool itself fails to launch[^\n]*same file/.test(review));
|
|
23
|
+
ok('plan-review.md: runs the overflow call on a failed call', /trailhead-plan-review-rounds\.js overflow --output/.test(review));
|
|
24
|
+
ok('plan-review.md: overflow true means no retry, recorded as skipped with its reason', /`overflow: true`[^\n]*do not retry[^\n]*skipped[^\n]*`reason`/.test(review));
|
|
25
|
+
ok('plan-review.md: the one-retry rule never applies after an overflow', /One retry \(never after a context overflow\)/.test(review));
|
|
26
|
+
ok('plan-review.md: PLAN-REVIEW lists every skipped or dropped reviewer with its reason, never silent', /`PLAN-REVIEW`[^\n]*every reviewer skipped or dropped, with its reason[^\n]*budget skip[^\n]*context overflow[^\n]*second timeout[^\n]*hard error[^\n]*never a silent drop/.test(review));
|
|
27
|
+
ok('plan-review.md: all reviewers skipped falls back to the no-reviewer rule', /every reviewer ends up skipped[^\n]*no-reviewer-left rule/.test(review));
|
|
28
|
+
|
|
29
|
+
ok('configuration-reference.md: has the plan_review.context row', /^\| `plan_review\.context` \|/m.test(config));
|
|
30
|
+
ok('configuration-reference.md: explains the 65% rule, the estimate and the post-check', /\*\*plan_review\.context\.\*\*[^\n]*ceil\(bytes \/ 3\)[^\n]*65%[^\n]*not a guarantee[^\n]*not retried/.test(config));
|
|
31
|
+
ok('configuration-reference.md: documents the per-reviewer merge', /\*\*plan_review\.context\.\*\*[^\n]*merge per reviewer[^\n]*project wins/.test(config));
|
|
32
|
+
|
|
33
|
+
ok('plan-review.md: the overflow call passes the brief so echoed lines are ignored', /overflow --output <dir>\/<cli>\.out --prompt <brief file>/.test(review));
|
|
34
|
+
ok('plan-review.md: a failing budget or overflow helper has a stated fallback', /When `budget` or `overflow` itself fails[^\n]*never guess[^\n]*at most 100 KiB/.test(review));
|
|
35
|
+
|
|
36
|
+
// #368: every reviewer call is bounded by the run helper.
|
|
37
|
+
ok('plan-review.md: every reviewer call goes through the run subcommand, whatever the launch mode', /Every reviewer call goes through the `run` subcommand, whatever the launch mode[^\n]*trailhead-plan-review-rounds\.js run --output <dir>\/<cli>\.out -- /.test(review));
|
|
38
|
+
ok('plan-review.md: the helper bounds the call because the Bash timeout does not apply to a background call', /helper is what bounds[^\n]*`timeout` parameter does not apply to a background call/.test(review));
|
|
39
|
+
ok('plan-review.md: a timeout skips overflow and takes the one-retry rule', /- `timeout`:[^\n]*\*\*Do not run `overflow`\*\*[^\n]*one-retry rule/.test(review));
|
|
40
|
+
ok('plan-review.md: a second timeout drops the reviewer, recorded with the seconds', /A second timeout \*\*drops\*\*[^\n]*`second timeout`[^\n]*seconds/.test(review));
|
|
41
|
+
ok('plan-review.md: failed or error runs overflow then the one-retry rule', /- `failed` or `error`: run `node <templates>\/trailhead-plan-review-rounds\.js overflow/.test(review));
|
|
42
|
+
ok('plan-review.md: the helper-fails fallback names no JSON line and excludes a status error line', /helper is missing, exits non-zero with no JSON line \(a helper killed by a signal prints none\), or prints non-JSON output\*\*[^\n]*`status: error` JSON line is not this case[^\n]*`failed`\/`error` branch/.test(review));
|
|
43
|
+
ok('plan-review.md: no human prompt on this path, never reported as still running past the bound', /No human prompt anywhere on this path[^\n]*never reported as "still running" past the bound/.test(review));
|
|
44
|
+
ok('plan-review.md: a missing run helper falls back to timeout -k 10 600, 124 or 137 meaning timeout', /helper is missing, exits non-zero[^\n]*`timeout -k 10 600 <cmd> < \/dev\/null > <dir>\/<cli>\.out 2>&1`[^\n]*`gtimeout`[^\n]*124 or 137/.test(review));
|
|
45
|
+
ok('plan-review.md: with no bound available the reviewer is skipped, recorded', /neither available, skip the reviewer, recording that no bound was available/.test(review));
|
|
46
|
+
ok('plan-review.md: the stdin line is not evidence of a stdin hang', /`Reading additional input from stdin\.\.\.` is \*\*not evidence of a stdin hang\*\*/.test(review));
|
|
47
|
+
ok('plan-review.md: the synchronous case still goes through run with the tool timeout above the helper deadline', /still goes through `run`[^\n]*`600000`[^\n]*above the helper's deadline/.test(review));
|
|
48
|
+
ok('plan-review.md: the raw-shell 0% CPU stuck check is gone', !/0% CPU/.test(review));
|
|
49
|
+
const techniques = read('skills/_shared/techniques.md');
|
|
50
|
+
ok('techniques.md: the outcome is awaited up to the bound, a stalled plan reviewer ends as a recorded drop', /the outcome always awaited \(up to its bound[^\n]*stalled reviewer ends as a dropped reviewer, recorded in `PLAN-REVIEW`, never an open wait/.test(techniques));
|
|
51
|
+
|
|
52
|
+
const root = path.resolve(__dirname, '..');
|
|
53
|
+
const readme = fs.readFileSync(path.join(root, 'README.md'), 'utf8');
|
|
54
|
+
const readmeIt = fs.readFileSync(path.join(root, 'README.it.md'), 'utf8');
|
|
55
|
+
const site = fs.readFileSync(path.join(root, 'site', 'src', 'content', 'docs', 'docs', 'configuration.mdx'), 'utf8');
|
|
56
|
+
for (const [name, text] of [['README.md', readme], ['README.it.md', readmeIt], ['configuration.mdx', site]]) {
|
|
57
|
+
ok(`${name}: has the plan_review.context row`, /^\| `plan_review\.context` \|/m.test(text));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
for (const [name, text] of [['plan-review.md', review], ['configuration-reference.md', config], ['techniques.md', techniques]]) {
|
|
61
|
+
ok(`${name}: no em-dash`, !text.includes(String.fromCharCode(0x2014)));
|
|
62
|
+
}
|
|
63
|
+
console.log(`plan-review-budget-docs.test.js: ${passed} checks passed`);
|
|
@@ -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`);
|
|
@@ -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,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,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,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,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`);
|
|
@@ -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
|
@@ -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
|
}
|
|
@@ -246,7 +248,7 @@ function claudePaths(configDir) {
|
|
|
246
248
|
// The six Bash guard modules. They are shipped, but only require()d by the
|
|
247
249
|
// merged trailhead-bash-guards.js (one process per Bash call), never registered
|
|
248
250
|
// on their own. Legacy installs registered each one; the installers strip those.
|
|
249
|
-
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'];
|
|
250
252
|
// Every hook script copied into the hooks dir (shipped runtime files).
|
|
251
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'];
|
|
252
254
|
// The registered entrypoints: the shipped files that must appear in
|
|
@@ -260,10 +262,12 @@ const ENTRYPOINT_FILES = HOOK_FILES.filter((f) => !BASH_GUARD_MODULES.includes(f
|
|
|
260
262
|
// also does require('./lib/body-sources.js'), #210; trailhead-check-update.js
|
|
261
263
|
// does require('./lib/model-defaults-review.js'), which in turn reads the
|
|
262
264
|
// sibling model-defaults.json, #186; both mockup-link hooks do
|
|
263
|
-
// 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
|
|
264
268
|
// sweep: the Claude hooks/lib dir is shared with other plugins, so we must
|
|
265
269
|
// 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'];
|
|
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'];
|
|
267
271
|
// search-guard is Claude-Code-specific (#169), and so is mockup-link-guard
|
|
268
272
|
// (it gates the AskUserQuestion tool, which Codex lacks): excluded from both
|
|
269
273
|
// the Codex hook copy and the Codex verify spec's expected hookFiles, so a
|
|
@@ -613,7 +617,10 @@ function installCodex(configDir, { useSymlink }) {
|
|
|
613
617
|
for (const f of (fs.existsSync(L.promptsDir) ? fs.readdirSync(L.promptsDir) : [])) {
|
|
614
618
|
if (f.startsWith('trailhead-')) rmrf(path.join(L.promptsDir, f));
|
|
615
619
|
}
|
|
616
|
-
|
|
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
|
+
}
|
|
617
624
|
|
|
618
625
|
// Sweep the whole engine + any prior per-verb skills, then project fresh.
|
|
619
626
|
sweepTrailheadSkills(L.skillsRoot);
|
|
@@ -686,12 +693,12 @@ function installCodex(configDir, { useSymlink }) {
|
|
|
686
693
|
featureManual = true;
|
|
687
694
|
}
|
|
688
695
|
|
|
689
|
-
// Per-technique agent registry: project ALL
|
|
696
|
+
// Per-technique agent registry: project ALL 9 committed agents
|
|
690
697
|
// (readAgentDefs, sourced from plugins/trailhead/agents/*.md) as
|
|
691
698
|
// trailhead-<technique>.toml under the real ~/.codex/agents/ dir, uniformly.
|
|
692
|
-
// A keyed technique (plan/execute/research/review/debug/verify/codebase-map)
|
|
699
|
+
// A keyed technique (plan/execute/research/review/debug/verify/codebase-map/grill)
|
|
693
700
|
// carries its models.codex.<key> pin (merged project + global config) when
|
|
694
|
-
// set, else is pin-less;
|
|
701
|
+
// set, else is pin-less; fix tracks the models.codex.execute pin.
|
|
695
702
|
// Sweep only trailhead's own files first, so a stale pin from a prior
|
|
696
703
|
// install (or a removed technique) never lingers, without touching the
|
|
697
704
|
// user's other custom-agent TOMLs.
|