@marcomigozzi/trailhead 0.14.0 → 0.16.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.
Files changed (156) hide show
  1. package/README.it.md +158 -34
  2. package/README.md +147 -33
  3. package/bin/auto-intro-docs.test.js +72 -0
  4. package/bin/auto-merge-docs.test.js +33 -0
  5. package/bin/auto-merge-ticket-docs.test.js +32 -0
  6. package/bin/auto-review-rounds-docs.test.js +45 -0
  7. package/bin/auto-run-docs.test.js +60 -0
  8. package/bin/auto-scope-docs.test.js +33 -0
  9. package/bin/auto-slots-docs.test.js +45 -0
  10. package/bin/auto-uat-docs.test.js +47 -0
  11. package/bin/base-preflight-docs.test.js +65 -0
  12. package/bin/execute-split-docs.test.js +146 -0
  13. package/bin/grilling-holistic-review-docs.test.js +90 -0
  14. package/bin/isolation-answer-docs.test.js +41 -0
  15. package/bin/isolation-copy-docs.test.js +28 -0
  16. package/bin/lib/codex-projection.js +6 -6
  17. package/bin/lib/codex-projection.test.js +43 -2
  18. package/bin/needs-you-docs.test.js +41 -0
  19. package/bin/pick-stress-test-docs.test.js +105 -0
  20. package/bin/report-status-docs.test.js +195 -0
  21. package/bin/resolve-helpers-docs.test.js +36 -0
  22. package/bin/scope-confirm-skip-docs.test.js +70 -0
  23. package/bin/trailhead.js +6 -6
  24. package/bin/trailhead.test.js +355 -16
  25. package/bin/uat-setup-docs.test.js +65 -0
  26. package/package.json +1 -1
  27. package/plugins/trailhead/.claude-plugin/plugin.json +1 -1
  28. package/plugins/trailhead/agents/trailhead-code-review.md +15 -2
  29. package/plugins/trailhead/agents/trailhead-codebase-map.md +20 -1
  30. package/plugins/trailhead/agents/trailhead-debug.md +20 -1
  31. package/plugins/trailhead/agents/trailhead-executor.md +29 -2
  32. package/plugins/trailhead/agents/trailhead-fix.md +29 -2
  33. package/plugins/trailhead/agents/trailhead-plan.md +20 -1
  34. package/plugins/trailhead/agents/trailhead-research.md +22 -3
  35. package/plugins/trailhead/agents/trailhead-verify.md +15 -2
  36. package/plugins/trailhead/commands/auto.md +2 -2
  37. package/plugins/trailhead/hooks/lib/auto-merge.test.js +467 -0
  38. package/plugins/trailhead/hooks/lib/auto-queue.test.js +245 -0
  39. package/plugins/trailhead/hooks/lib/auto-run.test.js +318 -0
  40. package/plugins/trailhead/hooks/lib/auto-slot.test.js +278 -0
  41. package/plugins/trailhead/hooks/lib/base-preflight.test.js +513 -0
  42. package/plugins/trailhead/hooks/lib/checkout-in.test.js +173 -0
  43. package/plugins/trailhead/hooks/lib/commit-message-check.js +1 -1
  44. package/plugins/trailhead/hooks/lib/commit-message-check.test.js +3 -2
  45. package/plugins/trailhead/hooks/lib/commit-msg-module-repo.test.js +116 -0
  46. package/plugins/trailhead/hooks/lib/commit-msg-sync.test.js +100 -9
  47. package/plugins/trailhead/hooks/lib/dashboard.test.js +472 -0
  48. package/plugins/trailhead/hooks/lib/fixtures/commit-msg-legacy-js +104 -0
  49. package/plugins/trailhead/hooks/lib/isolation-answer.test.js +258 -0
  50. package/plugins/trailhead/hooks/lib/isolation-copy.test.js +298 -0
  51. package/plugins/trailhead/hooks/lib/issue-body.test.js +470 -0
  52. package/plugins/trailhead/hooks/lib/merge-ticket.test.js +394 -0
  53. package/plugins/trailhead/hooks/lib/needs-you.test.js +177 -0
  54. package/plugins/trailhead/hooks/lib/plan-review-rounds.test.js +319 -0
  55. package/plugins/trailhead/hooks/lib/report-status.test.js +366 -0
  56. package/plugins/trailhead/hooks/lib/shell-scan.js +36 -1
  57. package/plugins/trailhead/hooks/lib/shell-scan.test.js +14 -0
  58. package/plugins/trailhead/hooks/lib/statusline-context.test.js +140 -0
  59. package/plugins/trailhead/hooks/lib/uat-backfill.test.js +150 -0
  60. package/plugins/trailhead/hooks/lib/uat-followup.test.js +169 -0
  61. package/plugins/trailhead/hooks/lib/uat-setup.test.js +340 -0
  62. package/plugins/trailhead/hooks/lib/unblock.test.js +207 -0
  63. package/plugins/trailhead/hooks/lib/work-state.test.js +45 -0
  64. package/plugins/trailhead/hooks/mod/README.md +209 -36
  65. package/plugins/trailhead/hooks/mod/auto-run-view.ts +93 -0
  66. package/plugins/trailhead/hooks/mod/auto-run.ts +165 -0
  67. package/plugins/trailhead/hooks/mod/map-pane-view.ts +304 -26
  68. package/plugins/trailhead/hooks/mod/map-pane.tsx +763 -160
  69. package/plugins/trailhead/hooks/mod/register.tsx +312 -40
  70. package/plugins/trailhead/hooks/mod/repo-pin.ts +167 -0
  71. package/plugins/trailhead/hooks/mod/session-registry.ts +65 -0
  72. package/plugins/trailhead/hooks/mod/turn-state.ts +14 -0
  73. package/plugins/trailhead/hooks/mod/whiteboard-follow.ts +98 -0
  74. package/plugins/trailhead/hooks/mod/work-state-locate.ts +191 -0
  75. package/plugins/trailhead/hooks/mod/work-state-prompt.ts +13 -15
  76. package/plugins/trailhead/hooks/mod/work-state-view.ts +38 -5
  77. package/plugins/trailhead/hooks/mod/worktree-porcelain.ts +17 -0
  78. package/plugins/trailhead/hooks/trailhead-bash-guards.js +8 -8
  79. package/plugins/trailhead/hooks/trailhead-bash-guards.test.js +22 -16
  80. package/plugins/trailhead/hooks/trailhead-install-guard.js +169 -34
  81. package/plugins/trailhead/hooks/trailhead-install-guard.test.js +180 -1
  82. package/plugins/trailhead/hooks/trailhead-labels-guard.js +231 -0
  83. package/plugins/trailhead/hooks/trailhead-labels-guard.test.js +212 -0
  84. package/plugins/trailhead/hooks/trailhead-search-guard.js +43 -7
  85. package/plugins/trailhead/hooks/trailhead-search-guard.test.js +66 -2
  86. package/plugins/trailhead/hooks/trailhead-secret-read-guard.js +296 -22
  87. package/plugins/trailhead/hooks/trailhead-secret-read-guard.test.js +253 -0
  88. package/plugins/trailhead/skills/_shared/choices.md +11 -4
  89. package/plugins/trailhead/skills/_shared/configuration-reference.md +17 -5
  90. package/plugins/trailhead/skills/_shared/load-first.md +3 -3
  91. package/plugins/trailhead/skills/_shared/multi-map.md +2 -2
  92. package/plugins/trailhead/skills/_shared/session-handoff.md +1 -1
  93. package/plugins/trailhead/skills/_shared/substrate-commands.md +47 -14
  94. package/plugins/trailhead/skills/_shared/substrate.md +9 -4
  95. package/plugins/trailhead/skills/_shared/teamwork.md +17 -8
  96. package/plugins/trailhead/skills/_shared/techniques/acceptance-testing.md +18 -10
  97. package/plugins/trailhead/skills/_shared/techniques/code-review.md +1 -1
  98. package/plugins/trailhead/skills/_shared/techniques/codebase-map.md +1 -1
  99. package/plugins/trailhead/skills/_shared/techniques/debug.md +2 -0
  100. package/plugins/trailhead/skills/_shared/techniques/grilling.md +4 -2
  101. package/plugins/trailhead/skills/_shared/techniques/plan-review.md +3 -1
  102. package/plugins/trailhead/skills/_shared/techniques/research.md +4 -2
  103. package/plugins/trailhead/skills/_shared/techniques/verify.md +1 -1
  104. package/plugins/trailhead/skills/_shared/techniques.md +12 -2
  105. package/plugins/trailhead/skills/trailhead/SKILL.md +1 -1
  106. package/plugins/trailhead/skills/trailhead-capture/SKILL.md +1 -1
  107. package/plugins/trailhead/skills/trailhead-capture/references/capture.md +3 -3
  108. package/plugins/trailhead/skills/trailhead-chart/SKILL.md +2 -2
  109. package/plugins/trailhead/skills/trailhead-chart/references/charting.md +5 -5
  110. package/plugins/trailhead/skills/trailhead-chart/references/inbox.md +2 -2
  111. package/plugins/trailhead/skills/trailhead-manage/SKILL.md +1 -1
  112. package/plugins/trailhead/skills/trailhead-manage/references/auditing.md +1 -1
  113. package/plugins/trailhead/skills/trailhead-view/SKILL.md +9 -6
  114. package/plugins/trailhead/skills/trailhead-work/SKILL.md +31 -18
  115. package/plugins/trailhead/skills/trailhead-work/references/auto.md +70 -22
  116. package/plugins/trailhead/skills/trailhead-work/references/executor-brief.md +29 -0
  117. package/plugins/trailhead/skills/trailhead-work/references/ticket-engines.md +29 -13
  118. package/plugins/trailhead/templates/statusline-trailhead.sh +77 -12
  119. package/plugins/trailhead/templates/trailhead-auto-merge.js +318 -0
  120. package/plugins/trailhead/templates/trailhead-auto-queue.js +186 -0
  121. package/plugins/trailhead/templates/trailhead-auto-run.js +294 -0
  122. package/plugins/trailhead/templates/trailhead-auto-slot.js +239 -0
  123. package/plugins/trailhead/templates/trailhead-base-preflight.js +305 -0
  124. package/plugins/trailhead/templates/trailhead-checkout-in.js +88 -0
  125. package/plugins/trailhead/templates/trailhead-commit-msg +38 -102
  126. package/plugins/trailhead/templates/trailhead-commit-msg-sync.js +58 -27
  127. package/plugins/trailhead/templates/trailhead-commit-msg.cjs +114 -0
  128. package/plugins/trailhead/templates/trailhead-dashboard.js +360 -0
  129. package/plugins/trailhead/templates/trailhead-isolation-answer.js +227 -0
  130. package/plugins/trailhead/templates/trailhead-isolation-copy.js +239 -0
  131. package/plugins/trailhead/templates/trailhead-issue-body.js +459 -0
  132. package/plugins/trailhead/templates/trailhead-merge-ticket.js +366 -0
  133. package/plugins/trailhead/templates/trailhead-needs-you.js +193 -0
  134. package/plugins/trailhead/templates/trailhead-plan-review-rounds.js +289 -0
  135. package/plugins/trailhead/templates/trailhead-report-status.js +274 -0
  136. package/plugins/trailhead/templates/trailhead-uat-backfill.js +114 -0
  137. package/plugins/trailhead/templates/trailhead-uat-followup.js +128 -0
  138. package/plugins/trailhead/templates/trailhead-uat-setup.js +385 -0
  139. package/plugins/trailhead/templates/trailhead-unblock.js +111 -0
  140. package/plugins/trailhead/templates/trailhead-work-state.js +25 -7
  141. package/plugins/trailhead/tests/auto-run-ui.test.ts +444 -0
  142. package/plugins/trailhead/tests/auto-run-view.test.ts +111 -0
  143. package/plugins/trailhead/tests/map-pane-ui.test.ts +2519 -382
  144. package/plugins/trailhead/tests/map-pane-view.test.ts +600 -18
  145. package/plugins/trailhead/tests/map-pane-write.test.ts +51 -0
  146. package/plugins/trailhead/tests/repo-pin-late-repo.test.ts +77 -0
  147. package/plugins/trailhead/tests/repo-pin.test.ts +123 -0
  148. package/plugins/trailhead/tests/session-registry.test.ts +50 -0
  149. package/plugins/trailhead/tests/whiteboard-follow.test.ts +90 -0
  150. package/plugins/trailhead/tests/work-state-locate.test.ts +33 -0
  151. package/plugins/trailhead/tests/work-state-prompt-hook.test.ts +276 -2
  152. package/plugins/trailhead/tests/work-state-prompt.test.ts +29 -35
  153. package/plugins/trailhead/tests/work-state-ui.test.ts +73 -1
  154. package/plugins/trailhead/tests/work-state-view.test.ts +44 -1
  155. package/plugins/trailhead/tests/work-state-worktree.test.ts +310 -0
  156. package/plugins/trailhead/types/index.d.ts +62 -3
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+ // Docs test for #284: the autonomous run asks the external review rounds at start-up and
3
+ // parks a ticket whose plan review does not converge. Plain asserts, no framework.
4
+ // Run: node bin/auto-review-rounds-docs.test.js
5
+ const assert = require('assert');
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ const plugin = path.resolve(__dirname, '..', 'plugins', 'trailhead');
10
+ const read = (rel) => fs.readFileSync(path.join(plugin, rel), 'utf8');
11
+ let passed = 0;
12
+ const ok = (name, cond) => { assert.ok(cond, name); passed++; };
13
+
14
+ const auto = read('skills/trailhead-work/references/auto.md');
15
+ const review = read('skills/_shared/techniques/plan-review.md');
16
+ const config = read('skills/_shared/configuration-reference.md');
17
+
18
+ ok('auto.md has the External review rounds bullet', auto.includes('**External review rounds.**'));
19
+ ok('auto.md names the ask call', auto.includes('trailhead-plan-review-rounds.js ask'));
20
+ ok('auto.md: skip when plan review is off or no independent reviewer CLI is installed', /`skip`[^.]*plan review off[^.]*no independent reviewer CLI/.test(auto));
21
+ ok('auto.md: one AskUserQuestion with the helper options, at most four, config Recommended first', /one `AskUserQuestion`[^.]*`options`[^.]*at most four[^.]*Recommended/.test(auto));
22
+ ok('auto.md names the five choices', /as in config, 1, 2, 3, or until convergence/.test(auto));
23
+ ok('auto.md: until convergence stops on no progress, hard ceiling 5', /no progress[^.]*hard ceiling 5/.test(auto));
24
+ ok('auto.md: numbered list of all five without a structured question tool', /full five as a numbered list/.test(auto));
25
+ ok('auto.md: an ask error hands back before any claim', /`error`[^.]*reports it and hands back before any claim/.test(auto));
26
+ ok('auto.md: the ask is the human\'s, no delegate, also under <n>', /no delegate[^.]*before any claim/.test(auto) && /also under `\/trailhead:auto <n>`/.test(auto));
27
+ ok('auto.md: the choice holds for every build ticket, each slot included', /holds for every `build` ticket of the run[^.]*slot/.test(auto));
28
+ ok('auto.md: the wired-separately aside is gone', !/wired separately/.test(auto));
29
+ ok('auto.md: names the parking trigger statuses', /`exhausted`[^.]*`no-progress`[^.]*`ceiling`/.test(auto));
30
+ ok('auto.md: parks after PLAN and PLAN-REVIEW are posted, quoting each concern\'s full text', /after the `PLAN` and `PLAN-REVIEW` comments are posted/.test(auto) && /full `text`[^.]*`surviving`[^.]*never just (its|the) key/.test(auto));
31
+ ok('auto.md: names the round call', auto.includes('trailhead-plan-review-rounds.js round'));
32
+
33
+ ok('auto.md: the ask helper warnings are printed in chat before the ask or skip', /non-empty `warnings`[\s\S]{0,200}printed in chat first, before the ask or the skip/.test(auto));
34
+ ok('plan-review.md: rounds come from config or the run start-up choice', /`config\.plan_review\.rounds`[^.]*autonomous run[^.]*start-up choice/.test(review));
35
+ ok('plan-review.md: calls the round helper and follows its decision', review.includes('trailhead-plan-review-rounds.js round') && /follows its decision/.test(review));
36
+ ok('plan-review.md: under an autonomous run the checkpoint is not posed, the ticket is parked', /autonomous run[^.]*checkpoint is not posed[\s\S]{0,400}trailhead-needs-you\.js park/.test(review));
37
+ ok('plan-review.md: the park reason lists the surviving blocking concerns', /reason listing the surviving blocking concerns/.test(review));
38
+ ok('plan-review.md: interactive behaviour unchanged', /interactive[^.]*unchanged/i.test(review));
39
+
40
+ ok('configuration-reference.md: plan_review.rounds notes the run ask override', /plan_review\.rounds[^\n]*autonomous run asks at start[^\n]*overrides the key for that run/.test(config));
41
+
42
+ for (const [name, text] of [['auto.md', auto], ['plan-review.md', review], ['configuration-reference.md', config]]) {
43
+ ok(`${name}: no em-dash`, !text.includes(String.fromCharCode(0x2014)));
44
+ }
45
+ console.log(`auto-review-rounds-docs.test.js: ${passed} checks passed`);
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env node
2
+ // Docs test for #295: the autonomous run persists its start-up choices and lists in
3
+ // .trailhead/auto-run.json so it survives a compaction. Plain asserts, no framework.
4
+ // Run: node bin/auto-run-docs.test.js
5
+ const assert = require('assert');
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ const plugin = path.resolve(__dirname, '..', 'plugins', 'trailhead');
10
+ const auto = fs.readFileSync(path.join(plugin, 'skills/trailhead-work/references/auto.md'), 'utf8');
11
+ const gitignore = fs.readFileSync(path.resolve(__dirname, '..', '.gitignore'), 'utf8');
12
+ let passed = 0;
13
+ const ok = (name, cond) => { assert.ok(cond, name); passed++; };
14
+
15
+ ok('auto.md: read runs at engagement, before the asks', /trailhead-auto-run\.js read --repo <main-checkout>[^\n]*before (any|the) ask/i.test(auto));
16
+ ok('auto.md: found with sameSession true is reused without asking and announced', /`sameSession: true`[^\n]*reuse it without asking/.test(auto) && /announced in one chat line naming the reused scope, slots and review/.test(auto));
17
+ ok('auto.md: another session\'s record asks once to resume or start fresh', /`sameSession: false`[^\n]*run still active elsewhere[^\n]*ask once/.test(auto) && /no delegate option/.test(auto) && /replaces the three asks/.test(auto));
18
+ ok('auto.md: resume reloads the skip and unmerged lists', /reload the record's skip and unmerged lists/.test(auto));
19
+ ok('auto.md: an explicit <n> that differs starts fresh with start --replace', /explicit `\/trailhead:auto <n>`[^\n]*differs[^\n]*starts a fresh run[^\n]*start --replace/.test(auto));
20
+ ok('auto.md: corrupt is reported then start --replace', /`corrupt`[^\n]*start --replace[^\n]*plain `start` would answer `exists`/.test(auto));
21
+ ok('auto.md: start runs after the last ask and before anything is claimed', /trailhead-auto-run\.js start --repo <main-checkout>[^\n]*--scope[^\n]*--slots[^\n]*--review/.test(auto) && /right after the last start-up ask and before anything is claimed/.test(auto));
22
+ ok('auto.md: the record is gitignored', auto.includes('.trailhead/auto-run.json') && /\.gitignore/.test(auto) && /^\.trailhead\/auto-run\.json$/m.test(gitignore));
23
+ ok('auto.md: every skipped outcome is reported and never blocks', /Any `skipped` result[^\n]*one chat line[^\n]*the run continues/.test(auto) && /skipped `read` is treated as `absent`/.test(auto) && /safety net, not a gate/.test(auto));
24
+ ok('auto.md: skip runs on each skip-list add', /trailhead-auto-run\.js skip --repo <main-checkout> --ticket <n>/.test(auto) && /every time a ticket joins the run's skip list/i.test(auto));
25
+ ok('auto.md: unmerged is recorded right after the abort, before the tracker call', /trailhead-auto-run\.js unmerged --repo <main-checkout> --ticket <n> --branch trailhead\/t<n>[^\n]*--file/.test(auto) && /before any tracker call/.test(auto));
26
+ ok('auto.md: unmerged again with --merge-ticket after the add succeeds', /--merge-ticket <n>/.test(auto) && /after the merge-ticket `add` succeeds/.test(auto));
27
+ ok('auto.md: a crash or failed add still leaves the branch on the persisted list', /still leaves the branch on the persisted unmerged list/.test(auto));
28
+ const parallel = auto.slice(auto.indexOf('## Parallel slots'), auto.indexOf('## Parking a ticket'));
29
+ const outside = auto.replace(parallel, '');
30
+ ok('auto.md: a general compaction rule sits outside Parallel slots and holds whatever the slot count and sameSession', /After a context compaction mid-run[^\n]*whatever the slot count[^\n]*trailhead-auto-run\.js read --repo <main-checkout>[^\n]*whatever `sameSession` says[^\n]*without asking/.test(outside) && /scope, slots, review choice, skip and unmerged lists/.test(outside));
31
+ ok('auto.md: the orchestrator knows it is mid-run from its own context', /mid-run from its own context/.test(outside));
32
+ ok('auto.md: the sameSession ask applies only at engagement', /`sameSession` ask applies only at engagement/.test(auto) && /fresh `\/trailhead:auto` invocation/.test(auto));
33
+ ok('auto.md: no session id host note (Codex)', /`sameSession` is always false on a host with no session id \(Codex\)/.test(auto) && /re-invocation after a crash always gets the resume-or-fresh ask/.test(auto) && /mid-run compaction never does/.test(auto));
34
+ ok('auto.md: the slot table bullet points at the general rule', /slot table from `trailhead-auto-slot\.js list`[^\n]*general compaction rule/.test(parallel) && !/trailhead-auto-run\.js read/.test(parallel));
35
+ ok('auto.md: run end unites the in-memory lists with the record', /in-memory skip and unmerged lists[^\n]*united with the record's/.test(auto));
36
+ ok('auto.md: run end clears the record, then resets the work states', /trailhead-auto-run\.js clear --repo <main-checkout>[^\n]*then[^\n]*--auto false/.test(auto) && /Clearing first means a crash between the two/.test(auto));
37
+ ok('auto.md: a host interrupt writes nothing, so Esc leaves the crash signature', !/graceful interrupt clears it too/.test(auto) && !/a graceful interrupt clears the record/.test(auto) && /same on disk as a crash/.test(auto) && /signature of both an Esc and a crash/.test(auto));
38
+ ok('auto.md: after Esc the same session reuses the record, a new session is asked again', /Esc[^\n]*reuses the record/.test(auto) && /new session[^\n]*(asked again|resume-or-fresh)/.test(auto));
39
+ ok('auto.md: step 2 writes the missing PAUSED checkpoint and label before resuming a crash-signature ticket', /Reconstruct the missing checkpoint/.test(auto) && /reconstructed at resume/.test(auto) && /adds `trailhead:paused` \(after the comment, so a failed comment never leaves a label without a checkpoint\)/.test(auto) && /resumes the ticket through the `resume` engine, which drops the label/.test(auto) && /no second checkpoint/.test(auto));
40
+ ok('auto.md: no run-end summary at a host interrupt, the resumed run covers it', /not emitted at the interrupt/.test(auto) && /resumed run's own (run-end )?summary/.test(auto));
41
+ ok('auto.md: crash signature and idempotence are stated on the latest checkpoint', /whose latest checkpoint is not a `PAUSED` comment/.test(auto) && /skipped only when the latest checkpoint is already a `PAUSED` comment or the label is present/.test(auto));
42
+ ok('auto.md: a slot ticket at the resolve step is exempt from reconstruction', /work state step is `resolve`[^\n]*exempt from (the )?reconstruction/.test(auto));
43
+ ok('auto.md: reconstruction happens when the ticket is resumed, not for a waiting extra', /Reconstruction happens when the ticket is actually resumed[^\n]*untouched/.test(auto));
44
+ const docs = {
45
+ 'README.md': 'README.md',
46
+ 'README.it.md': 'README.it.md',
47
+ 'workflow.md': 'site/src/content/docs/docs/workflow.md',
48
+ 'commands/work.md': 'site/src/content/docs/docs/commands/work.md',
49
+ };
50
+ const root = path.resolve(__dirname, '..');
51
+ for (const [name, rel] of Object.entries(docs)) {
52
+ const t = fs.readFileSync(path.join(root, rel), 'utf8');
53
+ const it = name === 'README.it.md';
54
+ ok(`${name}: no interrupt-time PAUSED checkpoint promise`, !/(Stop\/Esc|Esc or Stop|Esc)[^\n]*(keeps its claim and gets|keeps its claim with|mantiene il claim e riceve)[^\n]*`PAUSED`/.test(t) && !/gets a `PAUSED` checkpoint naming/.test(t));
55
+ ok(`${name}: Esc writes nothing and the next auto writes the missing PAUSED checkpoint`, it
56
+ ? /Esc[^\n]*non scrive nulla[^\n]*stessa traccia di un crash[^\n]*`\/trailhead:auto`[^\n]*checkpoint `PAUSED` mancante[^\n]*workspace e branch/.test(t) && /stessa sessione[^\n]*riusa[^\n]*nuova sessione[^\n]*chiede di nuovo/.test(t)
57
+ : /Esc[^\n]*writes nothing[^\n]*same trace as a crash[^\n]*`\/trailhead:auto`[^\n]*missing `PAUSED` checkpoint[^\n]*workspace and branch/.test(t) && /same session[^\n]*reuses[^\n]*new session[^\n]*asks again/.test(t));
58
+ }
59
+ ok('auto.md: no em-dash', !auto.includes(String.fromCharCode(0x2014)));
60
+ console.log(`auto-run-docs.test.js: ${passed} checks passed`);
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ // Docs test for #280: auto.md documents the run scope (asked at start, `<n>` skips),
3
+ // the queue order rule, the helper, the helper-error stop, and the report split.
4
+ // Run: node auto-scope-docs.test.js
5
+ const assert = require('assert');
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ const plugin = path.join(__dirname, '..', 'plugins', 'trailhead');
10
+ const read = (rel) => fs.readFileSync(path.join(plugin, rel), 'utf8');
11
+
12
+ let passed = 0;
13
+ const ok = (name, cond) => { assert.ok(cond, name); passed++; };
14
+
15
+ const auto = read('skills/trailhead-work/references/auto.md');
16
+ for (const needle of [
17
+ '**Pick the run scope.**', 'trailhead-auto-queue.js', "trailhead-auto-queue.js scopes", '`AskUserQuestion`', 'one map, the whiteboard, or everything',
18
+ '`/trailhead:auto <n>`', 'without asking', 'no delegate option', 'before anything is claimed',
19
+ 'trailhead-auto-queue.js queue --scope', '--skip', '`next`', 'whiteboard last', 'ascending map number', 'oldest workable ticket first',
20
+ 'A helper error stops the run', '`drift`', '`resume`', '--map none',
21
+ 'each touched map gets a comment with **only its own part**', 'the whiteboard part stays in chat only', 'a map the run never touched gets no comment',
22
+ ]) ok(`auto.md names: ${needle}`, auto.includes(needle));
23
+
24
+ for (const rel of ['skills/trailhead-work/references/auto.md', 'skills/trailhead-work/SKILL.md', 'commands/auto.md', 'templates/trailhead-auto-queue.js']) {
25
+ ok(`${rel} has no em-dash`, !read(rel).includes('—'));
26
+ }
27
+ for (const rel of ['skills/trailhead-work/SKILL.md', 'commands/auto.md']) {
28
+ ok(`${rel} describes a chosen scope`, /run scope/.test(read(rel)));
29
+ ok(`${rel} no longer says "the map's frontier"`, !read(rel).includes("the map's frontier"));
30
+ }
31
+ ok('commands/auto.md keeps the [map] hint', read('commands/auto.md').includes('argument-hint: "[map]"'));
32
+
33
+ console.log(`auto-scope-docs.test.js: ${passed} checks passed`);
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+ // Docs test for #283: the autonomous run asks for parallel slots and works each slot
3
+ // ticket in its own worktree. Plain asserts, no framework. Run: node bin/auto-slots-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 auto = read('skills/trailhead-work/references/auto.md');
14
+ const work = read('skills/trailhead-work/SKILL.md');
15
+ const team = read('skills/_shared/teamwork.md');
16
+
17
+ ok('auto.md asks the slot count, 1 to 4, proposed 2', /Parallel slots: 1, 2, 3 or 4/.test(auto) && /\*\*2 proposed\*\*/.test(auto) && auto.includes('AskUserQuestion'));
18
+ ok('auto.md asks the slot count even with <n>', /`\/trailhead:auto <n>` skips only the scope ask: it \*\*still asks the slot count\*\*/.test(auto));
19
+ ok('auto.md slot ask is the human\'s, no delegate', /there is no delegate option, and it is asked before anything is claimed/.test(auto));
20
+ ok('auto.md names the open helper call with --base', auto.includes('trailhead-auto-slot.js open --repo <main-checkout> --ticket <n> --base <sha>'));
21
+ ok('auto.md names the list helper call', auto.includes('trailhead-auto-slot.js list --repo <main-checkout>'));
22
+ ok('auto.md handles clone-required', auto.includes('clone-required') && auto.includes('checkout -b trailhead/t<n> <base>'));
23
+ ok('auto.md writes sessionless slot work state', auto.includes('env -u CLAUDE_CODE_SESSION_ID node <templates>/trailhead-work-state.js set --repo <workspace>'));
24
+ ok('auto.md resumes a ticket in its own workspace', /resumed \*\*in that workspace\*\*, whatever the slot count/.test(auto) && auto.includes('left alone'));
25
+ ok('auto.md: a slot never writes the active-map marker', /A slot never writes the `\.trailhead\/active-map` marker/.test(auto) && /every map-scoped call[^.]*names the map explicitly/.test(auto));
26
+ ok('auto.md: isolation override above 1 slot, none and auto included', /Above 1 slot[^.]*a worktree whatever `isolation:` says\*\* \(`none` and `auto` included\)/.test(auto));
27
+ ok('auto.md: background dispatch, nothing inline', auto.includes('background subagent') && auto.includes('nothing runs inline'));
28
+ ok('auto.md: in-flight tickets join --skip when filling a slot', /in-flight ticket/.test(auto) && /--skip <csv>` with the run's skip list \*\*plus every in-flight ticket\*\*/.test(auto));
29
+ ok('auto.md: integration held under git: pr and for a clone slot, branch and workspace kept', /Integration stays held under `git: pr`, and for a clone slot/.test(auto) && /parked needs-you for integration/.test(auto) && /\*\*kept\*\*/.test(auto));
30
+ ok('auto.md: no interrupt-time PAUSED for slot tickets, resume reconstructs it per workspace, run end per workspace', !/every in-flight slot ticket\*\* gets its `PAUSED` checkpoint/.test(auto) && /naming the slot workspace path and branch/.test(auto) && auto.includes('each remaining workspace'));
31
+
32
+ ok('auto.md: every set on a slot workspace is sessionless', /every `set` on a slot workspace[^.]*`env -u CLAUDE_CODE_SESSION_ID`/.test(auto));
33
+ ok('auto.md: the run-end reset on a workspace is sessionless too', /env -u CLAUDE_CODE_SESSION_ID node <templates>\/trailhead-work-state\.js set --repo <workspace> --auto false/.test(auto));
34
+ ok('auto.md: open statuses explained (reentered clone as is, stale worktree parks needs-you)', /`reentered` clone is used as is/.test(auto) && /stale worktree[\s\S]{0,400}park it needs-you/.test(auto) && auto.includes('git worktree prune'));
35
+
36
+ for (const [name, text] of [['teamwork.md', team], ['SKILL.md', work]]) {
37
+ ok(`${name}: no longer says the run is always the current checkout`, !/sequential work, no concurrency|works tickets sequentially/.test(text));
38
+ ok(`${name}: names the worktree per ticket above one slot`, /current checkout with one parallel slot and to a worktree per ticket above one/.test(text));
39
+ }
40
+ ok('teamwork.md worktree bullet names the slot helper', team.includes('through the `trailhead-auto-slot.js` helper'));
41
+
42
+ for (const [name, text] of [['auto.md', auto], ['SKILL.md', work], ['teamwork.md', team]]) {
43
+ ok(`${name}: no em-dash`, !text.includes(String.fromCharCode(0x2014)));
44
+ }
45
+ console.log(`auto-slots-docs.test.js: ${passed} checks passed`);
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+ // Docs test for #281: an autonomous run resolves a ticket through a UAT follow-up
3
+ // (found first, reused on resume), writes every record when it arises, and documents
4
+ // the hard-stop guarantee. Run: node auto-uat-docs.test.js
5
+ const assert = require('assert');
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ const plugin = path.join(__dirname, '..', 'plugins', 'trailhead');
10
+ const read = (rel) => fs.readFileSync(path.join(plugin, rel), 'utf8');
11
+
12
+ const DASH = String.fromCharCode(0x2014);
13
+ let passed = 0;
14
+ const ok = (name, cond) => { assert.ok(cond, name); passed++; };
15
+
16
+ const AUTO = 'skills/trailhead-work/references/auto.md';
17
+ const ACCEPT = 'skills/_shared/techniques/acceptance-testing.md';
18
+ const ENGINES = 'skills/trailhead-work/references/ticket-engines.md';
19
+ const SUBSTRATE = 'skills/_shared/substrate-commands.md';
20
+
21
+ const auto = read(AUTO);
22
+ for (const needle of [
23
+ 'trailhead-uat-followup.js find', '## Records as it goes', 'never the only record', 'UAT of:', 'trailhead:uat',
24
+ 'A helper error is not a "none found"', 'Only an open follow-up is reused', 'closed ones never count',
25
+ 'a crash in the instant between the claim and the work-state write', 'leftover work state', 'whose work state has `auto: true`', 'already names the same action', 'its own checklist is already the record', 'trailhead-work-state.js',
26
+ 'loses nothing that has arisen', 'UAT follow-ups opened this run',
27
+ 'each touched map gets a comment with **only its own part**', 'the whiteboard part stays in chat only', 'a map the run never touched gets no comment',
28
+ ]) ok(`auto.md names: ${needle}`, auto.includes(needle));
29
+ ok('auto.md no longer sets a ticket aside for a human-only UAT step', !auto.includes('leaves that step incomplete and sets the ticket aside'));
30
+ ok('auto.md names the needs-you label (#282)', auto.includes('trailhead:needs-you'));
31
+
32
+ const accept = read(ACCEPT);
33
+ for (const needle of ['trailhead-uat-followup.js find', 'autonomous run', 'auto.md', 'opens no duplicate'])
34
+ ok(`acceptance-testing.md names: ${needle}`, accept.includes(needle));
35
+
36
+ const engines = read(ENGINES);
37
+ ok('ticket-engines.md names the autonomous exception in build and bug Resolve', engines.split('Under an autonomous run the guard takes option (b) itself').length - 1 >= 2);
38
+
39
+ const substrate = read(SUBSTRATE);
40
+ for (const needle of ['trailhead-uat-followup.js find --parent <n> --repo <owner/name>', '"reuse"', 'paginated GraphQL `repository.issues` query', 'lagging search index'])
41
+ ok(`substrate-commands.md names: ${needle}`, substrate.includes(needle));
42
+
43
+ for (const rel of [AUTO, ACCEPT, ENGINES, SUBSTRATE, 'templates/trailhead-uat-followup.js', 'hooks/lib/uat-followup.test.js'])
44
+ ok(`${rel} has no em-dash`, !read(rel).includes(DASH));
45
+ ok('this test file has no em-dash', !fs.readFileSync(__filename, 'utf8').includes(DASH));
46
+
47
+ console.log(`auto-uat-docs.test.js: ${passed} checks passed`);
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+ // Docs test for #243: the skill prose wires the stale-base preflight into UAT and
3
+ // integration, and documents the preflight.checks config key.
4
+ // Run: node base-preflight-docs.test.js
5
+ const assert = require('assert');
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ const skills = path.join(__dirname, '..', 'plugins', 'trailhead', 'skills');
10
+ const read = (rel) => fs.readFileSync(path.join(skills, rel), 'utf8');
11
+
12
+ let passed = 0;
13
+ const ok = (name, cond) => { assert.ok(cond, name); passed++; };
14
+
15
+ const HELPER = 'trailhead-base-preflight.js';
16
+ // file -> strings it must carry
17
+ const EXPECT = {
18
+ '_shared/teamwork.md': [
19
+ '**Stale-base preflight.**', HELPER, '--config-root', 'preflight -> sync -> checks -> tests -> Code review -> goal check -> UAT', 'preflight -> sync -> checks -> tests -> integration', 're-run the Code review on it before integrating', 'start of Verify',
20
+ '`git: pr` without isolation', 'immediately before the push and PR',
21
+ '`current`', '`synced`', '`conflict`', '`dirty`', '`unverified`', '`trunk-diverged`', '`sync-failed`', 'Fail closed',
22
+ 'right before the trunk push', 'integration target', 'non-fast-forward', 'testsRequired', 'preflight.checks',
23
+ ],
24
+ '_shared/configuration-reference.md': ['`preflight.checks`', '**Preflight checks.**', 'migration', 'uniq -d'],
25
+ '_shared/techniques/acceptance-testing.md': ['Stale-base preflight comes first', HELPER, 'already ran at the start of Verify'],
26
+ 'trailhead-work/references/auto.md': ['stale-base preflight never auto-resolves', '`conflict`'],
27
+ };
28
+ for (const [rel, needles] of Object.entries(EXPECT)) {
29
+ const text = read(rel);
30
+ for (const needle of needles) ok(`${rel} names: ${needle}`, text.includes(needle));
31
+ }
32
+
33
+ // Each engine call site names the preflight.
34
+ const engines = read('trailhead-work/references/ticket-engines.md');
35
+ const lines = engines.split('\n');
36
+ const step = (prefix, from = 0) => lines.findIndex((l, i) => i >= from && l.startsWith(prefix));
37
+ const buildVerify = step('4. **Verify**');
38
+ const buildResolve = step('5. **Resolve**', buildVerify + 1);
39
+ const bugVerify = step('4. **Verify**', buildResolve + 1);
40
+ const bugResolve = step('5. **Resolve**', bugVerify + 1);
41
+ for (const [name, idx] of [['build Verify', buildVerify], ['build Resolve', buildResolve], ['bug Verify', bugVerify], ['bug Resolve', bugResolve]]) {
42
+ ok(`${name} step found`, idx >= 0);
43
+ ok(`${name} names the stale-base preflight`, lines[idx].includes('Stale-base preflight'));
44
+ }
45
+ ok('build/bug Verify start with the preflight, before the tests and Code review',
46
+ [buildVerify, bugVerify].every((i) => {
47
+ const l = lines[i];
48
+ const p = l.indexOf('Stale-base preflight');
49
+ return p >= 0 && p < l.indexOf('**Code review**') && p < l.indexOf('**Goal-backward verification**') && p < l.indexOf('**Acceptance testing**');
50
+ }));
51
+ ok('Verify runs the tests after the sync, before the Code review',
52
+ [buildVerify, bugVerify].every((i) => lines[i].indexOf('then re-run the tests') > 0 && lines[i].indexOf('then re-run the tests') < lines[i].indexOf('**Code review**')));
53
+ ok('Resolve names git: pr without isolation',
54
+ [buildResolve, bugResolve].every((i) => lines[i].includes('`git: pr` without isolation: on the ticket branch, immediately before the push and PR')));
55
+ ok('Resolve runs it before the Pending-UAT guard',
56
+ [buildResolve, bugResolve].every((i) => lines[i].indexOf('Stale-base preflight') < lines[i].indexOf('Pending-UAT guard')));
57
+ ok('Pending-UAT option (a) runs the preflight first',
58
+ engines.split('with no ticket (the stale-base preflight first, then the UAT setup, then the drive-mode ask').length === 3);
59
+ ok('UAT follow-up starts with the preflight', engines.includes('starting with the stale-base preflight'));
60
+
61
+ for (const rel of ['_shared/teamwork.md', '_shared/configuration-reference.md', '_shared/techniques/acceptance-testing.md', 'trailhead-work/references/ticket-engines.md', 'trailhead-work/references/auto.md']) {
62
+ ok(`${rel} has no em-dash`, !read(rel).includes('—'));
63
+ }
64
+
65
+ console.log(`base-preflight-docs.test.js: ${passed} checks passed`);
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env node
2
+ // Pins the Execute/Fix split (#251): the executor-side text lives in
3
+ // trailhead-work/references/executor-brief.md (the one text injected at spawn),
4
+ // while the Execute (build) and Fix (bug) steps of ticket-engines.md keep only
5
+ // the orchestrator moves. Run: node execute-split-docs.test.js. No framework.
6
+ const assert = require('assert');
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+
10
+ let passed = 0;
11
+ const ok = (name, cond) => { assert.ok(cond, name); passed++; };
12
+
13
+ const plugin = path.join(__dirname, '..', 'plugins', 'trailhead');
14
+ const refs = path.join(plugin, 'skills', 'trailhead-work', 'references');
15
+ const briefPath = path.join(refs, 'executor-brief.md');
16
+ const exists = fs.existsSync(briefPath);
17
+ ok('executor-brief.md exists', exists);
18
+ const brief = fs.readFileSync(briefPath, 'utf8');
19
+ const lb = brief.toLowerCase();
20
+ const engines = fs.readFileSync(path.join(refs, 'ticket-engines.md'), 'utf8');
21
+ const lines = engines.split('\n');
22
+
23
+ const block = (startPrefix, from = 0) => {
24
+ const s = lines.findIndex((l, i) => i >= from && l.startsWith(startPrefix));
25
+ if (s === -1) return '';
26
+ const e = lines.findIndex((l, i) => i > s && l.startsWith('4. **Verify**'));
27
+ return lines.slice(s, e === -1 ? undefined : e).join('\n');
28
+ };
29
+ const execute = block('3. **Execute**');
30
+ const fix = block('3. **Fix**');
31
+ ok('the build Execute block was found', execute.length > 500);
32
+ ok('the bug Fix block was found', fix.length > 500);
33
+
34
+ // --- the brief: what the executor does ---------------------------------------------------
35
+ const has = (src, ...needles) => needles.every((n) => src.includes(n));
36
+ ok('brief: atomic conventional commits with a Refs trailer', has(lb, 'atomic', 'conventional', 'refs: #<n>'));
37
+ ok('brief: a fix takes the confirmed DIAGNOSIS as its spec', brief.includes('the confirmed `DIAGNOSIS` (bug)'));
38
+ ok('brief: a resumed spawn continues from the commits already made', has(brief, 'Resumed spawn', 'never redo or revert'));
39
+ ok('brief: stop and report finishes the unaffected work first', brief.includes('finish the work it does not affect'));
40
+ ok('Execute and Fix: inline, the session applies the brief and asks the user directly',
41
+ execute.includes('asking the user directly') && fix.includes('asking the user directly'));
42
+ ok('brief: one commit = one verifiable step', brief.includes('One commit = one verifiable step'));
43
+ ok('brief: TDD per the config.tdd value passed', has(brief, 'config.tdd', '`seams`', '`on`', '`off`'));
44
+ ok('brief: a fix gets a test that fails before and passes after', lb.includes('fails before and passes after'));
45
+ ok('brief: dependency vetting criteria', has(lb, 'typosquat', 'age', 'downloads', 'repository metadata'));
46
+ ok('brief: the sentinel re-run of the exact command', has(brief, 'TRAILHEAD_VERIFIED_INSTALL=1', 'exact'));
47
+ ok('brief: sentinel only when clean or passed as user-approved', lb.includes('user-approved') && lb.includes('clean'));
48
+ ok('brief: a suspicious package: stop and report, never install, never set the sentinel',
49
+ lb.includes('suspicious') && lb.includes('stop and report') && lb.includes('never install') && lb.includes('never set'));
50
+ ok('brief: 100% fidelity to a passed approved mockup', brief.includes('100% fidelity') && lb.includes('approved mockup'));
51
+ ok('brief: infeasible element: stop and report, never silently deviate',
52
+ lb.includes('infeasible') && lb.includes('never silently deviate'));
53
+ ok('brief: returns the enumerated fidelity list in its report', brief.includes('enumerated fidelity list'));
54
+ ok('brief: new scope is reported, not worked', lb.includes('scope') && lb.includes('reported, not worked'));
55
+ const report = brief.slice(brief.indexOf('## Report'));
56
+ ok('brief: has a Report section', brief.includes('## Report'));
57
+ ok('brief: Report names flagged packages with exact install command and evidence',
58
+ has(lb.slice(lb.indexOf('## report')), 'flagged package', 'exact install command', 'evidence'));
59
+ ok('brief: Report names the fidelity list as element/state -> reproduced or infeasible with why',
60
+ has(report, 'element/state', 'reproduced or infeasible', 'why'));
61
+ ok('brief: Report names surfaced scope', report.toLowerCase().includes('surfaced scope'));
62
+ ok('brief: the report takes the shape of the implementing Return contract',
63
+ has(report, 'implementing `Return contract`', 'status: done | partial | blocked', '`commits:`', '`blockers:`', '`fidelity:`', '`surfaced scope:`'));
64
+ ok('brief: no longer defers the report shape to a later ticket', !report.includes('#249'));
65
+
66
+ // --- the brief carries none of the orchestrator-only moves ---------------------------------
67
+ ok('brief: no AskUserQuestion', !brief.includes('AskUserQuestion'));
68
+ ok('brief: no "ask the user"', !lb.includes('ask the user'));
69
+ ok('brief: no config.design', !brief.includes('config.design'));
70
+ ok('brief: no Prototype technique instruction', !lb.includes('prototype technique'));
71
+ ok('brief: no gh issue / gh pr tracker write', !/gh (issue|pr)\b/.test(lb));
72
+ ok('brief: no branch or PR creation', !/(create|creates|creating|open|opens) (the |a )?(branch|pr\b|pull request)/.test(lb));
73
+ ok('brief: no em-dash', !brief.includes('—'));
74
+
75
+ // --- the build Execute block: the orchestrator part --------------------------------------------
76
+ const lx = execute.toLowerCase();
77
+ ok('Execute: Prototype FIRST', execute.includes('Prototype technique') && execute.includes('FIRST'));
78
+ ok('Execute: config.design routing, never a hosted mode to a local disk mockup',
79
+ has(execute, 'config.design', 'never shortcut a hosted mode to a local disk mockup'));
80
+ ok('Execute: config.design.approval gate', has(execute, 'config.design.approval', '`explicit`', '`auto`'));
81
+ ok('Execute: branch before the spawn and PR after under git: pr',
82
+ has(execute, 'git: pr', 'the orchestrator creates the branch before the spawn and opens the PR'));
83
+ ok('Execute: worktree under isolation: worktree', has(execute, 'isolation: worktree', 'git worktree'));
84
+ ok('Execute: posts the fidelity list as a ticket comment',
85
+ has(execute, 'returns the enumerated fidelity list in its report', 'the orchestrator posts it as a ticket comment'));
86
+ ok('Execute: an infeasible element is raised with the user and settled',
87
+ lx.includes('infeasible') && lx.includes('revise and re-approve the mockup') && lx.includes('accepts a named deviation'));
88
+ ok('Execute: re-spawns with the settled mockup and accepted deviations',
89
+ lx.includes('re-spawn') && lx.includes('accepted deviations'));
90
+ ok('Execute: block-and-ask on a flagged package', lx.includes('block-and-ask') && lx.includes('flagged package'));
91
+ ok('Execute: only on an explicit yes re-spawn with the package as user-approved',
92
+ lx.includes('explicit yes') && lx.includes('user-approved'));
93
+ ok('Execute: on a no the package is not installed and the plan is revisited',
94
+ lx.includes('not installed') && lx.includes('revisited with the user'));
95
+ ok('Execute: captures surfaced scope', lx.includes('surfaced scope') && lx.includes('captured'));
96
+ ok('Execute: dispatches trailhead-executor on config.models.execute',
97
+ has(execute, '`trailhead-executor`', 'config.models.execute'));
98
+ ok('Execute: the brief is the only protocol text', has(execute, 'executor-brief.md', 'only protocol text'));
99
+ ok('Execute: lists the runtime inputs',
100
+ has(lx, 'ticket number', 'plan', 'base sha', 'config.tdd', 'mockup link', 'accepted mockup deviations'));
101
+ ok('Execute: inline when the model is unset or equals the session model', lx.includes('inline') && lx.includes('session'));
102
+
103
+ // --- the bug Fix block: self-contained ------------------------------------------------------------
104
+ const lf = fix.toLowerCase();
105
+ ok('Fix: dispatches trailhead-fix on config.models.execute with the brief as the only protocol text',
106
+ has(fix, '`trailhead-fix`', 'config.models.execute', 'executor-brief.md', 'only protocol text'));
107
+ ok('Fix: lists the runtime inputs',
108
+ has(lf, 'ticket number', 'base sha', 'config.tdd', 'mockup link', 'accepted mockup deviations'));
109
+ ok('Fix: UI-fix Prototype + approval',
110
+ has(fix, 'Prototype technique', 'config.design', 'config.design.approval') && lf.includes('ui'));
111
+ ok('Fix: posts the fidelity list as a ticket comment',
112
+ has(fix, 'enumerated fidelity list', 'the orchestrator posts it as a ticket comment'));
113
+ ok('Fix: infeasible element settle-and-re-spawn loop',
114
+ lf.includes('infeasible') && lf.includes('revise and re-approve the mockup') && lf.includes('accepts a named deviation') && lf.includes('re-spawn'));
115
+ ok('Fix: block-and-ask on a flagged package with explicit-yes resume',
116
+ lf.includes('block-and-ask') && lf.includes('flagged package') && lf.includes('explicit yes') && lf.includes('user-approved'));
117
+ ok('Fix: captures surfaced scope', lf.includes('surfaced scope') && lf.includes('captured'));
118
+ ok('Fix: branch before the spawn and PR under git: pr',
119
+ has(fix, 'git: pr', 'the orchestrator creates the branch before the spawn and opens the PR'));
120
+ ok('Fix: worktree under isolation: worktree', has(fix, 'isolation: worktree', 'git worktree'));
121
+
122
+ // --- agents point to the brief -----------------------------------------------------------------------------
123
+ for (const a of ['trailhead-executor', 'trailhead-fix']) {
124
+ const md = fs.readFileSync(path.join(plugin, 'agents', `${a}.md`), 'utf8');
125
+ ok(`${a}.md points to executor-brief.md`, md.includes('trailhead-work/references/executor-brief.md'));
126
+ }
127
+ const techniques = fs.readFileSync(path.join(plugin, 'skills', '_shared', 'techniques.md'), 'utf8');
128
+ ok('techniques.md: Execute and Fix inject executor-brief.md, not the whole step',
129
+ techniques.includes('trailhead-work/references/executor-brief.md'));
130
+
131
+ // --- no behaviour lost from the pre-split Execute paragraph ------------------------------------------------------
132
+ const both = execute + '\n' + brief;
133
+ for (const needle of [
134
+ 'Prototype technique', 'FIRST', 'config.design', 'never shortcut a hosted mode to a local disk mockup',
135
+ 'config.design.approval', '100% fidelity', 'fidelity list', 'Acceptance testing', 'Goal-backward verification',
136
+ 'atomic commits', 'Refs: #<n>', '`git:`', 'git worktree', 'config.models.execute', '`trailhead-executor`',
137
+ 'verify-before-install', 'TRAILHEAD_VERIFIED_INSTALL', 'block-and-ask', 'captured',
138
+ ]) {
139
+ ok(`no behaviour lost: "${needle}" is in the Execute block or the brief`, both.includes(needle));
140
+ }
141
+
142
+ for (const [label, src] of [['executor-brief.md', brief], ['ticket-engines.md', engines], ['techniques.md', techniques]]) {
143
+ ok(`${label}: no em-dash`, !src.includes('—'));
144
+ }
145
+
146
+ console.log(`execute-split-docs.test.js: ${passed} checks passed`);
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+ // Docs test for #301: every grilling close runs a holistic review pass before the final
3
+ // confirmation. The rule is stated once (Grilling technique); call-sites only point at it.
4
+ // Run: node grilling-holistic-review-docs.test.js
5
+ const assert = require('assert');
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ const skills = path.join(__dirname, '..', 'plugins', 'trailhead', 'skills');
10
+ const read = (rel) => fs.readFileSync(path.join(skills, rel), 'utf8');
11
+
12
+ let passed = 0;
13
+ const ok = (name, cond) => { assert.ok(cond, name); passed++; };
14
+
15
+ const ANCHOR = 'Holistic review before the final confirmation';
16
+ const DEFINITION = `**${ANCHOR}.**`;
17
+
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. the bold definition exists exactly once, in the Grilling technique
22
+ const defs = walk(skills).filter((f) => fs.readFileSync(f, 'utf8').includes(DEFINITION));
23
+ ok('definition appears in exactly one file', defs.length === 1);
24
+ ok('definition lives in grilling.md', defs[0] === path.join(skills, '_shared', 'techniques', 'grilling.md'));
25
+ const grilling = read('_shared/techniques/grilling.md');
26
+ ok('definition appears once', grilling.split(DEFINITION).length === 2);
27
+
28
+ // 2. the definition bullet carries the normative fragments
29
+ const lines = grilling.split('\n');
30
+ const rule = lines.find((l) => l.includes(DEFINITION));
31
+ for (const frag of [
32
+ 'Once the frontier is empty and before the final confirmation',
33
+ 'as a whole',
34
+ 'against each other and against the destination',
35
+ 'proposed amendments',
36
+ 'non-working combinations',
37
+ 'contradictions, gaps, choices that break each other or the destination',
38
+ 'by question number',
39
+ 'say so in one line and go straight to the confirmation',
40
+ 'accept, amend or reject',
41
+ 'reject keeps the choices as they were',
42
+ 'consciously accepted risk',
43
+ 'reopens that branch',
44
+ 'the review runs again on the new set',
45
+ 're-confirmed in one play-back',
46
+ 'at most once more',
47
+ 'Only then is the final set played back, confirmed and recorded',
48
+ '`AskUserQuestion` menu',
49
+ 'without the delegate option',
50
+ ]) ok(`rule carries: ${frag}`, rule.includes(frag));
51
+
52
+ // 3. the Stop bullet is followed by the review
53
+ const stop = lines.find((l) => l.startsWith('- **Stop only when the frontier is empty'));
54
+ ok('stop bullet found', !!stop);
55
+ ok('stop bullet references the anchor', stop.includes(ANCHOR));
56
+ ok('stop bullet scopes the hold to the final set', stop.includes('the final set is not confirmed or recorded before it has run'));
57
+
58
+ // 4. call-sites only point at the rule
59
+ const pointer = (name, text, startsWith) => {
60
+ const l = text.split('\n').find((x) => x.startsWith(startsWith));
61
+ ok(`${name} found`, !!l);
62
+ ok(`${name} references the anchor`, l.includes(ANCHOR));
63
+ ok(`${name} does not restate the rule`, !l.includes('accepted risk') && !l.includes('reject keeps'));
64
+ };
65
+ pointer('chart SKILL standalone grill', read('trailhead-chart/SKILL.md'), 'Run a standalone **Grilling**');
66
+ const engines = read('trailhead-work/references/ticket-engines.md');
67
+ pointer('decision Converge', engines, '2. **Converge.**');
68
+ pointer('build Discuss', engines, '1. **Discuss**');
69
+ ok('build Discuss records the set after the review', engines.split('\n').find((l) => l.startsWith('1. **Discuss**')).includes('after the review'));
70
+ const charting = read('trailhead-chart/references/charting.md');
71
+ pointer('charting Mode 1 step 3', charting, '3. **Create the map**');
72
+ pointer('charting Mode 1-bis step 2', charting, '2. **Name the destination of the remaining stretch.**');
73
+ pointer('charting Mode 1-bis step 4', charting, '4. **Map the frontier of the remainder**');
74
+ ok('charting Mode 1 step 3 covers the destination too', charting.split('\n').find((l) => l.startsWith('3. **Create the map**')).includes('step 1 destination'));
75
+ const choices = read('_shared/choices.md');
76
+ const row = choices.split('\n').find((l) => l.startsWith('| Grilling holistic review'));
77
+ ok('choices row found', !!row);
78
+ const cells = row.split('|').map((c) => c.trim());
79
+ ok('choices row delegate No', cells[2].startsWith('No'));
80
+ ok('choices row defer No', cells[3] === 'No');
81
+ const prose = choices.split('\n').find((l) => l.startsWith('**Prose / conversational call-sites'));
82
+ ok('prose paragraph references the anchor', prose.includes(ANCHOR));
83
+
84
+ // 5. no em-dash in the touched files
85
+ for (const rel of ['_shared/techniques/grilling.md', '_shared/choices.md', 'trailhead-chart/SKILL.md',
86
+ 'trailhead-work/references/ticket-engines.md', 'trailhead-chart/references/charting.md'])
87
+ ok(`${rel} has no em-dash`, !read(rel).includes(String.fromCharCode(8212)));
88
+ ok('this test has no em-dash', !fs.readFileSync(__filename, 'utf8').includes(String.fromCharCode(8212)));
89
+
90
+ console.log(`grilling-holistic-review-docs.test.js: ${passed} checks passed`);
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env node
2
+ // Docs test for #245: the skill prose names the isolation-answer helper's check/record
3
+ // reuse for the `auto` work-start ask, its fingerprint contract and the 8-hour session.
4
+ // Run: node isolation-answer-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
+
12
+ let passed = 0;
13
+ const ok = (name, cond) => { assert.ok(cond, name); passed++; };
14
+
15
+ const teamwork = fs.readFileSync(path.join(skills, '_shared', 'teamwork.md'), 'utf8');
16
+ const workSkill = fs.readFileSync(path.join(skills, 'trailhead-work', 'SKILL.md'), 'utf8');
17
+ const choices = fs.readFileSync(path.join(skills, '_shared', 'choices.md'), 'utf8');
18
+ const gitignore = fs.readFileSync(path.join(root, '.gitignore'), 'utf8');
19
+
20
+ for (const [name, text] of [['teamwork.md', teamwork], ['trailhead-work/SKILL.md', workSkill]]) {
21
+ ok(`${name} names check`, text.includes('trailhead-isolation-answer.js check'));
22
+ ok(`${name} names record`, text.includes('trailhead-isolation-answer.js record'));
23
+ ok(`${name} has no em-dash`, !text.includes('—'));
24
+ }
25
+ ok('choices.md has no em-dash', !choices.includes('—'));
26
+
27
+ for (const needle of [
28
+ '--others <csv|none|unavailable>',
29
+ 'unavailable',
30
+ '8 hours',
31
+ '`/trailhead:auto` and an explicit `none`/`worktree`/`clone` never read or write the record',
32
+ "Isolation: reusing this session's answer",
33
+ "(one ticket at a time)",
34
+ "add it to that repo's `.gitignore` if absent",
35
+ '.trailhead/isolation-answer.json',
36
+ 'three-dir lookup',
37
+ ]) ok(`teamwork.md carries: ${needle}`, teamwork.includes(needle));
38
+
39
+ ok('.gitignore carries the record', gitignore.includes('.trailhead/isolation-answer.json'));
40
+
41
+ console.log(`isolation-answer-docs.test.js: ${passed} checks passed`);
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+ // Docs test for #242: the skill prose names the gitignored-file copy helper for new
3
+ // isolation workspaces and documents the isolation.copy config key.
4
+ // Run: node isolation-copy-docs.test.js
5
+ const assert = require('assert');
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ const skills = path.join(__dirname, '..', 'plugins', 'trailhead', 'skills');
10
+ const read = (rel) => fs.readFileSync(path.join(skills, rel), 'utf8');
11
+
12
+ let passed = 0;
13
+ const ok = (name, cond) => { assert.ok(cond, name); passed++; };
14
+
15
+ // file -> strings it must carry
16
+ const EXPECT = {
17
+ 'trailhead-work/SKILL.md': ['trailhead-isolation-copy.js --from <main-checkout> --to <working-root>', 'Gitignored files in a new workspace'],
18
+ '_shared/teamwork.md': ['trailhead-isolation-copy.js --from <main-checkout> --to <working-root>', 'Gitignored files in a new workspace', 'isolation.copy', 'before any install', '`rejected`', '`warnings`', '`status: skipped`'],
19
+ '_shared/configuration-reference.md': ['`isolation.copy`', 'Isolation copy', '.example', '.sample'],
20
+ };
21
+
22
+ for (const [rel, needles] of Object.entries(EXPECT)) {
23
+ const text = read(rel);
24
+ for (const needle of needles) ok(`${rel} names: ${needle}`, text.includes(needle));
25
+ ok(`${rel} has no em-dash`, !text.includes('—'));
26
+ }
27
+
28
+ console.log(`isolation-copy-docs.test.js: ${passed} checks passed`);