@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
@@ -91,7 +91,7 @@ This trailhead engine is running on **Codex CLI**, not Claude Code. The installe
91
91
  trailhead is a native Codex skill. Invoke it as \`$trailhead <verb>\` (e.g. \`$trailhead work\`, \`$trailhead new "idea"\`); bare \`$trailhead\` is smart entry. Never \`/trailhead:<verb>\` (that is the Claude Code surface). Whatever you type after the verb is the arguments. For discoverability the installer also projects one thin skill per verb (\`$trailhead-work\`, \`$trailhead-bug\`, …) that just delegates to \`$trailhead <verb>\`, so the verbs surface in the \`$\`-menu; \`$trailhead <verb>\` stays the canonical form and this SKILL.md the single source of truth. (Codex custom prompts under \`~/.codex/prompts/\` are deliberately NOT used: they do not surface as slash commands.)
92
92
 
93
93
  ## B. This IS the skill; the engine is split across sibling skill dirs
94
- Codex loaded this SKILL.md because you invoked \`$trailhead\` (or a \`$trailhead-<verb>\` / \`$trailhead-<cluster>\` entry); there is no separate Skill tool to call. The engine is split across sibling skill dirs under \`~/.codex/skills/\`: the dispatcher (\`trailhead/\`), five cluster skills (\`trailhead-chart/\`, \`trailhead-work/\`, \`trailhead-view/\`, \`trailhead-capture/\`, \`trailhead-manage/\`), and the shared core (\`_shared/\`, no SKILL.md). Read every referenced file **relative to the SKILL.md you are currently in**: a \`references/*.md\` from this skill's own dir, and the shared core (\`../_shared/*.md\`, or \`../../_shared/*.md\` from a \`references/\` file) from the sibling \`_shared/\` dir. Where the dispatcher's routing says "call the Skill tool with skill name \`trailhead-<cluster>\` and arguments \`<verb> <rest>\`", there is no Skill tool on Codex: **Read \`../trailhead-<cluster>/SKILL.md\` (the sibling dir) and follow it**, treating \`<verb> <rest>\` as its arguments.
94
+ Codex loaded this SKILL.md because you invoked \`$trailhead\` (or a \`$trailhead-<verb>\` / \`$trailhead-<cluster>\` entry); there is no separate Skill tool to call. The engine is split across sibling skill dirs under \`~/.codex/skills/\`: the dispatcher (\`trailhead/\`), five cluster skills (\`trailhead-chart/\`, \`trailhead-work/\`, \`trailhead-view/\`, \`trailhead-capture/\`, \`trailhead-manage/\`), and the shared core (\`_shared/\`, no SKILL.md). Resolve every referenced file **relative to the SKILL.md you are currently in** into an absolute path, then read it with a single \`cat <absolute path>\` or the file-read tool (never \`cd\` into a skills dir and then read a relative path): a \`references/*.md\` from this skill's own dir, and the shared core (\`../_shared/*.md\`, or \`../../_shared/*.md\` from a \`references/\` file) from the sibling \`_shared/\` dir. Where the dispatcher's routing says "call the Skill tool with skill name \`trailhead-<cluster>\` and arguments \`<verb> <rest>\`", there is no Skill tool on Codex: **Read \`../trailhead-<cluster>/SKILL.md\` (the sibling dir) and follow it**, treating \`<verb> <rest>\` as its arguments.
95
95
 
96
96
  ## C. AskUserQuestion -> plain-text numbered list (the guaranteed path on Codex)
97
97
  Codex's \`request_user_input\` tool only works in **Plan Mode** (Codex's collaboration mode with a plan-review step). trailhead skills run in Codex's **Default mode**: the installer never enables \`collaboration_modes\` / Plan Mode, so \`request_user_input\` is unavailable here. This is a **permanent, known constraint** of running trailhead on Codex, not a conditional "if unavailable" case.
@@ -113,7 +113,7 @@ Codex has a subagent toolkit: the multi_agent tools (\`spawn_agent\`, \`send_inp
113
113
  The engine's \`/clear\` is Codex's \`/new\` (start a fresh session). Command references in a handoff use the skill form (\`$trailhead work <n>\`).
114
114
 
115
115
  ## F. Lifecycle hooks (native Codex hooks)
116
- Codex has a hook bus, so trailhead's guardrails run as **real Codex hooks**, not degraded prose. The installer registers them in \`~/.codex/hooks.json\`: commit-guard, secret-guard, install-guard and body-guard as \`PreToolUse\` (matcher \`Bash\`) hooks that can veto the command before it runs (the four run in one process, a single \`trailhead-bash-guards.js --guards=commit,secret,install,body\` registration, so a Bash call pays one start-up instead of four), secret-read-guard as a \`PreToolUse\` (matcher \`Read|Bash\`) hook that denies reads of \`.env\`/\`.env.*\`/\`.secrets\`, the injection-scanner as a \`PostToolUse\` (matcher \`Bash\`) advisory, mockup-link-stop as a \`Stop\` hook (when your final reply asks to approve a mockup without carrying its link, it continues the turn so you rewrite the ask with the URL or file path in the prose), and check-update as a \`SessionStart\` hook. On Codex, file reads happen through shell commands rather than a native Read-tool event, so the secret-read guard's enforcement here runs via its **Bash** arm (protection is not lost); the \`Read\` half of its matcher is a Claude-Code-native path, registered on Codex too only so it fires without changes if a future Codex version adds an equivalent event. search-guard is Claude-Code-specific (it only prevents arming Claude's \`Read()\` deny rule under bypass permissions, a mechanism Codex lacks), so it is not projected to Codex at all. They use the same wire format they use on Claude Code (JSON on stdin, a \`decision\`/\`permissionDecision\` verdict on stdout), which Codex accepts. The host-independent git \`commit-msg\` hook is still installed at repo first-use as well (defence in depth), exactly as on Claude. Codex gates hooks behind \`features.hooks = true\` and reviews them for trust on first start: the installer enables the flag, and Codex will ask you to trust trailhead's hooks the next time it starts.
116
+ Codex has a hook bus, so trailhead's guardrails run as **real Codex hooks**, not degraded prose. The installer registers them in \`~/.codex/hooks.json\`: commit-guard, secret-guard, install-guard, body-guard and labels-guard as \`PreToolUse\` (matcher \`Bash\`) hooks that can veto the command before it runs (the five run in one process, a single \`trailhead-bash-guards.js --guards=commit,secret,install,body,labels\` registration, so a Bash call pays one start-up instead of five), secret-read-guard as a \`PreToolUse\` (matcher \`Read|Bash\`) hook that denies reads of \`.env\`/\`.env.*\`/\`.secrets\`, the injection-scanner as a \`PostToolUse\` (matcher \`Bash\`) advisory, mockup-link-stop as a \`Stop\` hook (when your final reply asks to approve a mockup without carrying its link, it continues the turn so you rewrite the ask with the URL or file path in the prose), and check-update as a \`SessionStart\` hook. On Codex, file reads happen through shell commands rather than a native Read-tool event, so the secret-read guard's enforcement here runs via its **Bash** arm (protection is not lost); the \`Read\` half of its matcher is a Claude-Code-native path, registered on Codex too only so it fires without changes if a future Codex version adds an equivalent event. search-guard is Claude-Code-specific (it only prevents arming Claude's \`Read()\` deny rule under bypass permissions, a mechanism Codex lacks), so it is not projected to Codex at all. They use the same wire format they use on Claude Code (JSON on stdin, a \`decision\`/\`permissionDecision\` verdict on stdout), which Codex accepts. The host-independent git \`commit-msg\` hook is still installed at repo first-use as well (defence in depth), exactly as on Claude. Codex gates hooks behind \`features.hooks = true\` and reviews them for trust on first start: the installer enables the flag, and Codex will ask you to trust trailhead's hooks the next time it starts.
117
117
 
118
118
  ## G. Design / UI mockups: artifacts -> stitch offer, else local disk
119
119
  The \`artifacts\` mockup mode (a Design canvas Artifact built with Claude Code's \`Artifact\` tool; the legacy \`claude.ai/design\` value means the same) does not exist on Codex: there is no \`Artifact\` tool here. When \`config.design\` names it, follow the Prototype technique's fallback: **offer once to switch this project to \`design: stitch\`** (the Google Stitch MCP runs on Codex too), and only on decline produce a throwaway static HTML mockup next to the code (\`disk\`), link it from the ticket, and say why. Never try to create or edit an Artifact on Codex. \`config.design.approval\` still applies unchanged: \`explicit\` waits for the user's go-ahead before any UI code, \`auto\` surfaces the mockup and proceeds.
@@ -366,9 +366,9 @@ function codexVerbSkillPlan(codexHome, verbs) {
366
366
  // --- codexHookEntries -----------------------------------------------------
367
367
  // The guardrail hooks trailhead registers on Codex, as {event, matcher,
368
368
  // command} records. Commands run the copied hook scripts under hooksScriptsDir
369
- // via node, matching Claude's `node "<path>"` form. The four Bash guards
370
- // (commit, secret, install, body) are ONE registration, the merged
371
- // trailhead-bash-guards.js with `--guards=commit,secret,install,body` (a single
369
+ // via node, matching Claude's `node "<path>"` form. The five Bash guards
370
+ // (commit, secret, install, body, labels) are ONE registration, the merged
371
+ // trailhead-bash-guards.js with `--guards=commit,secret,install,body,labels` (a single
372
372
  // node process per Bash call); secret-read is a separate PreToolUse hook. All
373
373
  // PreToolUse guards can veto; the injection-scanner
374
374
  // is a PostToolUse advisory; check-update runs at SessionStart. Reused by the
@@ -381,7 +381,7 @@ function codexVerbSkillPlan(codexHome, verbs) {
381
381
  function codexHookEntries(hooksScriptsDir) {
382
382
  const cmd = (name) => `node "${path.join(hooksScriptsDir, name)}"`;
383
383
  return [
384
- { event: 'PreToolUse', matcher: 'Bash', command: `${cmd('trailhead-bash-guards.js')} --guards=commit,secret,install,body` },
384
+ { event: 'PreToolUse', matcher: 'Bash', command: `${cmd('trailhead-bash-guards.js')} --guards=commit,secret,install,body,labels` },
385
385
  { event: 'PreToolUse', matcher: 'Read|Bash', command: cmd('trailhead-secret-read-guard.js') },
386
386
  { event: 'PostToolUse', matcher: 'Bash', command: cmd('trailhead-issue-injection-scanner.js') },
387
387
  { event: 'Stop', matcher: '', command: cmd('trailhead-mockup-link-stop.js') },
@@ -173,8 +173,8 @@ const entries = codexHookEntries('/h');
173
173
  ok('codexHookEntries returns 5 entries', Array.isArray(entries) && entries.length === 5);
174
174
  const bashEntries = entries.filter((e) => e.event === 'PreToolUse' && e.matcher === 'Bash');
175
175
  ok('codexHookEntries: exactly one PreToolUse/Bash registration', bashEntries.length === 1);
176
- ok('codexHookEntries: the Bash entry is the merged hook with exactly --guards=commit,secret,install,body',
177
- bashEntries[0].command === 'node "/h/trailhead-bash-guards.js" --guards=commit,secret,install,body');
176
+ ok('codexHookEntries: the Bash entry is the merged hook with exactly --guards=commit,secret,install,body,labels',
177
+ bashEntries[0].command === 'node "/h/trailhead-bash-guards.js" --guards=commit,secret,install,body,labels');
178
178
  ok('codexHookEntries: no per-guard script is registered on its own', entries.every((e) =>
179
179
  !/trailhead-(commit|secret|install|search|body)-guard\.js/.test(e.command)));
180
180
  ok('codexHookEntries: secret-read-guard is PreToolUse/Read|Bash', entries.some((e) =>
@@ -410,6 +410,11 @@ ok('convertToCodex projects the anchor positives to Codex forms', (() => {
410
410
  // the projected work skill and handoff carry the set/clear calls.
411
411
  ok('convertToCodex keeps the trailhead-work-state.js filename',
412
412
  convertToCodex('node <templates>/trailhead-work-state.js set').includes('<templates>/trailhead-work-state.js set'));
413
+ // #253: the Resolve bookkeeping helper filenames survive the projection.
414
+ ok('convertToCodex keeps the trailhead-issue-body.js filename',
415
+ convertToCodex('node <templates>/trailhead-issue-body.js append --issue 1').includes('<templates>/trailhead-issue-body.js append'));
416
+ ok('convertToCodex keeps the trailhead-dashboard.js filename',
417
+ convertToCodex('node <templates>/trailhead-dashboard.js --dry-run').includes('<templates>/trailhead-dashboard.js --dry-run'));
413
418
  ok('convertToCodex keeps CLAUDE_CODE_SESSION_ID intact',
414
419
  convertToCodex('session from CLAUDE_CODE_SESSION_ID when set').includes('CLAUDE_CODE_SESSION_ID'));
415
420
  {
@@ -417,6 +422,12 @@ ok('convertToCodex keeps CLAUDE_CODE_SESSION_ID intact',
417
422
  const workSkill = convertToCodex(fs.readFileSync(path.join(skillsDir, 'trailhead-work', 'SKILL.md'), 'utf8'));
418
423
  ok('projected work skill carries the work-start set call',
419
424
  workSkill.includes('trailhead-work-state.js set --repo <working-root> --map <n> --ticket <n> --title "<title>" --step discuss'));
425
+ // #234: the scope confirm menu call-site and the adapter's numbered-list rule both reach Codex.
426
+ const workInjected = injectCodexAdapterHeader(workSkill);
427
+ ok('projected work skill carries the step 3 scope-confirm menu call-site',
428
+ /AskUserQuestion[\s\S]{0,300}\*\*Proceed\*\*/.test(workInjected));
429
+ ok('projected work skill carries the adapter AskUserQuestion -> numbered list rule',
430
+ workInjected.includes('## C. AskUserQuestion -> plain-text numbered list'));
420
431
  const handoff = convertToCodex(fs.readFileSync(path.join(skillsDir, '_shared', 'session-handoff.md'), 'utf8'));
421
432
  ok('projected session handoff carries the work state clear call',
422
433
  handoff.includes('trailhead-work-state.js clear --repo <working-root>'));
@@ -430,4 +441,34 @@ ok('convertToCodex keeps CLAUDE_CODE_SESSION_ID intact',
430
441
  autoRef.includes('--auto true --auto-done <n>'));
431
442
  }
432
443
 
444
+ // #248: the shared boundary blocks survive the Codex projection of every real agent.
445
+ {
446
+ const { parseSource } = require('../../scripts/sync-agent-blocks.js');
447
+ const shared = parseSource(fs.readFileSync(path.join(__dirname, '../../scripts/agent-blocks.md'), 'utf8'));
448
+ const agentsDir = path.join(__dirname, '../../plugins/trailhead/agents');
449
+ const files = fs.readdirSync(agentsDir).filter((f) => /^trailhead-.*\.md$/.test(f));
450
+ ok('projection boundaries: found the eight real agents', files.length === 8);
451
+ ok('projection boundaries: source has common and implementing blocks', !!shared.common && !!shared.implementing);
452
+ // #249: every agent's projected TOML carries exactly its family block, whole.
453
+ const familyOf = {
454
+ 'trailhead-verify': 'reviewing', 'trailhead-code-review': 'reviewing',
455
+ 'trailhead-executor': 'implementing', 'trailhead-fix': 'implementing',
456
+ 'trailhead-debug': 'investigating', 'trailhead-research': 'investigating', 'trailhead-codebase-map': 'investigating',
457
+ 'trailhead-plan': 'plan',
458
+ };
459
+ const families = ['reviewing', 'implementing', 'investigating', 'plan'];
460
+ ok('projection boundaries: source has every family block', families.every((f) => !!shared[f]));
461
+ for (const f of files) {
462
+ const name = f.slice(0, -3);
463
+ const body = fs.readFileSync(path.join(agentsDir, f), 'utf8').replace(/^---\r?\n[\s\S]*?\r?\n---[ \t]*\r?\n?/, '');
464
+ const out = codexAgentToml({ name, description: 'd', developerInstructions: body, model: null, effort: null });
465
+ ok(`${name}: projection carries the whole common block`, !!shared.common && out.includes(shared.common));
466
+ ok(`${name}: has a family`, families.includes(familyOf[name]));
467
+ for (const fam of families) {
468
+ ok(`${name}: projection ${familyOf[name] === fam ? 'carries the whole' : 'omits the'} ${fam} block`,
469
+ !!shared[fam] && out.includes(shared[fam]) === (familyOf[name] === fam));
470
+ }
471
+ }
472
+ }
473
+
433
474
  console.log(`✓ codex-projection: ${passed} assertions passed`);
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env node
2
+ // Docs test for #282: the needs-you label (an autonomous run parks a ticket for the
3
+ // human) is documented where the run, the claim and the substrate depend on it.
4
+ // Plain asserts, no framework. Run: node bin/needs-you-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 work = read('skills/trailhead-work/SKILL.md');
16
+ const substrate = read('skills/_shared/substrate.md');
17
+ const commands = read('skills/_shared/substrate-commands.md');
18
+
19
+ for (const needle of ['trailhead:needs-you', 'trailhead-needs-you.js park', '`NEEDS YOU`', 'needs-you ticket', 'queue skips it', 'does not reconsider it', 'surviving blocking review concern', 'stale-base stop', 'a failing UAT setup'.replace('a failing', 'failing')]) {
20
+ ok(`auto.md names: ${needle}`, auto.includes(needle));
21
+ }
22
+ ok('auto.md no longer says "set aside" / "set-aside"', !/set[- ]aside|sets? (?:it|that|such a|the) (?:ticket )?aside|sets? .{0,20} aside/i.test(auto));
23
+
24
+ const claimStep = (work.match(/^2\. Choose the ticket\..*$/m) || [''])[0];
25
+ const quickBullet = (work.match(/^- \*\*Same isolation setup as `work`\.\*\*.*$/m) || [''])[0];
26
+ const CLEAR = 'node <templates>/trailhead-needs-you.js clear --issue <n> --repo <owner/name>';
27
+ ok('work claim step clears needs-you with the helper', claimStep.includes(CLEAR));
28
+ ok('quick isolation bullet clears needs-you with the helper', quickBullet.includes(CLEAR));
29
+ ok('work claim step has the by-hand fallback', claimStep.includes('gh issue edit <n> --remove-label trailhead:needs-you'));
30
+ ok('a failed clear is reported, never blocking the claim (work)', /failed `clear` is reported to the user in one line but never blocks the claim/.test(claimStep));
31
+ ok('a failed clear is reported, never blocking the claim (quick)', /failed `clear` is reported to the user in one line but never blocks the claim/.test(quickBullet));
32
+
33
+ ok('substrate.md documents the label, off every run, on the human frontier', /\*\*`trailhead:needs-you`\*\*[\s\S]{0,900}not a frontier exclusion for humans/.test(substrate) && substrate.includes('twenty-one labels'));
34
+ ok('substrate-commands.md ensures the label idempotently', commands.includes('gh label create trailhead:needs-you --color D93F0B --description "Needs you: an autonomous run parked it for the human" --force'));
35
+ ok('substrate-commands.md documents park and clear', commands.includes('trailhead-needs-you.js park --issue <n> --reason <text> --repo <owner/name>') && commands.includes('trailhead-needs-you.js clear --issue <n> --repo <owner/name>'));
36
+ ok('substrate-commands.md first-use list has twenty-one labels', /all twenty-one:[^\n]*`trailhead:needs-you`/.test(commands));
37
+
38
+ for (const [name, text] of [['auto.md', auto], ['SKILL.md', work], ['substrate.md', substrate], ['substrate-commands.md', commands]]) {
39
+ ok(`${name}: no em-dash`, !text.includes(String.fromCharCode(0x2014)));
40
+ }
41
+ console.log(`needs-you-docs.test.js: ${passed} checks passed`);
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env node
2
+ // Docs test: the agent never offers or acts on its own pick unchecked. The stress-test rule is
3
+ // stated once (choices.md) and pointed at by grilling, the delegate option, auto and the Plan;
4
+ // the Plan carries a Done-when coverage check, decision Diverge re-checks a framed option set,
5
+ // and inbox never closes or answers someone else's issue without the user's yes.
6
+ // Run: node pick-stress-test-docs.test.js
7
+ const assert = require('assert');
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ const skills = path.join(__dirname, '..', 'plugins', 'trailhead', 'skills');
12
+ const read = (rel) => fs.readFileSync(path.join(skills, rel), 'utf8');
13
+
14
+ let passed = 0;
15
+ const ok = (name, cond) => { assert.ok(cond, name); passed++; };
16
+
17
+ const ANCHOR = 'Stress-test the pick before offering it';
18
+ const DEFINITION = `**${ANCHOR}.**`;
19
+
20
+ const walk = (dir) => fs.readdirSync(dir, { withFileTypes: true }).flatMap((e) =>
21
+ e.isDirectory() ? walk(path.join(dir, e.name)) : [path.join(dir, e.name)]);
22
+ const lineOf = (text, pred) => text.split('\n').find(pred);
23
+
24
+ // 1. la definizione esiste una sola volta, in choices.md
25
+ const defs = walk(skills).filter((f) => fs.readFileSync(f, 'utf8').includes(DEFINITION));
26
+ ok('definition appears in exactly one file', defs.length === 1);
27
+ ok('definition lives in choices.md', defs[0] === path.join(skills, '_shared', 'choices.md'));
28
+ const choices = read('_shared/choices.md');
29
+ ok('definition appears once', choices.split(DEFINITION).length === 2);
30
+
31
+ // 2. la definizione porta i frammenti normativi
32
+ const rule = lineOf(choices, (l) => l.includes(DEFINITION));
33
+ for (const frag of [
34
+ 'a recommended answer in grilling',
35
+ '`(Recommended)`',
36
+ 'the delegate option',
37
+ 'an option `auto` takes on its own',
38
+ 'the edge case or failure mode where it breaks',
39
+ 'checked against the code rather than assumed',
40
+ 'the choices already settled',
41
+ 'real holes, not manufactured doubt',
42
+ 'One pass, no loop',
43
+ 'present only that',
44
+ 'Weak point:',
45
+ 'still recommended because',
46
+ 'conversation language',
47
+ 'where no human sees it live',
48
+ 'trivial or already-decided choice',
49
+ ]) ok(`rule carries: ${frag}`, rule.includes(frag));
50
+
51
+ // 3. i call-site puntano alla regola senza ripeterla
52
+ const pointer = (name, line) => {
53
+ ok(`${name} found`, !!line);
54
+ ok(`${name} references the anchor`, line.includes(ANCHOR));
55
+ ok(`${name} does not restate the rule`, !line.includes('manufactured doubt'));
56
+ };
57
+ const grilling = read('_shared/techniques/grilling.md');
58
+ const gl = grilling.split('\n');
59
+ const pickIdx = gl.findIndex((l) => l.startsWith('- **One question at a time, with your pick.**'));
60
+ ok('grilling pick bullet found', pickIdx >= 0);
61
+ pointer('grilling self-check bullet', gl[pickIdx + 1]);
62
+ ok('grilling bullet keeps the per-question check', gl[pickIdx + 1].includes('by question number'));
63
+ ok('grilling pick bullet points at the self-check', gl[pickIdx].includes('stress-tested per the next bullet'));
64
+ pointer('delegate step 2', lineOf(choices, (l) => l.startsWith('2. The agent states **which option it would choose')));
65
+ const auto = read('trailhead-work/references/auto.md');
66
+ const autoLine = lineOf(auto, (l) => l.startsWith('- **Advisory / process choice**'));
67
+ pointer('auto advisory choice', autoLine);
68
+ ok('auto records the weak point', autoLine.includes('`Weak point:` line'));
69
+ const engines = read('trailhead-work/references/ticket-engines.md');
70
+ const plan = lineOf(engines, (l) => l.startsWith('2. **Plan**'));
71
+ pointer('build Plan', plan);
72
+
73
+ // 4. Plan: copertura del Done when prima di ready
74
+ for (const frag of [
75
+ '**Coverage check before `ready`.**',
76
+ 'every `Done when` criterion',
77
+ 'a criterion no step delivers is a gap',
78
+ 'return `needs_input`',
79
+ 'the short inline plan too',
80
+ ]) ok(`plan carries: ${frag}`, plan.includes(frag));
81
+
82
+ // 5. Diverge: un set di opzioni gia' inquadrato viene ricontrollato
83
+ const diverge = lineOf(engines, (l) => l.startsWith('1. **Diverge'));
84
+ ok('diverge found', !!diverge);
85
+ ok('diverge no longer skips blindly', !diverge.includes('skip straight to grilling when it did.'));
86
+ for (const frag of ['still current and complete', 'Decisions so far', 'status quo', '`OPTIONS` comment'])
87
+ ok(`diverge carries: ${frag}`, diverge.includes(frag));
88
+
89
+ // 6. Inbox: nessuna chiusura o commento pubblico senza un si'
90
+ const inbox = read('trailhead-chart/references/inbox.md');
91
+ ok('inbox drops "on your own for clear ones"', !inbox.includes('on your own for clear ones'));
92
+ const intro = lineOf(inbox, (l) => l.startsWith('For each issue in the first two sections'));
93
+ for (const frag of ['propose a disposition', 'one list', 'explicit yes', 'never close', 'comment on', 'out-of-scope ruling'])
94
+ ok(`inbox intro carries: ${frag}`, intro.includes(frag));
95
+ const dup = lineOf(inbox, (l) => l.startsWith('- **Duplicate.**'));
96
+ for (const frag of ['what each one covers', 'partial overlap', 'not a duplicate'])
97
+ ok(`inbox duplicate carries: ${frag}`, dup.includes(frag));
98
+
99
+ // 7. niente em-dash
100
+ for (const rel of ['_shared/choices.md', '_shared/techniques/grilling.md', 'trailhead-work/references/auto.md',
101
+ 'trailhead-work/references/ticket-engines.md', 'trailhead-chart/references/inbox.md'])
102
+ ok(`${rel} has no em-dash`, !read(rel).includes(String.fromCharCode(8212)));
103
+ ok('this test has no em-dash', !fs.readFileSync(__filename, 'utf8').includes(String.fromCharCode(8212)));
104
+
105
+ console.log(`pick-stress-test-docs.test.js: ${passed} checks passed`);
@@ -0,0 +1,195 @@
1
+ #!/usr/bin/env node
2
+ // Pins the report status protocol in the shipped skills (#250, #249): the
3
+ // canonical "Malformed review report" paragraph (every engine-agent report) and
4
+ // VERIFY comment layout in _shared/techniques.md, the clause on both Verify lines of
5
+ // ticket-engines.md, the return-not-post wording in the verify and code-review
6
+ // techniques, and how Plan, Execute, Fix, Diagnose, research and the codebase map
7
+ // read the fields of their agent's report.
8
+ // Run: node report-status-docs.test.js. No framework: plain asserts.
9
+ const assert = require('assert');
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+
13
+ let passed = 0;
14
+ const ok = (name, cond) => { assert.ok(cond, name); passed++; };
15
+
16
+ const skills = path.join(__dirname, '..', 'plugins', 'trailhead', 'skills');
17
+ const read = (...p) => fs.readFileSync(path.join(skills, ...p), 'utf8');
18
+ const techniques = read('_shared', 'techniques.md');
19
+ const verify = read('_shared', 'techniques', 'verify.md');
20
+ const codeReview = read('_shared', 'techniques', 'code-review.md');
21
+ const acceptance = read('_shared', 'techniques', 'acceptance-testing.md');
22
+ const engines = read('trailhead-work', 'references', 'ticket-engines.md');
23
+ const brief = read('trailhead-work', 'references', 'executor-brief.md');
24
+ const research = read('_shared', 'techniques', 'research.md');
25
+ const codebaseMap = read('_shared', 'techniques', 'codebase-map.md');
26
+ const debug = read('_shared', 'techniques', 'debug.md');
27
+ const charting = read('trailhead-chart', 'references', 'charting.md');
28
+ const researchAgent = fs.readFileSync(path.join(__dirname, '..', 'plugins', 'trailhead', 'agents', 'trailhead-research.md'), 'utf8');
29
+
30
+ // --- the canonical paragraph -------------------------------------------------------
31
+ const start = techniques.indexOf('**Malformed review report');
32
+ ok('techniques.md has the Malformed review report paragraph', start !== -1);
33
+ const neverBlock = techniques.indexOf('**Reviews never block the session');
34
+ ok('the paragraph sits next to "Reviews never block the session"',
35
+ start !== -1 && neverBlock !== -1 && Math.abs(start - neverBlock) < 2500 && start > neverBlock);
36
+ const para = start === -1 ? '' : techniques.slice(start, techniques.indexOf('\n| Technique |', start));
37
+ const lc = para.toLowerCase();
38
+ ok('the paragraph section was found and is bounded', para.length > 1000 && !para.includes('| **Grilling**'));
39
+
40
+ ok('it MUST run on every engine-agent report, the two reviews included', /MUST run on every/.test(para)
41
+ && para.includes('Code review') && para.includes('Goal-backward verification'));
42
+ 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'));
44
+ ok('the checker line carries fields and body', para.includes('{"action","verdict","problem","report","fields","body"}'));
45
+ ok('it covers subagent, inline and the lean inline pass', lc.includes('subagent') && lc.includes('inline') && lc.includes('lean inline pass'));
46
+ 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'));
48
+ ok('the three templates dirs, in order',
49
+ (() => {
50
+ const a = para.indexOf('${CLAUDE_PLUGIN_ROOT}/templates/');
51
+ const b = para.indexOf('~/.claude/trailhead/templates/');
52
+ const c = para.indexOf('~/.codex/skills/trailhead/templates/');
53
+ return a !== -1 && b > a && c > b;
54
+ })());
55
+ ok('the plugin root dir is skipped when unset', lc.includes('skipped when unset'));
56
+ ok('a private temp dir from mktemp -d, once per report', para.includes('`mktemp -d`') && lc.includes('once') && lc.includes('0700'));
57
+ ok('the raw report is written to report.md at the literal printed path with the Write tool',
58
+ para.includes('<that dir>/report.md') && para.includes('Write tool') && lc.includes('literal printed path'));
59
+ ok('the dir is removed with rm -rf at the literal absolute path', para.includes('rm -rf <that dir>') && lc.includes('literal absolute path'));
60
+ ok('cleanup runs after every path',
61
+ lc.includes('cleanup runs after every path') && ['accept', 'normalise', 're-ask check', 'second failure', 'checker failure'].every((w) => lc.includes(w)));
62
+ ok('never a fixed or predictable path, never reused', lc.includes('never a fixed or predictable path') && lc.includes('never reused'));
63
+ ok('stdin stays a CLI input but the protocol does not use it', lc.includes('stdin') && lc.includes('does not use'));
64
+ ok('the checker-returned report replaces the raw report', lc.includes('checker-returned `report` replaces the raw report'));
65
+ ok('the engine branches only on the returned verdict', para.includes('branches only on the returned `verdict`'));
66
+ ok('the engine reads the returned fields and body, never re-derives them from prose',
67
+ para.includes('reads the returned `fields`') && para.includes('`body`') && lc.includes('instead of re-deriving them from the prose'));
68
+ ok('reask: ask once, continue the same agent where the host allows, else re-dispatch',
69
+ lc.includes('ask once') && lc.includes('continue the same agent') && lc.includes('re-dispatch the same `trailhead-<technique>` agent')
70
+ && lc.includes('same model override') && lc.includes('malformed report quoted'));
71
+ const prompt = 'Your report could not be accepted: `<problem>` (missing: no `status:` line; unknown-token: `<found>` is not an allowed token; '
72
+ + 'conflicting: more than one `status:` line, so quote or reword any status text in the body, which must not start a line with `status:` outside a code fence or blockquote; counts: the counts line is missing or repeated; fields: a required field is missing or an invariant is broken). '
73
+ + 'Return the complete report again with its first line exactly `status: <token>`, where `<token>` is one of `<allowed tokens>`, '
74
+ + 'and no other `status:` line<for code-review: , and its second line exactly `critical: <n> · warning: <n> · info: <n>`>'
75
+ + '<for fields: , with each of `<required fields>` as a top-level `<label>:` line and `<the broken invariant>` holding, with nothing after the last field>. '
76
+ + 'Do not post it anywhere.';
77
+ ok('the corrective prompt, verbatim', para.includes(prompt));
78
+ ok('for code-review the prompt always restates both required lines', lc.includes('always restates both required lines'));
79
+ ok('for fields the prompt names the missing fields and the broken invariant', lc.includes('names the missing or empty fields and the invariant that broke'));
80
+ ok('the reply is re-checked', lc.includes('re-check'));
81
+ ok('a second failure: no verdict, nothing posted, no third ask, no inference from the body',
82
+ lc.includes('no verdict') && lc.includes('nothing posted') && lc.includes('no third ask') && lc.includes('no inference from the body'));
83
+ ok('a second failure: HITL checkpoint naming the technique and the problem, Resolve stays blocked',
84
+ para.includes('HITL checkpoint') && lc.includes('naming the technique and the problem') && lc.includes('resolve stays blocked'));
85
+ ok('the previous VERIFY comment stays latest', lc.includes('previous `verify` comment stays latest'));
86
+ ok('a step that needed the report stays blocked too', lc.includes('so does any step that needed the report'));
87
+ ok('the layout part is for the reviewing pair only', para.includes('**VERIFY comment layout (the reviewing pair only).**'));
88
+ ok('checker missing, exit 2 or non-JSON: apply the same rules by hand, never guess',
89
+ lc.includes('checker is missing') && lc.includes('exit 2') && lc.includes('non-json') && lc.includes('by hand') && lc.includes('never guess a verdict'));
90
+
91
+ // --- the VERIFY comment layout ------------------------------------------------------
92
+ ok('layout: one VERIFY comment per Verify round', /one `VERIFY` comment per Verify round/.test(para));
93
+ ok('layout part 1: the code-review report verbatim, status line 1, counts line 2, ledger, findings',
94
+ lc.includes('code-review report verbatim') && lc.includes('counts line 2') && lc.includes('ledger'));
95
+ ok('layout part 2: a "## Goal-backward verification" heading and the verify report verbatim',
96
+ para.includes('`## Goal-backward verification` heading') && lc.includes('verify report verbatim'));
97
+ ok('layout part 3: test or criterion outcome, then acceptance testing results',
98
+ lc.includes('test/criterion outcome') && lc.includes('acceptance-testing results'));
99
+ ok('layout: an issues_found round posts parts 1 and 3 only', lc.includes('`issues_found` posts parts 1 and 3 only'));
100
+ ok('layout: a verify-only round repeats the latest code-review report verbatim as part 1',
101
+ lc.includes('repeats the latest checker-returned code-review report verbatim as part 1'));
102
+ ok('layout: the latest VERIFY comment holds the counts line and ledger', lc.includes('latest `verify` comment always holds the authoritative counts line and ledger'));
103
+ ok('layout: the engine never reads the comment back to branch', lc.includes('never by reading the comment back'));
104
+
105
+ // --- both Verify lines -----------------------------------------------------------------
106
+ const verifyLines = engines.split('\n').filter((l) => l.trim().startsWith('4. **Verify**'));
107
+ ok('ticket-engines.md has exactly two Verify (step 4) lines', verifyLines.length === 2);
108
+ for (const [i, l] of verifyLines.entries()) {
109
+ ok(`Verify line ${i + 1}: run the report-status check before branching on status`, l.includes('run the report-status check before branching on status'));
110
+ ok(`Verify line ${i + 1}: points to the paragraph by name`, l.includes('*Malformed review report*') && l.includes('../../_shared/techniques.md'));
111
+ ok(`Verify line ${i + 1}: points to the VERIFY comment layout`, l.includes('layout'));
112
+ ok(`Verify line ${i + 1}: keeps the Ticket diff reference (#181)`, l.includes('**Ticket diff**'));
113
+ }
114
+
115
+ // --- the techniques return, the orchestrator posts --------------------------------------
116
+ ok('verify.md: "Return the verdict" with a leading status', verify.includes('Return the verdict with a leading `status:`'));
117
+ ok('verify.md: the orchestrator posts it under the Goal-backward verification heading of the round\'s VERIFY comment',
118
+ verify.includes('the orchestrator posts it') && verify.includes('`## Goal-backward verification` heading') && verify.includes("round's `VERIFY` comment"));
119
+ ok('verify.md: no "Post the result"', !verify.includes('Post the result'));
120
+ ok('verify.md keeps the three tokens', ['met', 'gaps_found', 'inconclusive'].every((t) => verify.includes(`\`status: ${t}\``)));
121
+ ok('code-review.md: returns the report and the orchestrator posts it as the VERIFY comment',
122
+ /Return the report/.test(codeReview) && codeReview.includes('the orchestrator posts it as the `VERIFY` comment'));
123
+ ok('code-review.md: no "Report in the ticket\'s VERIFY comment"', !codeReview.includes("Report in the ticket's `VERIFY` comment"));
124
+ ok('code-review.md: status tag still on its own first line', codeReview.includes('machine-checkable status tag on its own first line'));
125
+ ok('code-review.md: counts line phrasing kept', codeReview.includes('critical: <n> · warning: <n> · info: <n>')
126
+ && codeReview.includes('**Counts line, right under `status:`.**'));
127
+ ok('code-review.md: dispositions and the latest VERIFY comment kept',
128
+ codeReview.includes('`fixed (<commit>)`') && codeReview.includes('latest `VERIFY` comment'));
129
+ ok('code-review.md keeps the #181 Ticket diff rule', codeReview.includes("**Ticket diff: review the ticket's own commits, never a range.**"));
130
+
131
+ // --- acceptance results are part 3 of the round's single VERIFY comment ---------------
132
+ ok('acceptance-testing.md: results are gathered as it goes and land in part 3 of the round\'s VERIFY comment',
133
+ acceptance.includes('as it goes') && acceptance.includes("part 3 of the round's single `VERIFY` comment"));
134
+ ok('acceptance-testing.md: the orchestrator posts once the round\'s checks, including acceptance, are done',
135
+ acceptance.includes('the orchestrator posts once the round\'s checks, including this one, are done'));
136
+ ok('acceptance-testing.md: no longer records "in the VERIFY comment as it goes"', !acceptance.includes('in the `VERIFY` comment as it goes'));
137
+ ok('acceptance-testing.md: the screenshot and the pending checklist go to part 3, not straight into a comment',
138
+ !acceptance.includes('screenshot into the `VERIFY` comment') && !acceptance.includes('checklist in the `VERIFY` comment'));
139
+ ok('acceptance-testing.md: the two start paths outside a Verify round post their own VERIFY comment',
140
+ acceptance.includes("the Pending-UAT guard's option (a) and a UAT follow-up, there is no round: the orchestrator posts the results as a `VERIFY` comment of their own"));
141
+ ok('acceptance-testing.md: no em-dash', !acceptance.includes('\u2014'));
142
+
143
+ // --- every engine step reads the fields of its agent's report (#249) --------------------
144
+ const stepOf = (src, startPrefix, endPrefix) => {
145
+ const ls = src.split('\n');
146
+ const a = ls.findIndex((l) => l.startsWith(startPrefix));
147
+ const b = ls.findIndex((l, i) => i > a && l.startsWith(endPrefix));
148
+ return a === -1 ? '' : ls.slice(a, b === -1 ? undefined : b).join('\n');
149
+ };
150
+ const has = (src, ...needles) => needles.every((n) => src.includes(n));
151
+ const plan = stepOf(engines, '2. **Plan**', '3. **Execute**');
152
+ const execute = stepOf(engines, '3. **Execute**', '4. **Verify**');
153
+ const fixStep = stepOf(engines, '3. **Fix**', '4. **Verify**');
154
+ const diagnose = stepOf(engines, '2. **Diagnose**', '3. **Fix**');
155
+ ok('the steps were found', [plan, execute, fixStep, diagnose].every((x) => x.length > 300));
156
+ ok('Plan: checks the planner report with --technique plan', has(plan, '--technique plan', '*Malformed review report*'));
157
+ ok('Plan: ready posts the checker body verbatim as the PLAN comment', has(plan, '`status: ready`', "checker's `body` verbatim", '`PLAN` comment'));
158
+ ok('Plan: needs_input asks the questions, re-spawns with the answers, never guesses',
159
+ has(plan, '`status: needs_input`', '`questions:`', 're-spawn the planner with the answers', 'never answer them yourself or guess'));
160
+ for (const [label, step, tech] of [['Execute', execute, 'executor'], ['Fix', fixStep, 'fix']]) {
161
+ ok(`${label}: checks the report with --technique ${tech}`, has(step, `--technique ${tech}`, '*Malformed review report*'));
162
+ ok(`${label}: branches on the verdict and reads the fields, never reconstructs from prose`,
163
+ has(step, "checker's `verdict`", '`fields`', 'never reconstruct a result from the prose'));
164
+ ok(`${label}: done goes to Verify`, has(step, '`status: done` -> Verify'));
165
+ ok(`${label}: partial or blocked acts on each blockers entry, then re-spawns`,
166
+ has(step, '`status: partial` or `blocked`', 'each `blockers:` entry', 'foreign change', 're-spawn'));
167
+ ok(`${label}: posts fidelity as the fidelity comment and routes surfaced scope`, has(step, 'Post `fidelity:` as the fidelity comment', '`surfaced scope:`'));
168
+ ok(`${label}: commits must equal the Refs set since the base, both directions, every SHA resolves, a mismatch is surfaced`,
169
+ has(step, '`commits:`', 'must equal the `Refs: #<n>` commit set since the base SHA', 'both directions', 'every SHA must resolve', 'surfaced to the user, never patched over'));
170
+ }
171
+ ok('Diagnose: checks the debug report with --technique debug', has(diagnose, '--technique debug', '*Malformed review report*'));
172
+ ok('Diagnose: found feeds the DIAGNOSIS from answer and evidence', has(diagnose, '`status: found`', '`answer:`', '`evidence:`', '`DIAGNOSIS`'));
173
+ ok('Diagnose: inconclusive stays in diagnosis, needs_input asks the open questions',
174
+ has(diagnose, '`status: inconclusive`', 'stays in diagnosis', '`status: needs_input`', '`open questions:`'));
175
+ ok('research: no research/<name> branch anywhere', ![research, charting, researchAgent].some((t) => /research\/(<name>|&lt;name)/.test(t)));
176
+ ok('research: the report is the artifact, posted as a RESEARCH comment with every claim and its URL',
177
+ has(research, 'the report is the artifact', '`RESEARCH` comment', '`evidence:`', 'URL'));
178
+ ok('research: branches on found, inconclusive and needs_input',
179
+ has(research, '`status: found`', 'resolve the ticket', '`status: inconclusive`', 'accept the answer at that confidence, narrow the question, or run another round',
180
+ '`status: needs_input`', '`open questions:`', 're-spawn'));
181
+ ok('research: checked with --technique research', research.includes('--technique research'));
182
+ ok('charting: the research fan-out posts the report, no branch', has(charting, '`RESEARCH` comment', 'no branch'));
183
+ ok('research agent: identity returns findings in the report', researchAgent.includes('returning findings with their citations in your report') && researchAgent.includes('commit nothing'));
184
+ ok('codebase-map: each reader returns an investigating report checked with --technique codebase-map',
185
+ has(codebaseMap, 'investigating report', '--technique codebase-map'));
186
+ ok('codebase-map: distils the answer fields; inconclusive notes the gap; needs_input asked once then re-spawned or unmapped',
187
+ has(codebaseMap, 'distil** the `answer:` fields', '`status: inconclusive`', 'note the gap', '`status: needs_input`', 'once', 're-spawn that facet', 'unmapped'));
188
+ ok('debug: a Report line points at the investigating contract', has(debug, '**Report.**', 'investigating report', 'never commits or creates a branch', '`DIAGNOSIS`'));
189
+ ok('brief: the report names the implementing Return contract', has(brief, 'implementing `Return contract`', '`blockers:`'));
190
+
191
+ for (const [label, src] of [['techniques.md', techniques], ['verify.md', verify], ['code-review.md', codeReview], ['ticket-engines.md', engines], ['executor-brief.md', brief], ['research.md', research], ['codebase-map.md', codebaseMap], ['debug.md', debug], ['charting.md', charting]]) {
192
+ ok(`${label}: no em-dash`, !src.includes('—'));
193
+ }
194
+
195
+ console.log(`report-status-docs.test.js: ${passed} checks passed`);
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ // Docs test for #253: the skill prose names the Resolve bookkeeping helpers as the
3
+ // default path (trailhead-issue-body.js, trailhead-dashboard.js, and the one `clear`
4
+ // that also removes the session-ticket marker), while keeping the by-hand fallback.
5
+ // Run: node resolve-helpers-docs.test.js
6
+ const assert = require('assert');
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+
10
+ const skills = path.join(__dirname, '..', 'plugins', 'trailhead', 'skills');
11
+ const read = (rel) => fs.readFileSync(path.join(skills, rel), 'utf8');
12
+
13
+ let passed = 0;
14
+ const ok = (name, cond) => { assert.ok(cond, name); passed++; };
15
+
16
+ // file -> strings it must carry
17
+ const EXPECT = {
18
+ '_shared/substrate.md': ['trailhead-issue-body.js', 'trailhead-dashboard.js', 'when a helper is missing'],
19
+ '_shared/substrate-commands.md': ['trailhead-issue-body.js append --issue <map> --section "Decisions so far"', 'trailhead-dashboard.js', '--set-last-audit', 'fully generated'],
20
+ 'trailhead-work/references/ticket-engines.md': ['trailhead-issue-body.js append --issue <map> --section "Decisions so far"', 'trailhead-dashboard.js', 'trailhead-work-state.js clear --repo <working-root>'],
21
+ 'trailhead-work/SKILL.md': ['trailhead-issue-body.js append --issue <map> --section "Decisions so far"', 'trailhead-dashboard.js', 'trailhead-work-state.js clear --repo <working-root>',
22
+ 'trailhead-issue-body.js remove --issue <map> --section "Not yet specified" --match "<unique text of the entry>"'],
23
+ '_shared/session-handoff.md': ['trailhead-work-state.js clear --repo <working-root>', 'removes both'],
24
+ '_shared/teamwork.md': ['trailhead-issue-body.js append --issue <map> --section "Decisions so far"', 'sessionTicket', 'when the helper is missing'],
25
+ 'trailhead-view/SKILL.md': ['trailhead-dashboard.js', '--dry-run'],
26
+ 'trailhead-manage/references/auditing.md': ['trailhead-dashboard.js --set-last-audit'],
27
+ '_shared/configuration-reference.md': ['trailhead-issue-body.js replace --issue <conventions> --section :preamble'],
28
+ };
29
+
30
+ for (const [rel, needles] of Object.entries(EXPECT)) {
31
+ const text = read(rel);
32
+ for (const needle of needles) ok(`${rel} names: ${needle}`, text.includes(needle));
33
+ ok(`${rel} has no em-dash`, !text.includes('—'));
34
+ }
35
+
36
+ console.log(`resolve-helpers-docs.test.js: ${passed} checks passed`);
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env node
2
+ // Docs test for #244: the ticket-start scope confirm is skipped on a ticket this session
3
+ // already shaped. The rule is stated once (Work the map step 3); the quick bullet and the
4
+ // choices table only point at it.
5
+ // Run: node scope-confirm-skip-docs.test.js
6
+ const assert = require('assert');
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+
10
+ const skills = path.join(__dirname, '..', 'plugins', 'trailhead', 'skills');
11
+ const read = (rel) => fs.readFileSync(path.join(skills, rel), 'utf8');
12
+
13
+ let passed = 0;
14
+ const ok = (name, cond) => { assert.ok(cond, name); passed++; };
15
+
16
+ const ANCHOR = 'Skip the confirm on a ticket this session already shaped';
17
+ const DEFINITION = `**${ANCHOR}.**`;
18
+
19
+ const walk = (dir) => fs.readdirSync(dir, { withFileTypes: true }).flatMap((e) =>
20
+ e.isDirectory() ? walk(path.join(dir, e.name)) : [path.join(dir, e.name)]);
21
+
22
+ // 1. the bold definition exists exactly once, in trailhead-work/SKILL.md
23
+ const defs = walk(skills).filter((f) => fs.readFileSync(f, 'utf8').includes(DEFINITION));
24
+ ok('definition appears in exactly one file', defs.length === 1);
25
+ ok('definition lives in trailhead-work/SKILL.md', defs[0] === path.join(skills, 'trailhead-work', 'SKILL.md'));
26
+ const work = read('trailhead-work/SKILL.md');
27
+ ok('definition appears once in SKILL.md', work.split(DEFINITION).length === 2);
28
+
29
+ // 2. step 3 carries the normative fragments
30
+ const lines = work.split('\n');
31
+ const s = lines.findIndex((l) => l.startsWith('3. **Brief the ticket and confirm scope'));
32
+ const e = lines.findIndex((l, i) => i > s && l.startsWith('4. '));
33
+ ok('step 3 found', s >= 0 && e > s);
34
+ const step3 = lines.slice(s, e).join('\n');
35
+ ok('step 3 carries the definition', step3.includes(DEFINITION));
36
+ // match the fragments in the rule sub-bullet only, not the whole of step 3
37
+ const rule = lines.slice(s, e).find((l) => l.includes(DEFINITION));
38
+ for (const frag of [
39
+ "from the user's own request for it",
40
+ 'a Grilling round on this ticket concluded',
41
+ 'created in bulk',
42
+ 'earlier session',
43
+ '/clear',
44
+ 'resume',
45
+ 'one-line',
46
+ 'never inferred from tracker metadata',
47
+ '`auto` is unchanged',
48
+ ]) ok(`rule carries: ${frag}`, rule.includes(frag));
49
+
50
+ // 3. the quick bullet only points at the rule
51
+ const quick = lines.find((l) => l.startsWith('- **Briefs and confirms scope first'));
52
+ ok('quick bullet found', !!quick);
53
+ ok('quick bullet references the anchor', quick.includes(ANCHOR));
54
+ ok('quick bullet gates the menu on the rule', quick.includes('Whether the menu is posed at all follows'));
55
+ ok('quick "<text>" gets the note, no menu', quick.includes('one-line note, no menu'));
56
+ ok('quick bullet does not restate bulk exclusion', !quick.includes('created in bulk'));
57
+ ok('quick bullet does not restate grill trigger', !quick.includes('a Grilling round on this ticket concluded'));
58
+
59
+ // 4. the choices row only points at the rule
60
+ const row = read('_shared/choices.md').split('\n').find((l) => l.startsWith('| Ticket-start scope confirm'));
61
+ ok('choices row found', !!row);
62
+ ok('choices row references the anchor', row.includes(ANCHOR));
63
+ ok('choices row does not restate bulk exclusion', !row.includes('created in bulk'));
64
+
65
+ // 5. no em-dash in the touched files
66
+ for (const rel of ['trailhead-work/SKILL.md', '_shared/choices.md'])
67
+ ok(`${rel} has no em-dash`, !read(rel).includes('\u2014'));
68
+ ok('this test has no em-dash', !fs.readFileSync(__filename, 'utf8').includes('\u2014'));
69
+
70
+ console.log(`scope-confirm-skip-docs.test.js: ${passed} checks passed`);