@marcomigozzi/trailhead 0.16.1 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/README.it.md +82 -25
  2. package/README.md +82 -25
  3. package/bin/base-preflight-docs.test.js +4 -1
  4. package/bin/bulk-cross-reference-docs.test.js +36 -0
  5. package/bin/capture-action.test.js +167 -0
  6. package/bin/capture-docs.test.js +32 -0
  7. package/bin/engine-route-docs.test.js +155 -0
  8. package/bin/facet-stamp-docs.test.js +79 -0
  9. package/bin/forced-mutation-docs.test.js +38 -0
  10. package/bin/grill-model-docs.test.js +155 -0
  11. package/bin/grilling-holistic-review-docs.test.js +7 -3
  12. package/bin/guard-axis-docs.test.js +69 -0
  13. package/bin/inbox-needs-info-docs.test.js +68 -0
  14. package/bin/lib/codex-projection.js +28 -19
  15. package/bin/lib/codex-projection.test.js +77 -20
  16. package/bin/lib/host-descriptor.js +1 -1
  17. package/bin/lib/host-descriptor.test.js +6 -0
  18. package/bin/needs-you-docs.test.js +9 -2
  19. package/bin/options-in-chat-docs.test.js +65 -0
  20. package/bin/pick-stress-test-docs.test.js +1 -1
  21. package/bin/pipeline-review-docs.test.js +118 -0
  22. package/bin/plan-review-budget-docs.test.js +63 -0
  23. package/bin/repo-target-docs.test.js +41 -0
  24. package/bin/report-status-docs.test.js +10 -2
  25. package/bin/seam-coverage-docs.test.js +46 -0
  26. package/bin/session-trust-docs.test.js +37 -0
  27. package/bin/standards-brief-docs.test.js +112 -0
  28. package/bin/surfaced-scope-docs.test.js +57 -0
  29. package/bin/test-once-docs.test.js +101 -0
  30. package/bin/test-run-docs.test.js +47 -0
  31. package/bin/trailhead.js +15 -8
  32. package/bin/trailhead.test.js +71 -17
  33. package/bin/unblock-docs.test.js +56 -0
  34. package/bin/yes-accepts-recommended-docs.test.js +54 -0
  35. package/package.json +1 -1
  36. package/plugins/trailhead/.claude-plugin/plugin.json +1 -1
  37. package/plugins/trailhead/agents/trailhead-code-review.md +2 -1
  38. package/plugins/trailhead/agents/trailhead-codebase-map.md +1 -1
  39. package/plugins/trailhead/agents/trailhead-grill.md +41 -0
  40. package/plugins/trailhead/agents/trailhead-plan.md +1 -1
  41. package/plugins/trailhead/agents/trailhead-verify.md +1 -0
  42. package/plugins/trailhead/hooks/lib/audit-walk.test.js +62 -0
  43. package/plugins/trailhead/hooks/lib/capture.test.js +439 -0
  44. package/plugins/trailhead/hooks/lib/checkout-find.test.js +195 -0
  45. package/plugins/trailhead/hooks/lib/checkout-in.test.js +83 -7
  46. package/plugins/trailhead/hooks/lib/dashboard.test.js +19 -0
  47. package/plugins/trailhead/hooks/lib/gh-auth-preflight.js +89 -0
  48. package/plugins/trailhead/hooks/lib/gh-auth-preflight.test.js +132 -0
  49. package/plugins/trailhead/hooks/lib/git-env-mod.test.js +55 -0
  50. package/plugins/trailhead/hooks/lib/git-env-spawn.test.js +73 -0
  51. package/plugins/trailhead/hooks/lib/git-env.test.js +51 -0
  52. package/plugins/trailhead/hooks/lib/global-config-dir.test.js +48 -0
  53. package/plugins/trailhead/hooks/lib/heredoc-mod.test.js +85 -0
  54. package/plugins/trailhead/hooks/lib/inbox-needs-info.test.js +179 -0
  55. package/plugins/trailhead/hooks/lib/inherit-session.js +17 -0
  56. package/plugins/trailhead/hooks/lib/issue-body.test.js +165 -0
  57. package/plugins/trailhead/hooks/lib/model-defaults-review.js +22 -1
  58. package/plugins/trailhead/hooks/lib/model-defaults-review.test.js +64 -1
  59. package/plugins/trailhead/hooks/lib/model-route.js +207 -0
  60. package/plugins/trailhead/hooks/lib/model-route.test.js +231 -0
  61. package/plugins/trailhead/hooks/lib/needs-you.test.js +45 -1
  62. package/plugins/trailhead/hooks/lib/plan-review-rounds.test.js +272 -0
  63. package/plugins/trailhead/hooks/lib/repo-target.test.js +256 -0
  64. package/plugins/trailhead/hooks/lib/report-status.test.js +56 -1
  65. package/plugins/trailhead/hooks/lib/statusline-git-env.test.js +47 -0
  66. package/plugins/trailhead/hooks/lib/test-run.test.js +757 -0
  67. package/plugins/trailhead/hooks/lib/trailhead-git-env.js +40 -0
  68. package/plugins/trailhead/hooks/mod/README.md +42 -17
  69. package/plugins/trailhead/hooks/mod/auto-run.ts +2 -1
  70. package/plugins/trailhead/hooks/mod/git-env.ts +31 -0
  71. package/plugins/trailhead/hooks/mod/heredoc.ts +79 -0
  72. package/plugins/trailhead/hooks/mod/map-pane-view.ts +3 -1
  73. package/plugins/trailhead/hooks/mod/map-pane.tsx +204 -49
  74. package/plugins/trailhead/hooks/mod/my-tickets.ts +76 -0
  75. package/plugins/trailhead/hooks/mod/repo-pin.ts +113 -9
  76. package/plugins/trailhead/hooks/mod/work-state-locate.ts +2 -1
  77. package/plugins/trailhead/hooks/trailhead-bash-guards.js +8 -8
  78. package/plugins/trailhead/hooks/trailhead-bash-guards.test.js +28 -14
  79. package/plugins/trailhead/hooks/trailhead-check-update.js +20 -2
  80. package/plugins/trailhead/hooks/trailhead-check-update.test.js +60 -0
  81. package/plugins/trailhead/hooks/trailhead-labels-guard.js +2 -2
  82. package/plugins/trailhead/hooks/trailhead-labels-guard.test.js +1 -1
  83. package/plugins/trailhead/hooks/trailhead-test-guard.js +232 -0
  84. package/plugins/trailhead/hooks/trailhead-test-guard.test.js +172 -0
  85. package/plugins/trailhead/skills/_shared/capture-core.md +123 -0
  86. package/plugins/trailhead/skills/_shared/choices.md +2 -2
  87. package/plugins/trailhead/skills/_shared/configuration-reference.md +30 -16
  88. package/plugins/trailhead/skills/_shared/configuration.md +1 -1
  89. package/plugins/trailhead/skills/_shared/load-first.md +2 -0
  90. package/plugins/trailhead/skills/_shared/session-handoff.md +1 -1
  91. package/plugins/trailhead/skills/_shared/substrate-commands.md +19 -6
  92. package/plugins/trailhead/skills/_shared/substrate.md +4 -3
  93. package/plugins/trailhead/skills/_shared/surfaced-scope.md +23 -0
  94. package/plugins/trailhead/skills/_shared/teamwork.md +11 -5
  95. package/plugins/trailhead/skills/_shared/techniques/acceptance-testing.md +1 -1
  96. package/plugins/trailhead/skills/_shared/techniques/code-review-pipeline.md +24 -0
  97. package/plugins/trailhead/skills/_shared/techniques/code-review.md +22 -6
  98. package/plugins/trailhead/skills/_shared/techniques/codebase-map.md +6 -3
  99. package/plugins/trailhead/skills/_shared/techniques/debug.md +5 -2
  100. package/plugins/trailhead/skills/_shared/techniques/grilling.md +28 -3
  101. package/plugins/trailhead/skills/_shared/techniques/plan-review.md +16 -4
  102. package/plugins/trailhead/skills/_shared/techniques/research.md +4 -1
  103. package/plugins/trailhead/skills/_shared/techniques/tdd.md +4 -2
  104. package/plugins/trailhead/skills/_shared/techniques/verify.md +5 -1
  105. package/plugins/trailhead/skills/_shared/techniques.md +8 -6
  106. package/plugins/trailhead/skills/trailhead-capture/SKILL.md +2 -4
  107. package/plugins/trailhead/skills/trailhead-capture/references/capture.md +29 -4
  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 +11 -6
  111. package/plugins/trailhead/skills/trailhead-manage/references/auditing.md +18 -3
  112. package/plugins/trailhead/skills/trailhead-work/SKILL.md +12 -9
  113. package/plugins/trailhead/skills/trailhead-work/references/auto.md +3 -1
  114. package/plugins/trailhead/skills/trailhead-work/references/executor-brief.md +2 -0
  115. package/plugins/trailhead/skills/trailhead-work/references/ticket-engines.md +13 -13
  116. package/plugins/trailhead/templates/statusline-trailhead.sh +10 -3
  117. package/plugins/trailhead/templates/trailhead-audit-walk.js +56 -4
  118. package/plugins/trailhead/templates/trailhead-auto-merge.js +2 -1
  119. package/plugins/trailhead/templates/trailhead-auto-queue.js +2 -1
  120. package/plugins/trailhead/templates/trailhead-auto-slot.js +2 -1
  121. package/plugins/trailhead/templates/trailhead-base-preflight.js +10 -5
  122. package/plugins/trailhead/templates/trailhead-capture.js +419 -0
  123. package/plugins/trailhead/templates/trailhead-checkout-find.js +257 -0
  124. package/plugins/trailhead/templates/trailhead-checkout-in.js +78 -21
  125. package/plugins/trailhead/templates/trailhead-commit-msg-sync.js +2 -1
  126. package/plugins/trailhead/templates/trailhead-dashboard.js +15 -2
  127. package/plugins/trailhead/templates/trailhead-git-env.js +40 -0
  128. package/plugins/trailhead/templates/trailhead-inbox-needs-info.js +161 -0
  129. package/plugins/trailhead/templates/trailhead-isolation-answer.js +2 -1
  130. package/plugins/trailhead/templates/trailhead-isolation-copy.js +9 -4
  131. package/plugins/trailhead/templates/trailhead-issue-body.js +161 -12
  132. package/plugins/trailhead/templates/trailhead-merge-ticket.js +2 -1
  133. package/plugins/trailhead/templates/trailhead-needs-you.js +38 -4
  134. package/plugins/trailhead/templates/trailhead-plan-review-rounds.js +261 -7
  135. package/plugins/trailhead/templates/trailhead-repo-target.js +302 -0
  136. package/plugins/trailhead/templates/trailhead-report-status.js +57 -9
  137. package/plugins/trailhead/templates/trailhead-test-run.js +752 -0
  138. package/plugins/trailhead/templates/trailhead-uat-backfill.js +2 -1
  139. package/plugins/trailhead/templates/trailhead-uat-followup.js +2 -1
  140. package/plugins/trailhead/templates/trailhead-uat-setup.js +9 -4
  141. package/plugins/trailhead/templates/trailhead-unblock.js +2 -1
  142. package/plugins/trailhead/tests/auto-run-ui.test.ts +2 -1
  143. package/plugins/trailhead/tests/git-env.test.ts +20 -0
  144. package/plugins/trailhead/tests/map-pane-ui.test.ts +386 -50
  145. package/plugins/trailhead/tests/map-pane-view.test.ts +6 -0
  146. package/plugins/trailhead/tests/map-pane-write.test.ts +57 -1
  147. package/plugins/trailhead/tests/my-tickets.test.ts +66 -0
  148. package/plugins/trailhead/tests/repo-pin-late-repo.test.ts +3 -2
  149. package/plugins/trailhead/tests/repo-pin-relocate.test.ts +175 -0
  150. package/plugins/trailhead/tests/repo-pin.test.ts +20 -5
  151. package/plugins/trailhead/tests/unscrub.ts +8 -0
  152. package/plugins/trailhead/tests/work-state-prompt-hook.test.ts +3 -2
  153. package/plugins/trailhead/tests/work-state-worktree.test.ts +2 -1
  154. package/plugins/trailhead/types/index.d.ts +7 -0
@@ -1,5 +1,8 @@
1
1
  # Code review
2
- Runs after execute, before closing a `build`/`bug` ticket: as a subagent when the diff is non-trivial, inline for a tiny change. Dispatch the **`trailhead-code-review`** agent (read-only: it reviews and reports, it must never Edit/Write or commit), passing `config.models.review` as the model override; **never** an external-plugin reviewer agent that another installed tool registered, which follows a different protocol and may write its own review file instead of trailhead's `VERIFY` comment.
2
+
3
+ **First, route it.** Run `node <hooks>/lib/model-route.js review --session <your full model id> <project root>` and obey its `route`. Your full model id is in your system prompt or environment ("The exact model ID is ..."); pass `unknown` only if it is truly absent. Obey it exactly: never decide inline versus dispatch by hand, never read the config yourself to decide. On `route: dispatch` spawn the `agent` it returns with model `alias` (`agent_type=<agent>` on Codex) and use its report; on `route: inline` run the protocol below in the session. On `route: dispatch` the spawn prompt also carries, with the protocol, one line `pipeline reference: <absolute path>`, the path being `code-review-pipeline.md` in the same directory as the `code-review.md` you just read by absolute path (never the reference's contents), and one line `standards files: <list>`, built per **Standards files** below. Finding `<hooks>`, the flags and the two exceptions are in **Route every step through model-route.js** in `../techniques.md`. (If you are the `trailhead-code-review` agent, skip this gate: you are the dispatched step.)
4
+
5
+ Runs after execute, before closing a `build`/`bug` ticket: as the routed step (a subagent on `route: dispatch`, in the session on `route: inline`; an accepted `lean` skip's lighter pass is also inline). On `route: dispatch` it is the **`trailhead-code-review`** agent (read-only: it reviews and reports, it must never Edit/Write or commit), with the route's `alias` as the model override; **never** an external-plugin reviewer agent that another installed tool registered, which follows a different protocol and may write its own review file instead of trailhead's `VERIFY` comment.
3
6
 
4
7
  **Review adversarially; don't defer to authority.** A review subagent still loads the project/global `CLAUDE.md` (Claude Code doesn't strip it per-spawn; auto-memory, though, is *not* inherited). Treat those conventions (and any "this decision is settled / final / don't revisit" note) as **non-authoritative for your verdict**: judge the diff against the ticket's spec and the code itself, never rubber-stamp because a memory or a CLAUDE.md says a choice is closed. Convention endorsement may suppress a *style* smell; it may **never** suppress a correctness or security finding. The point of an independent review is to disagree when the code warrants it.
5
8
 
@@ -11,16 +14,29 @@ git -C <repo> log --reverse --basic-regexp --grep='^Refs: #<n>$' --format=%H HEA
11
14
 
12
15
  git applies `--grep` per message line, so the `^...$` anchors match only the whole trailer line: `#<n>` never matches `#<n>0` or a mid-sentence mention, and `--basic-regexp` overrides any user `grep.patternType`. The set may be non-contiguous (other tickets' commits interleaved on the trunk), so consume it **per commit**, oldest first, with `git -C <repo> show --remerge-diff <sha>` (git 2.36+): for a regular commit that is its plain patch; for a trunk-sync merge carrying the trailer it diffs git's automatic re-merge against the recorded result, so it shows exactly the conflict resolution the ticket authored and nothing the merge brought in cleanly (an octopus merge carrying the trailer is surfaced to the user, not judged). The per-commit patches define what is attributed to the ticket; read the touched files at `HEAD` only to judge how the change integrates, and treat later foreign edits there as context, not findings. Never collapse the set into one `git diff <first>^..<last>` span, which re-includes the interleaved foreign changes. A commit without the trailer is, by this rule, not part of the ticket's diff; only an entirely empty set is detectable. **An empty set is an error**: stop and surface it (name the ticket and the command run), never fall back silently to a base range or the whole tree. Recompute the set on every review round and every verify run, never reuse a cached list: fix commits carry the same trailer, so the set grows with each fix-and-re-review round.
13
16
 
14
- Review on four axes:
17
+ **Standards files: list them before the review.** So the Standards & spec axis reads every documented standard in the repo, not whichever ones the reviewer happens to find, list them up front, on every pass including the lean inline one, with exactly this command (`<repo>` as in **Ticket diff**; a tracked file only, at any depth):
18
+
19
+ ```
20
+ git -C <repo> ls-files -- ':(glob,icase)**/CONTRIBUTING*' ':(glob,icase)**/CODING_STANDARDS*' ':(glob)**/AGENTS.md' ':(glob)**/CLAUDE.md' ':(glob)**/.editorconfig' ':(glob)**/.eslintrc*' ':(glob)**/eslint.config.*' ':(glob)**/.prettierrc*' ':(glob)**/prettier.config.*' ':(glob)**/biome.json*' ':(glob)**/.stylelintrc*' ':(glob)**/.markdownlint*' ':(glob)**/ruff.toml' ':(glob)**/.ruff.toml' ':(glob)**/.flake8' ':(glob)**/.pylintrc' ':(glob)**/pyproject.toml' ':(glob)**/.rubocop.yml' ':(glob)**/.golangci.*' ':(glob)**/rustfmt.toml' ':(glob)**/.rustfmt.toml' ':(glob)**/clippy.toml' ':(glob)**/.clang-format' ':(glob)**/.shellcheckrc'
21
+ ```
22
+
23
+ Then add the repo's `trailhead:conventions` issue as `issue #<n>`, looked up per **Find a singleton issue** in `../substrate-commands.md` (a repo without one simply has no such entry). On `route: dispatch` the orchestrator does this before the spawn and the spawn prompt carries the result as one line, `standards files: <list>`, comma-separated absolute paths (each listed path joined to `<repo>`) plus the issue entry, the paths only, never their contents; an empty result is `standards files: none`. Recompute the list on every review round, as with the **Ticket diff** set: a fix commit may touch a standard. On `route: inline` the session runs the listing and reads the files itself. The reviewer reads every entry before judging the axis (the issue with `gh issue view <n> --json body`), and each standards finding cites the file (or the issue) and the line it applies; with no such citation it is not a standards finding. A linter or formatter config also tells the reviewer what tooling already enforces, which the axis skips. This does not lift **Review adversarially** above: a listed file may suppress a style smell, never a correctness or security finding. With no `standards files:` line in the prompt, the reviewer runs the same listing itself, never skips it; an entry it cannot read is named in the report, not guessed at. `standards files: none` means the repo documents no standard: the axis then judges the spec alone.
24
+
25
+ Review on five axes:
15
26
 
16
27
  - **Correctness** (real bugs): broken logic, unhandled errors, edge cases, race conditions, wrong data flow.
17
28
  - **Security**: injection, secret leakage, auth/authz gaps, unvalidated boundary input.
18
- - **Reuse & simplification**: hunt the classic smells, each a *judgement call* (never a hard violation), and for each name it → say how to simplify: duplicated code (extract), mysterious name (rename), primitive obsession & data clumps (introduce a type), long parameter list, feature envy / message chains (move or collapse), repeated switches, speculative generality (delete), shotgun surgery & divergent change, middle-man / shallow pass-through (delete or deepen). Suppress any a documented standard endorses; skip what tooling enforces.
19
- - **Standards & spec**: does it match the repo's documented conventions AND do what the ticket actually asked (missing requirements, scope creep, plausibly-wrong implementations)? Suppress smells a documented standard endorses; skip anything tooling already enforces.
29
+ - **Reuse & simplification**: hunt the classic smells, each a *judgement call* (never a hard violation), and for each name it → say how to simplify: duplicated code (extract), mysterious name (rename), primitive obsession & data clumps (introduce a type), long parameter list, feature envy / message chains (move or collapse), repeated switches, speculative generality (delete; see **Impossible-state guards**), shotgun surgery & divergent change, middle-man / shallow pass-through (delete or deepen). Suppress any a documented standard endorses; skip what tooling enforces.
30
+ - **Standards & spec**: does it match the repo's documented conventions (read the **Standards files** listed above and cite the file for each finding) AND do what the ticket actually asked (missing requirements, scope creep, plausibly-wrong implementations)? Suppress smells a documented standard endorses; skip anything tooling already enforces.
31
+ - **Impossible-state guards**: reachable code that defends against a problem that cannot occur in this project's stack. Subtypes: a guard on an impossible state; defence already enforced elsewhere; a pattern copied without its premises; a stale or phantom requirement; an abstraction with one instance (this extends the speculative generality smell). The guard's own code must be reachable (live), while the state it guards against must be shown impossible. Every finding cites concrete evidence (file:line or a doc) that the state cannot occur: a closed type or enum, an API or framework invariant, fixed configuration, validation already done upstream, or a guarantee of the stack itself. Without such evidence there is no finding, never the reviewer's say-so. Severity is at most Warning, never Critical, so it never enters the Critical-only fix-and-re-review loop. Not findings: trust boundaries (user input, network, files, env), deliberate defence-in-depth, test and DI seams, and dead code (a separate smell, not this axis). The exclusions win over the evidence: no evidence, upstream validation included, turns a check at a trust boundary into a finding.
32
+
33
+ **Pipeline reliability (only on a pipeline-touching diff)**: list the paths the ticket's commits touch (`git -C <repo> show --format= --name-only <sha>` per commit, the same set as the Ticket diff). When one matches `.github/workflows/*`, `Dockerfile*`, `docker-compose*` (basename, at any depth) or a deploy script (a script with `deploy` in its file name; a release script under another name is not matched), read `code-review-pipeline.md` and apply it, on every pass including the lean inline one; otherwise do not read it. Dispatched, read it at the path the spawn prompt names. Inline, it sits beside this file. It is not a sixth axis: the findings use the same IDs, counts line and ledger. If a path matches and the reference cannot be read (no path passed, or unreadable), the review is incomplete and fails closed: the report carries a Critical finding `pipeline checks did not run` naming the matched path, so its status is `issues_found` and Resolve stays blocked. The orchestrator discards that incomplete report (never posted, no fix round spent, the finding never enters the ledger, so the Disposition gate is untouched) and re-spawns with the path. If the reference is still unreadable, it surfaces a HITL checkpoint naming the path, and Resolve stays blocked. Inline, the session surfaces that checkpoint directly.
34
+
35
+ Classify each finding: **Critical** (bug/security/breaks the spec) → **Warning** (should fix) → **Info** (judgement call). Before reporting ANY finding, **verify it adversarially**: re-read the actual code, confirm the failing path is reachable and the claim holds (except for Impossible-state guards, where what you verify is that the state cannot occur, not a reachable failing path; Impossible-state guards findings are at most Warning, never Critical, so they never enter the Critical-only fix-and-re-review loop). Discard plausible-but-wrong findings; an unverified finding is worse than none. Quote the offending hunk (and the standard/spec line) per finding. **Stable finding IDs**: each verified finding gets an ID the first time it's reported, `C1`, `W1`, `I1`, ..., by severity, numbered in discovery order, and keeps that ID across every round of the fix-and-re-review loop below; a re-review that finds the same issue still open reuses its ID, a genuinely new finding takes the next number in its severity. An ID's severity is fixed at first report: if a re-review reclassifies a finding to a different severity, mark the old ID `reclassified as <new ID>` in the ledger and continue the finding under a new ID of the new severity; a `reclassified` line is a pointer, not a disposition, and is left out of the counts, so the finding counts once, under its current severity. Return the report (the agent never posts it; the orchestrator posts it as the `VERIFY` comment after the report-status check), **led by a machine-checkable status tag on its own first line**: `status: clean` when no **Critical** finding remains (nothing blocks Resolve), or `status: issues_found` when one or more Criticals are still open (the caller must iterate the fix-and-re-review loop below, or run the HITL checkpoint, before resolving). Warnings/Info alone leave the tag `status: clean`, since they don't gate resolution. Emit that line verbatim as the report's first line.
20
36
 
21
- Classify each finding: **Critical** (bug/security/breaks the spec) → **Warning** (should fix) → **Info** (judgement call). Before reporting ANY finding, **verify it adversarially**: re-read the actual code, confirm the failing path is reachable and the claim holds. Discard plausible-but-wrong findings; an unverified finding is worse than none. Quote the offending hunk (and the standard/spec line) per finding. **Stable finding IDs**: each verified finding gets an ID the first time it's reported, `C1`, `W1`, `I1`, ..., by severity, numbered in discovery order, and keeps that ID across every round of the fix-and-re-review loop below; a re-review that finds the same issue still open reuses its ID, a genuinely new finding takes the next number in its severity. An ID's severity is fixed at first report: if a re-review reclassifies a finding to a different severity, mark the old ID `reclassified as <new ID>` in the ledger and continue the finding under a new ID of the new severity; a `reclassified` line is a pointer, not a disposition, and is left out of the counts, so the finding counts once, under its current severity. Return the report (the agent never posts it; the orchestrator posts it as the `VERIFY` comment after the report-status check), **led by a machine-checkable status tag on its own first line**: `status: clean` when no **Critical** finding remains (nothing blocks Resolve), or `status: issues_found` when one or more Criticals are still open (the caller must iterate the fix-and-re-review loop below, or run the HITL checkpoint, before resolving). Warnings/Info alone leave the tag `status: clean`, since they don't gate resolution. Emit that line verbatim as the report's first line.
37
+ **Counts line, right under `status:`.** The comment's second line, right under `status:`, is the cumulative severity count: `critical: <n> · warning: <n> · info: <n>` (zeros written out, never omitted), the count of distinct findings (by ID, excluding `reclassified` pointers) reported on this ticket so far, across every round. Cumulative means a finding fixed in an earlier round still counts here, so the totals always match the ledger below. Below the counts comes the **findings ledger** (authoritative for this ticket), one line per finding ID with its disposition: `fixed (<commit>)`, `deferred → <ticket>` (a ticket that exists, named and linked: an already-open ticket, or one opened on the user's explicit yes per **Scope that surfaces while working a ticket** (`../surfaced-scope.md`), never unasked; when the user declines or leaves it unanswered, and under an autonomous run, where no ticket is opened unasked, use `accepted: <one-line reason>` instead), `accepted: <one-line reason>`, or `open` while the disposition is still undecided. The ledger lives in the latest `VERIFY` comment of the ticket and is rewritten each round; the resolution comment links to it rather than duplicating it. Then the findings grouped by severity with file:line and the fix, as before, so the Verify step branches on the tag without parsing prose. A `status: clean` report whose counts line has Critical above zero must carry a ledger line closing each Critical (`fixed`, `deferred` or `accepted`); otherwise the report-status check fails it closed to `issues_found`.
22
38
 
23
- **Counts line, right under `status:`.** The comment's second line, right under `status:`, is the cumulative severity count: `critical: <n> · warning: <n> · info: <n>` (zeros written out, never omitted), the count of distinct findings (by ID, excluding `reclassified` pointers) reported on this ticket so far, across every round. Cumulative means a finding fixed in an earlier round still counts here, so the totals always match the ledger below. Below the counts comes the **findings ledger** (authoritative for this ticket), one line per finding ID with its disposition: `fixed (<commit>)`, `deferred → <ticket>` (a ticket that exists, named and linked: an already-open ticket, or one opened on the user's explicit yes per **Scope that surfaces while working a ticket** (served by the capture cluster), never unasked; when the user declines or leaves it unanswered, and under an autonomous run, where no ticket is opened unasked, use `accepted: <one-line reason>` instead), `accepted: <one-line reason>`, or `open` while the disposition is still undecided. The ledger lives in the latest `VERIFY` comment of the ticket and is rewritten each round; the resolution comment links to it rather than duplicating it. Then the findings grouped by severity with file:line and the fix, as before, so the Verify step branches on the tag without parsing prose. A `status: clean` report whose counts line has Critical above zero must carry a ledger line closing each Critical (`fixed`, `deferred` or `accepted`); otherwise the report-status check fails it closed to `issues_found`.
39
+ **Test record.** When the orchestrator passes a test record (`test record: ...`, the full-suite outcome of Verify), read it and never re-run the full suite (a focused test to probe one finding is fine); with no record passed, test as today (without `testing.full` no test record is passed).
24
40
 
25
41
  **Converge on the blockers with a bounded budget and an explicit boundary.** A **Critical** finding is *blocking*: it must be fixed before Resolve. Fixing one can introduce or reveal another, so the review is a **loop, not a single pass**: fix every Critical, then re-review the recomputed **Ticket diff** set, up to `config.code_review.rounds` (default 2) fix-and-re-review rounds. Stop when no Critical remains, then Resolve, giving each remaining Warning/Info its disposition in the ledger. **If the rounds are exhausted with Critical findings still open**, never resolve silently and never silently keep going: surface an explicit HITL checkpoint in the chat, **name each surviving blocker**, and put the choice to the user, (a) **run another 2 review rounds** on the remaining blockers (the point of this ask), or (b) **proceed with the blockers recorded** (each surviving Critical is recorded in the ledger as `deferred` or `accepted`), or (c) **run until it converges** (keep the fix + re-review loop going with no round cap, stopping only when no Critical remains; as a guard against an endless loop, if a round makes no progress, i.e. the same Critical findings come back unfixed, stop and bring the choice back to this checkpoint, naming them). **Wait for their pick; never auto-advance in either direction over open blocking findings.** (This mirrors **Cross-AI plan review**'s boundary ask: see `plan-review.md` step 4.)
26
42
 
@@ -1,5 +1,8 @@
1
1
  # Codebase map (fan-out)
2
- One-time at project adoption. Fan out 5 read-only reader subagents in parallel (single message), each owning ONE non-overlapping facet; no facet reads another's territory. Dispatch each reader as the **`trailhead-codebase-map`** agent (read-only by construction); **never** an external-plugin mapper agent that another installed tool registered.
2
+
3
+ **First, route it.** Run `node <hooks>/lib/model-route.js codebase-map --session <your full model id> <project root>` and obey its `route`. Your full model id is in your system prompt or environment ("The exact model ID is ..."); pass `unknown` only if it is truly absent. Obey it exactly: never decide inline versus dispatch by hand, never read the config yourself to decide. On `route: dispatch` spawn the `agent` it returns with model `alias` (`agent_type=<agent>` on Codex) and use its report; on `route: inline` run the protocol below in the session. Finding `<hooks>`, the flags and the two exceptions are in **Route every step through model-route.js** in `../techniques.md`. (If you are the `trailhead-codebase-map` agent, skip this gate: you are the dispatched step.)
4
+
5
+ One-time at project adoption. Fan out 5 read-only reader subagents in parallel (single message), each owning ONE non-overlapping facet; no facet reads another's territory. Route once for the whole fan-out; on `route: dispatch` each reader is the **`trailhead-codebase-map`** agent (read-only by construction), with the route's `alias` as the model override; **never** an external-plugin mapper agent that another installed tool registered.
3
6
 
4
7
  **The repo to map may be handed in by absolute path** (the scan root), so **never assume the current working directory is that repo**: search and read it by absolute path and run git as `git -C <scan root> ...`, and **never `cd` (or `pushd`/`env -C`) into it in any shape** (no `cd <root> && grep <relative>`, no bare `cd <root>` line then a relative read, no heredoc/multi-statement block, and not as a standalone `cd <root>` in an earlier Bash call: the cwd persists across calls). This keeps every read target statically determinable, so a bypass-permissions scan of an out-of-tree repo runs without a manual approval prompt. See **Search-command hygiene** in `../techniques.md`.
5
8
 
@@ -9,7 +12,7 @@ One-time at project adoption. Fan out 5 read-only reader subagents in parallel (
9
12
  - **Risk & complexity hotspots**: tech debt, fragile/God files, security-sensitive paths, TODO/FIXME clusters, expensive-to-change areas.
10
13
  - **Test & build setup**: framework, test layout, mocking style, how to run tests/build/lint, coverage posture.
11
14
 
12
- Give each agent read-only tools only (Read/Grep/Glob, never Edit/Write), a tight facet brief, and instruction to cite concrete file paths in backticks and return an investigating report (`Return contract` in its definition) whose `answer:` is a compact summary (~10–20 lines), NOT file dumps, with `evidence:` as `file:line` paths. Keep facets disjoint so contexts don't collide; while they run, wait for all five. Run the report-status check on each report (`--technique codebase-map`, the *Malformed review report* protocol in `../techniques.md`) and read its `fields`: `status: found` -> use its `answer:`; `status: inconclusive` -> use its `answer:` too and note the gap in that facet; `status: needs_input` -> ask the user its `open questions:` once, then re-spawn that facet with the answers, or mark the facet unmapped if they cannot answer. Then **distil** the `answer:` fields (don't concatenate) into ONE compact write-up (a few lines per facet, load-bearing paths preserved) and post it as the body of the repo's **`trailhead:codebase` issue**: a single per-repo issue (label `trailhead:codebase`), created if absent, updated if present. It is **not** in any map's Notes: it's repo-scoped, so every map of the repo links it and a finished map never strands it (each map's Notes carries just a link). The raw per-facet output is throwaway scratch: mine it, discard it. Do NOT persist per-facet reports as project state, do NOT re-run per ticket or per map.
15
+ Give each agent read-only tools only (Read/Grep/Glob, never Edit/Write), a tight facet brief, and instruction to cite concrete file paths in backticks and return an investigating report (`Return contract` in its definition) whose `answer:` is a compact summary (~10–20 lines), NOT file dumps, with `evidence:` as `file:line` paths. Keep facets disjoint so contexts don't collide; while they run, wait for all five. Run the report-status check on each report (`--technique codebase-map`, the *Malformed review report* protocol in `../techniques.md`) and read its `fields`: `status: found` -> use its `answer:`; `status: inconclusive` -> use its `answer:` too and note the gap in that facet; `status: needs_input` -> ask the user its `open questions:` once, then re-spawn that facet with the answers, or mark the facet unmapped if they cannot answer. Then **distil** the `answer:` fields (don't concatenate) into ONE compact write-up (a few lines per facet, load-bearing paths preserved) and **stamp every facet**: each `## ` facet opens with `_as of <short sha>_` and a blank line, the sha from `git -C <scan root> rev-parse --short HEAD`, the commit the facet is true at; a full re-run restamps all of them. Post the write-up as the body of the repo's **`trailhead:codebase` issue**: a single per-repo issue (label `trailhead:codebase`), created if absent, updated if present. It is **not** in any map's Notes: it's repo-scoped, so every map of the repo links it and a finished map never strands it (each map's Notes carries just a link). The raw per-facet output is throwaway scratch: mine it, discard it. Do NOT persist per-facet reports as project state, do NOT re-run per ticket or per map.
13
16
 
14
- **Keeping it fresh (incremental, not a re-run).** The full fan-out happens once. After that the issue is kept current **at Resolve**: when a resolved `build`/`bug` ticket materially changes the map (a new module or layer, a new dependency/integration, a new seam, a changed build/test command, a new convention), **patch just the affected facet in place**, a line or two, as part of that ticket's Resolve. Leave it untouched for cosmetic or in-facet changes. A **full re-run** is reserved for major drift (a rewrite, a stack change, an architecture the incremental patches no longer describe).
17
+ **Keeping it fresh (incremental, not a re-run).** The full fan-out happens once. After that the issue is kept current **at Resolve**: when a resolved `build`/`bug` ticket materially changes the map (a new module or layer, a new dependency/integration, a new seam, a changed build/test command, a new convention), **patch just the affected facet in place**, a line or two, as part of that ticket's Resolve. Leave it untouched for cosmetic or in-facet changes. Patch with `node <templates>/trailhead-issue-body.js ... --section "<facet>" --stamp <short sha>`: the stamp moves with the patch, a facet with no stamp gains it on its next patch, and an untouched facet keeps its old stamp. The audit flags a facet whose cited files changed after its stamp. A **full re-run** is reserved for major drift (a rewrite, a stack change, an architecture the incremental patches no longer describe).
15
18
 
@@ -1,7 +1,10 @@
1
1
  # Debug (scientific method)
2
+
3
+ **First, route it.** Run `node <hooks>/lib/model-route.js debug --session <your full model id> <project root>` and obey its `route`. Your full model id is in your system prompt or environment ("The exact model ID is ..."); pass `unknown` only if it is truly absent. Obey it exactly: never decide inline versus dispatch by hand, never read the config yourself to decide. On `route: dispatch` spawn the `agent` it returns with model `alias` (`agent_type=<agent>` on Codex) and use its report; on `route: inline` run the protocol below in the session. Finding `<hooks>`, the flags and the two exceptions are in **Route every step through model-route.js** in `../techniques.md`. (If you are the `trailhead-debug` agent, skip this gate: you are the dispatched step.)
4
+
2
5
  A bug is a science problem: observe, hypothesize, test, confirm, verify. Never patch on a hunch: an unconfirmed fix is a guess that recurs. Treat the issue as persistent debug state: log every hypothesis, test, and eliminated cause as comments, so a context reset never restarts the investigation.
3
6
 
4
- When run as a subagent, dispatch the **`trailhead-debug`** agent (full tools: it adds instrumentation and regression tests, so it needs Write), passing `config.models.debug` as the model override; **never** an external-plugin debugger agent that another installed tool registered.
7
+ When run as a subagent (`route: dispatch`), it is the **`trailhead-debug`** agent (full tools: it adds instrumentation and regression tests, so it needs Write), with the route's `alias` as the model override; **never** an external-plugin debugger agent that another installed tool registered.
5
8
 
6
9
  **Report.** The agent returns an investigating report (`Return contract` in its definition): `answer:` the confirmed cause stated as a falsifiable hypothesis, `evidence:` the falsification test and its result, `open questions:` what stays unsettled. It never commits or creates a branch: the orchestrator turns the report into the `DIAGNOSIS`.
7
10
 
@@ -18,5 +21,5 @@ When run as a subagent, dispatch the **`trailhead-debug`** agent (full tools: it
18
21
 
19
22
  **Pre-fix checkpoint: a confirmed cause, or no fix.** Before any fix is written, the `DIAGNOSIS` must record (1) the root cause stated as a *falsifiable* hypothesis and (2) the falsification test whose result would disprove it, actually run with the prediction holding. A cause that has not survived its own falsification test is still a guess: stay in diagnosis (loop steps 5–8, generating new hypotheses), never proceed to Fix. This is the hard gate on the `bug` engine's Diagnose→Fix boundary: only a confirmed cause graduates to the fix.
20
23
 
21
- 9. **Verify the fix.** State the oracle before the assertion (specified > derived/model > metamorphic > implicit-crash; never settle for "didn't crash"). Write the regression test at a correct seam exercising the real bug pattern; if none can exist, that absence IS a finding: flag it. Watch it fail, apply the fix, watch it pass. Then: original repro gone, full suite green, boundary neighbours (N-1/N/N+1, empty/singleton) pass, all `[DEBUG-]` probes and throwaway harnesses removed, winning hypothesis recorded in the commit.
24
+ 9. **Verify the fix.** State the oracle before the assertion (specified > derived/model > metamorphic > implicit-crash; never settle for "didn't crash"). Write the regression test at a correct seam exercising the real bug pattern; if none can exist, that absence IS a finding: flag it. Watch it fail, apply the fix, watch it pass. When that red comes from a forced change (the fix reverted, code or a fixture mutated), prove the change landed before trusting the run: diff the mutated file against a clean copy, or re-read it, and see the mutation there. A mutation that silently failed to apply leaves the code as it was, so the run proves nothing: a red then has another cause, and a green says nothing about the test. Then: original repro gone, full suite green (through `node <templates>/trailhead-test-run.js run --repo <working-root> --config-root <project root>` when `testing.full` is set; inside the `bug` engine Debug and Fix run focused tests only and that full run is the one at Verify; standalone Debug runs it once through the helper, and a later Verify on the same tree is a cache hit), boundary neighbours (N-1/N/N+1, empty/singleton) pass, all `[DEBUG-]` probes and throwaway harnesses removed, winning hypothesis recorded in the commit.
22
25
 
@@ -1,17 +1,42 @@
1
1
  # Grilling
2
2
  Interrogate the human live, one question at a time, until the decision is unambiguous and its rationale is captured. This is human-in-the-loop: the decision is theirs, so never answer on their behalf, never assume an answer you haven't heard, never proceed on silence.
3
3
 
4
+ **First, before any question: route the round.** Run `node <hooks>/lib/model-route.js grill --session <your full model id> <project root>` and obey its `route`. Your full model id is in your system prompt or environment ("The exact model ID is ..."); pass `unknown` only if it is truly absent. Never decide inline versus dispatch by hand, never skip the helper, never read the config yourself to decide. On `route: dispatch` you do not write the questions yourself: spawn `agent` with model `alias` and relay its report. On `route: inline`, continue with the bullets below. Finding `<hooks>`, the flags, each round and the checker are in **Who runs a round** at the end of this file. (If you are the `trailhead-grill` agent, skip this gate: you are the dispatched round.)
5
+
4
6
  - **Map the decision as a tree.** Every choice branches into the choices that hang off it. The *frontier* is every question whose prerequisites are already settled: the ones you can ask now without guessing. A question whose answer depends on another still-open one isn't on the frontier yet; it arrives there the moment that answer lands. You don't ask the frontier in a batch: you pick one from it, and recompute after the answer.
5
7
  - **Breadth before depth: enumerate the whole surface first.** When the subject is a space with many parts (mapping an app or a project's scope, not a single isolated choice), open by enumerating every functional area it implies (for a video player: media library, search, thumbnails, filters, genres, the player itself, ...) and get them all onto the record before deep-diving any one. Depth on the current thread is seductive, but the tree only ever grows the questions you actually raise, so an area you never name never enters it and stays silently missing. Fan out first (name every area), then converge each. The deliverable is coverage of the whole surface, not a deep answer on one corner of it.
6
- - **One question at a time, with your pick.** Number the question, state it, and offer your recommended answer, stress-tested per the next bullet. Then stop and wait. Each answer reshapes the tree: settled decisions push the frontier outward. Recompute and ask the next. Grilling stays human-decides: the **grilled questions themselves never offer the delegate option** (a recommended answer is not the delegate option), because converging a decision is the human's. A separate meta-ask a call-site may put *before* grilling (e.g. the `build`/`quick` Discuss "do you want a grilling round?") is its own advisory choice and can offer delegate/defer per that call-site. **When a grilled decision is genuinely blocked on a prerequisite, offer defer in prose** (state it in words as part of the question), per the recipe in `../choices.md`.
8
+ - **One question at a time, with your pick.** Number the question, state it, and offer your recommended answer, stress-tested per the next bullet. **A yes accepts the recommendation.** Word every question around your pick, so a plain "yes" accepts your pick: "Use Postgres for the queue? The alternative is Redis, ...", never an open "should we A or B?" followed by a recommendation, where a yes could mean either. A yes to any grilling question has exactly one meaning; any other answer names what they want instead. A play-back follows the same rule: your restated understanding is the pick, so a yes confirms it. Then stop and wait. Each answer reshapes the tree: settled decisions push the frontier outward. Recompute and ask the next. Grilling stays human-decides: the **grilled questions themselves never offer the delegate option** (a recommended answer is not the delegate option), because converging a decision is the human's. A separate meta-ask a call-site may put *before* grilling (e.g. the `build`/`quick` Discuss "do you want a grilling round?") is its own advisory choice and can offer delegate/defer per that call-site. **When a grilled decision is genuinely blocked on a prerequisite, offer defer in prose** (state it in words as part of the question), per the recipe in `../choices.md`.
7
9
  - **Stress-test your pick before offering it**, per **Stress-test the pick before offering it** in `../choices.md` (one pass, its `Weak point:` line shown with the pick). In grilling, its fit check names the earlier questions it touches by question number; it stays a local check, since the whole set is weighed later, in the holistic review below.
8
10
  - **Facts are your job, decisions are theirs.** When a question needs a fact from the environment (filesystem, code, tools), go find it yourself; never ask the human what you could look up. Only questions downstream of that lookup wait; ask the rest now.
9
11
  - **Refuse vague and hedged answers.** "Good," "fast," "simple," "users," "later" are not answers: probe. *Fast how: sub-second, or handles large datasets? Users: the Customer or the operator?* Make the abstract concrete: "walk me through it," "give me an example," "what does that actually look like?"
10
- - **Offer concrete options to react to, and show them, not just name them.** Present 2–4 sharp interpretations or specific choices that reveal priorities: never generic buckets ("technical/business/other") or leading options. **Naming the options is not enough: illustrate each one before asking the human to choose**, so the alternatives are legible without the user having to ask. For each option say what it is, what it optimizes for, and what it sacrifices; for a choice spanning several dimensions, lay the options out side by side (a comparison table or widget) as the natural default, rather than listing A/B/C inline in the question and waiting for the user to request the detail. But when the human signals they want to explain in their own words, drop the options and let them; resume structured questions after.
12
+ - **Offer concrete options to react to, and show them, not just name them.** Present 2–4 sharp interpretations or specific choices that reveal priorities: never generic buckets ("technical/business/other") or leading options. **Naming the options is not enough: illustrate each one before asking the human to choose**, so the alternatives are legible without the user having to ask. For each option say what it is, what it optimizes for, and what it sacrifices; for a choice spanning several dimensions, lay the options out side by side (a comparison table or widget) as the natural default, rather than listing A/B/C inline in the question and waiting for the user to request the detail. **The ask is answerable from the chat alone.** Options posted on the ticket (an `OPTIONS` comment, or any comment) are the record, never the ask surface: the turn that asks the human to pick renders every option asked about in chat (its label, a one-line summary, its trade-off, plus the recommendation), or offers them as choices of the host's structured question tool; never "I posted the options on the ticket, pick A1..An" with no option text. But when the human signals they want to explain in their own words, drop the options and let them; resume structured questions after.
11
13
  - **Surface hidden assumptions.** Say the unstated premise out loud and put it to them: "this only works if X is true, is it?" Cross-check claims against the code; when they contradict, name the contradiction and force a choice.
12
14
  - **Steelman and play devil's advocate.** State the strongest case for the option they're leaning away from. Attack the option they favor from its weakest seam. If it survives, the decision is stronger; if it cracks, you found the real question.
13
15
  - **Detect and open up tradeoffs.** When "or / versus / torn between / which is better" appears, pause: name what each side optimizes for, what each sacrifices, which aligns with the goal, then recommend with reasoning. Skip for trivial or already-decided choices.
14
16
  - **Play back your understanding** before closing each branch: restate the decision and its rationale in your words and have them confirm or correct.
15
17
  - **Stop only when the frontier is empty**: every branch visited, nothing silently assumed, the choice unambiguous *and* the why recorded. **For a multi-part space** (per the breadth bullet above) also require every functional area to have been enumerated and visited: an empty frontier reached while whole areas were never raised is a false finish, because the tree only ever grew the questions you raised. A single isolated choice has no areas to enumerate, so its empty frontier is a real finish. The empty frontier is then followed by the **Holistic review before the final confirmation** below: the final set is not confirmed or recorded before it has run.
16
- - **Holistic review before the final confirmation.** Once the frontier is empty and before the final confirmation, re-read all settled choices as a whole, against each other and against the destination (or the ticket's goal), because answers taken one at a time can fit badly together. Report (a) the proposed amendments: what you would change in the set where choices do not fit together; and (b) the non-working combinations: contradictions, gaps, choices that break each other or the destination. Each item names the choice(s) it touches by question number. **Nothing found:** say so in one line and go straight to the confirmation. **Otherwise** ask the human to accept, amend or reject: accept applies the proposed amendments; amend lets them rewrite an item or pick among them (mixed per-item answers go here); reject keeps the choices as they were. Whatever the answer, every non-working combination still standing afterwards (not resolved by the applied amendments, or rejected) is recorded with the set as a consciously accepted risk, never silently dropped. An applied amendment is re-confirmed in one play-back; it reopens that branch (the grill resumes from there) only when it undoes the rationale the branch was settled on, and then the review runs again on the new set, at most once more: whatever that second review still finds is put to the human the same way, and anything left standing is recorded as accepted risk, with no third pass. Only then is the final set played back, confirmed and recorded. **Format:** a forced choice of 3 real options, so an `AskUserQuestion` menu per the "Which format" rule in `../choices.md` (a numbered list on a host without one), without the delegate option (the set is the human's) and without defer; the grilled questions themselves stay conversational prose. A single isolated choice still runs it (usually the one-line nothing found).
18
+ - **Holistic review before the final confirmation.** Once the frontier is empty and before the final confirmation, re-read all settled choices as a whole, against each other and against the destination (or the ticket's goal), because answers taken one at a time can fit badly together. Report (a) the proposed amendments: what you would change in the set where choices do not fit together; and (b) the non-working combinations: contradictions, gaps, choices that break each other or the destination. Each item names the choice(s) it touches by question number. **Every non-working combination carries its fix**, stress-tested like any pick: the choice to change and to what, the question to reopen, or the ticket that will carry it. Only when no fix exists inside the set (it needs a fact not yet known, or it lies beyond the destination) say why in one line and name where it goes (a `research` ticket, a seed, the ticket that already tracks it). Accepting a combination as a risk is never the proposed fix and never a plain pointer to another ticket: it is the human's call, made on the menu below. **Nothing found:** say so in one line and go straight to the confirmation. **Otherwise** ask the human to apply, amend or keep: apply (the first option, recommended) applies every proposed amendment and fix; amend lets them rewrite an item or pick among them (mixed per-item answers go here); keep leaves the choices as they were. Whatever the answer, every non-working combination still standing afterwards (its fix not applied, or kept) is recorded with the set as a consciously accepted risk, never silently dropped. An applied amendment or fix is re-confirmed in one play-back; it reopens that branch (the grill resumes from there) only when it undoes the rationale the branch was settled on, and then the review runs again on the new set, at most once more: whatever that second review still finds is put to the human the same way, and anything left standing is recorded as accepted risk, with no third pass. Only then is the final set played back, confirmed and recorded. **Format:** a forced choice of 3 real options, so an `AskUserQuestion` menu per the "Which format" rule in `../choices.md` (a numbered list on a host without one), without the delegate option (the set is the human's) and without defer; the grilled questions themselves stay conversational prose. A single isolated choice still runs it (usually the one-line nothing found).
19
+
20
+ ## Who runs a round
21
+ The bullets above are the protocol of one round. Where a round runs is set by `models.grill`: the thinking behind each question can run on the read-only `trailhead-grill` agent, but **the main session stays the HITL orchestrator**. It alone asks the user, collects the answer and starts the next round. Every entry point points here instead of restating this.
22
+
23
+ When it runs as `trailhead-grill`, its output is always the report: it never asks the user live and never shows a menu (the orchestrator does, from the report). The rest of this section is the orchestrator's.
24
+
25
+ **Route.** Before the first round run `node <hooks>/lib/model-route.js grill --session <the session's full model id, or unknown> [--session-effort <effort>] [--host codex] [--no-subagents] <project root>`. `<hooks>` is the first of `../../hooks/lib/` and `../trailhead/hooks/lib/` (relative to the `_shared/` dir, as an absolute path) that contains it: the same two-path lookup `load-first.md` uses for `model-defaults-review.js`. Pass `unknown` when the session model is not known (a trailing `[...]` suffix such as `[1m]` is ignored, so pass the id as the session reports it), `--session-effort` whenever the session's reasoning effort is known, `--host codex` on Codex and `--no-subagents` on a subagent-less host. `models.grill` unset follows `models.plan` (and `models.codex.grill` follows `models.codex.plan`); the helper applies this (per layer: a config layer that sets `plan` but not `grill` implicitly sets `grill` to that `plan` for that layer, before the project-over-global merge (so a project that picked its `plan` grills on it even when the global config pins a `grill`; the same for `models.codex.*`)), so never resolve it by hand. The helper prints one JSON line `{"route","agent","model","alias","suggest","warning"}`.
26
+ - `route: inline`: run the technique in the session, as the bullets above say. When the helper returns `suggest`, first show one line suggesting the user switch the session to that model before the round (this is the subagent-less fallback). A `warning` is shown once.
27
+ - `route: dispatch`: spawn the agent and use its `agent` and `alias` verbatim: `agent` as the subagent and `alias` as the `Agent` model override on Claude Code, `agent_type=<agent>` on Codex. Never substitute another agent or model.
28
+ - If the agent is not registered in this environment, run inline, per the dispatch rule in `../techniques.md`.
29
+
30
+ **What the model pin guarantees.** On Claude Code a spawn gets only a family alias, so a dispatched grill is guaranteed the family of the resolved id, not its exact version. The full id still decides inline versus dispatch.
31
+
32
+ **Each round.**
33
+ 1. Send the agent the full state: this protocol (plus `domain-vocabulary.md` where the call site pairs it); the topic or the ticket body, the relevant decisions, the tickets the subject names and the destination; whether the subject is a single choice or a multi-part space; the numbered Q&A so far with the last `settled`, `frontier`, `areas` and `facts`; the review round (0, 1 or 2) and the last apply/amend/keep answer.
34
+ 2. To keep rounds stateless, re-dispatch with the full state each round. Continuing the same agent is allowed, but it still gets the full state. Carrying `facts` and `settled` forward keeps re-reading down.
35
+ 3. Check the report with `node <templates>/trailhead-report-status.js --technique grill --file <report>` (see *Malformed review report* in `../techniques.md`), then branch on the returned verdict:
36
+ - `question`: relay `question` and `recommended` to the user in prose, with every option in full per **The ask is answerable from the chat alone** (if the `question` only names them, ask the agent to re-render it), and worded per **A yes accepts the recommendation**, which the relay keeps (a question that breaks it goes back to the agent the same way). Never answer on the user's behalf and never proceed on silence. A vague answer goes back to the agent to probe. A `not looked up:` fact is fetched by the orchestrator and passed in the next round.
37
+ - `review` with `amendments` or `conflicts` not `none`: put the apply/amend/keep `AskUserQuestion` menu to the user (no delegate, no defer), then re-dispatch with the answer. The orchestrator caps reviews at two; anything still standing is recorded as accepted risk.
38
+ - `review` with both fields `none`: play back `settled`, confirm with the user and record it where the call site says.
39
+
40
+ **Inline rounds.** An inline round writes no report: the session asks the user directly, as the bullets above say, and the checker is not run.
17
41
 
42
+ **Codex.** An unset `models.codex.grill` follows `models.codex.plan` when that is pinned (the projected `trailhead-grill.toml` carries that pin), else inherits the session model, like every `models.codex.*` key. A pin equal to the session model (same effort, or no effort) routes inline.
@@ -8,15 +8,27 @@ When enabled, run this after the PLAN is drafted and before Execute:
8
8
 
9
9
  2. **Send the same brief to each reviewer.** Give every CLI the ticket's `## Question`, the drafted PLAN, and the map's relevant Notes/decisions in a **self-contained prompt**, so the reviewer needn't explore the repo. Ask for a short list of concerns (*risks, missing steps, wrong assumptions, simpler alternatives*) each tagged **blocking** or **non-blocking**.
10
10
 
11
+ **Budget each call, then capture its output.** Write the brief once to a file in the plan step's private `mktemp -d` directory. Before every reviewer call run `node <templates>/trailhead-plan-review-rounds.js budget --repo <project-root> --reviewer <cli> --prompt <brief file>` (it estimates the tokens against that CLI's context window, see `plan_review.context` in `../configuration-reference.md`) and follow its `decision`:
12
+ - `arg`: the prompt is small; make the inline call exactly as below, the prompt as the argument.
13
+ - `file`: pass the helper's `pointer` (a short "read the brief in this file" instruction) as the prompt argument, never the brief itself as one argument: a single Linux argument over 128 KiB fails with E2BIG.
14
+ - `skip`: do not invoke that reviewer; record it as skipped with the helper's `reason`.
15
+
16
+ **Every reviewer call goes through the `run` subcommand, whatever the launch mode**: `node <templates>/trailhead-plan-review-rounds.js run --output <dir>/<cli>.out -- <the CLI's headless command>`, launched with **`run_in_background: true`** by default. The helper is what bounds the call (a wall-clock deadline of 600 seconds, `--timeout <seconds>` to change it; SIGTERM then SIGKILL to the reviewer's whole process group), because the Bash tool's `timeout` parameter does not apply to a background call. It also closes stdin and captures stdout and stderr together into the output file, and prints one JSON line. On Claude Code the completion notice therefore always arrives within the bound, so polling needs no stuck-detection judgement. When the shell tool itself fails to launch the call (E2BIG raised before the helper starts), write its error text into that same file. Branch on the JSON `status`:
17
+ - `ok`: collect the output file.
18
+ - `timeout`: the reviewer hit the deadline. **Do not run `overflow`** (partial output of a stalled call is not an overflow signal); apply the one-retry rule below. A second timeout **drops** the reviewer, recorded as skipped with the reason `second timeout` and the seconds from the JSON.
19
+ - `failed` or `error`: run `node <templates>/trailhead-plan-review-rounds.js overflow --output <dir>/<cli>.out --prompt <brief file>` (the brief lets it ignore lines the CLI echoed back; rate-limit errors never count as an overflow): `overflow: true` means the prompt exceeded the reviewer's context (the estimate is not a guarantee, this catches what it missed), so **do not retry**; record the reviewer as skipped with the returned `reason`. Otherwise the one-retry rule below applies.
20
+
21
+ No human prompt anywhere on this path. The retried and the dropped reviewer are reported in one line each, and a reviewer is never reported as "still running" past the bound. **When `budget` or `overflow` itself fails** (exit 1 or 2, a missing helper or non-JSON output), never guess: for `budget`, fall back to the inline call when the brief is at most 100 KiB and skip the reviewer otherwise, recording the helper's error as the reason; for `overflow`, apply the one-retry rule as for any failed call. **When the `run` helper is missing, exits non-zero with no JSON line (a helper killed by a signal prints none), or prints non-JSON output** (never an unbounded call; a `status: error` JSON line is not this case, it takes the `failed`/`error` branch above): launch the CLI as `timeout -k 10 600 <cmd> < /dev/null > <dir>/<cli>.out 2>&1` (or `gtimeout` where GNU `timeout` is installed as that), where exit 124 or 137 means a timeout and is handled as above; with neither available, skip the reviewer, recording that no bound was available as the reason.
22
+
11
23
  Invoke each CLI **headless, in a fast read-only posture, with a generous timeout**. These are full agentic CLIs (startup, sandbox, reasoning), so a tight ~60s wall-clock is the usual cause of a "timed out" review, not a stuck reviewer. Two levers, both needed:
12
- - **Fast posture.** Make the call quick and non-interactive so it can't stall on an approval prompt or a heavy reasoning pass. For **codex**: `codex exec -s read-only -c model_reasoning_effort=low "<prompt>" < /dev/null` (read-only = nothing to approve; low effort = quick; `< /dev/null` closes stdin so it can't block waiting for EOF), or a preconfigured profile with `-p <profile>`. For **gemini**: `gemini -p "<prompt>" < /dev/null`; **qwen**: `qwen -p "<prompt>" < /dev/null`; **kimi** (Kimi Code): `kimi -p "<prompt>" < /dev/null` (`-m <model>`); **agy** (Antigravity): `agy --print-timeout 540s -p "<prompt>" < /dev/null` (it runs long, so give it a wide timeout floor). Verify each tool's exact headless flag and config key for your version. **Close stdin on every reviewer call** (append `< /dev/null`, or your shell tool's detached-stdin form): these agentic CLIs (codex most notably) also read a prompt from stdin and block on EOF, so a call that inherits an open or attached stdin, above all when backgrounded, hangs at "Reading additional input from stdin..." (0% CPU) instead of running; passing the prompt as an argument does not exempt it.
13
- - **Never block the session; background each reviewer and collect by polling.** Reviews never block the session (a hard engine default, see *Reviews never block the session* in `../techniques.md`), so the **default posture on every reviewer call, from the first attempt**, is to launch it with **`run_in_background: true`** and collect its output when it finishes, polling for progress meanwhile; backgrounding also runs a fan-out of reviewers in parallel instead of serially eating the wall-clock. These calls routinely run **several minutes**, and this harness's Bash tool **defaults to a ~2-minute cap that silently cuts them**, so a reviewer that must be run synchronously instead (a host with no background form) must set the Bash tool's **`timeout` parameter to its maximum** (`600000` ms = 10 min). Either way, **never rely on the default ~2-minute timeout for these calls**. **One retry:** if a call still times out at the wide margin, retry once; only on a second timeout or a hard error do you **drop** that reviewer and continue. A slow reviewer must never wedge or fail the build. **When you background via a raw shell `&` rather than the Bash tool's `run_in_background` (e.g. on Codex, §H), also close the reviewer's stdin** (`< /dev/null`) so it can't hang waiting for EOF, and treat a reviewer pinned at **0% CPU** with no output past a threshold as stuck: surface it and drop it after the retry rather than reporting "still running" indefinitely. **On Codex** (not Claude Code) the Bash-tool `timeout` / `run_in_background` knobs above do not exist: map them onto Codex's own shell/exec long-timeout, or a background-and-collect posture, per §H of the projected adapter header.
24
+ - **Fast posture.** Make the call quick and non-interactive so it can't stall on an approval prompt or a heavy reasoning pass. The headless command of each CLI is what you pass to `run` after `--`. For **codex**: `codex exec -s read-only -c model_reasoning_effort=low "<prompt>"` (read-only = nothing to approve; low effort = quick), or a preconfigured profile with `-p <profile>`. For **gemini**: `gemini -p "<prompt>"`; **qwen**: `qwen -p "<prompt>"`; **kimi** (Kimi Code): `kimi -p "<prompt>"` (`-m <model>`); **agy** (Antigravity): `agy --print-timeout 540s -p "<prompt>"` (it runs long, so give it a wide timeout floor). Verify each tool's exact headless flag and config key for your version. **Stdin must be closed on every reviewer call**: these agentic CLIs (codex most notably) also read a prompt from stdin, so a call that inherits an open or attached stdin may wait for EOF; passing the prompt as an argument does not exempt it. The `run` helper closes it (the raw fallback form appends `< /dev/null`). The line `Reading additional input from stdin...` is **not evidence of a stdin hang**: codex-cli prints it with stdin already closed, so never diagnose a stall from it. Whether it depends on the stdin state with an open pipe was not tested.
25
+ - **Never block the session; background each reviewer and collect by polling.** Reviews never block the session (a hard engine default, see *Reviews never block the session* in `../techniques.md`), so the **default posture on every reviewer call, from the first attempt**, is to launch the `run` call with **`run_in_background: true`** and collect its output file when it finishes, polling for progress meanwhile; backgrounding also runs a fan-out of reviewers in parallel instead of serially eating the wall-clock. These calls routinely run **several minutes**, and this harness's Bash tool **defaults to a ~2-minute cap that silently cuts a synchronous call**. A reviewer that must be run synchronously instead (a host with no background form) still goes through `run`, with the Bash tool's **`timeout` parameter set to its maximum** (`600000` ms = 10 min) and above the helper's deadline, so the helper rather than the tool ends the call. Either way, **never rely on the default ~2-minute timeout for these calls**. **One retry (never after a context overflow):** if a call ends in `timeout` (or fails without an overflow), retry once; only on a second timeout or a hard error do you **drop** that reviewer and continue. A slow reviewer must never wedge or fail the build. **On Codex** (not Claude Code) the Bash-tool `timeout` / `run_in_background` knobs above do not exist: map them onto Codex's own shell/exec long-timeout, or a background-and-collect posture around the same `run` call, per §H of the projected adapter header.
14
26
 
15
- (**Durable fix:** pin each reviewer CLI to a fast "passive posture" once, in the CLI's own config, e.g. codex's `~/.codex/config.toml` model + reasoning effort, so every review call is quick by default. Setting such a profile once is the durable fix; the inline flags above are the per-call equivalent. The fast posture makes a call *quicker* but does not replace non-blocking collection: background the reviewer by default, and **when a reviewer must run synchronously instead, still set the explicit long timeout alongside the fast posture** so a still-slow call is not *cut*.)
27
+ (**Durable fix:** pin each reviewer CLI to a fast "passive posture" once, in the CLI's own config, e.g. codex's `~/.codex/config.toml` model + reasoning effort, so every review call is quick by default. Setting such a profile once is the durable fix; the inline flags above are the per-call equivalent. The fast posture makes a call *quicker* but does not replace non-blocking collection: background the reviewer by default, and **when a reviewer must run synchronously instead, still set the explicit long timeout alongside the fast posture** so a still-slow call is not *cut*; the `run` helper's deadline is that bound.)
16
28
 
17
29
  3. **Converge.** Collect the concerns. The planner (on `config.models.plan`) revises the PLAN to resolve every **blocking** concern and weighs the non-blocking ones. If the plan changed materially, re-review; loop up to `config.plan_review.rounds` (default 2), or, under an autonomous run, up to the run's start-up choice (see `trailhead-work/references/auto.md`, which overrides the key for that run). After each round, write the blocking concerns still open to the ticket's history file (a JSON array, one entry per round, each concern `{"key","text"}` with a short stable `key` reused across rounds; keep it in a private `mktemp -d` directory for the plan step) and call `node <templates>/trailhead-plan-review-rounds.js round --choice <config|1|2|3|until> --configured <n> --history <file>`; the orchestrator follows its decision: `converged` stops clean, `continue` revises and re-reviews, and `exhausted`, `no-progress` or `ceiling` stop with blocking concerns surviving (`surviving` carries their full text). Outside an autonomous run, with the key's rounds, stop when no blocking concerns remain or the rounds are exhausted.
18
30
 
19
- 4. **Record and decide.** Post the final PLAN plus a short **`PLAN-REVIEW`** comment: who reviewed, the concerns raised, and how each was resolved or consciously deferred. **If blocking concerns survive the last round** (the rounds are exhausted with concerns still open), stopping is fine, but **say so plainly and never advance silently in either direction**: tell the user in the chat that the review stopped with the rounds spent and blocking concerns still open, **name each surviving blocking concern**, and **put the choice to them**: (a) **run another review cycle** (re-review + revise for more rounds, the point of this ask), (b) proceed to Execute accepting or consciously deferring the surviving concerns, (c) keep revising the plan first, then re-review, or (d) **run until it converges** (keep the revise + re-review loop going with no round cap, stopping only when no blocking concern remains; as a guard against an endless loop, if a round makes no progress, i.e. the same blocking concerns come back unresolved, stop and bring the choice back to this checkpoint, naming them). **Wait for their pick; never auto-proceed to Execute, and never just stop, over open blocking concerns.** This checkpoint's choice **offers** the delegate/defer options, per the injection recipe in `../choices.md`.
31
+ 4. **Record and decide.** Post the final PLAN plus a short **`PLAN-REVIEW`** comment: who reviewed, the concerns raised, and how each was resolved or consciously deferred. It also lists **every reviewer skipped or dropped, with its reason** (budget skip, context overflow, second timeout, hard error): never a silent drop. If every reviewer ends up skipped, the no-reviewer-left rule of step 1 applies. **If blocking concerns survive the last round** (the rounds are exhausted with concerns still open), stopping is fine, but **say so plainly and never advance silently in either direction**: tell the user in the chat that the review stopped with the rounds spent and blocking concerns still open, **name each surviving blocking concern**, and **put the choice to them**: (a) **run another review cycle** (re-review + revise for more rounds, the point of this ask), (b) proceed to Execute accepting or consciously deferring the surviving concerns, (c) keep revising the plan first, then re-review, or (d) **run until it converges** (keep the revise + re-review loop going with no round cap, stopping only when no blocking concern remains; as a guard against an endless loop, if a round makes no progress, i.e. the same blocking concerns come back unresolved, stop and bring the choice back to this checkpoint, naming them). **Wait for their pick; never auto-proceed to Execute, and never just stop, over open blocking concerns.** This checkpoint's choice **offers** the delegate/defer options, per the injection recipe in `../choices.md`.
20
32
 
21
33
  **Under an autonomous run the checkpoint is not posed.** The rounds were chosen at start-up, so once the helper reports surviving blocking concerns, post the `PLAN` and `PLAN-REVIEW` comments, then park the ticket with `node <templates>/trailhead-needs-you.js park` (the reason listing the surviving blocking concerns with the full `text` of each, never just its key) and move on; the run continues with the next ticket. Interactive behaviour (outside a run) is unchanged: the checkpoint above stands.
22
34
 
@@ -1,5 +1,8 @@
1
1
  # Research
2
- Spawn a focused subagent (AFK, parallelisable) with exactly ONE clear question: sharp enough that the answer is a decision, not a survey. Vague scope breeds rabbit holes; a narrow question is its own leash. Dispatch the **`trailhead-research`** agent (it commits nothing and creates no branch: the report is the artifact), passing `config.models.research` as the model override; **never** an external-plugin researcher agent that another installed tool registered.
2
+
3
+ **First, route it.** Run `node <hooks>/lib/model-route.js research --session <your full model id> <project root>` and obey its `route`. Your full model id is in your system prompt or environment ("The exact model ID is ..."); pass `unknown` only if it is truly absent. Obey it exactly: never decide inline versus dispatch by hand, never read the config yourself to decide. On `route: dispatch` spawn the `agent` it returns with model `alias` (`agent_type=<agent>` on Codex) and use its report; on `route: inline` run the protocol below in the session. Finding `<hooks>`, the flags and the two exceptions are in **Route every step through model-route.js** in `../techniques.md`. (If you are the `trailhead-research` agent, skip this gate: you are the dispatched step.)
4
+
5
+ Spawn a focused subagent (AFK, parallelisable) with exactly ONE clear question: sharp enough that the answer is a decision, not a survey. Vague scope breeds rabbit holes; a narrow question is its own leash. On `route: dispatch` it is the **`trailhead-research`** agent (it commits nothing and creates no branch: the report is the artifact), with the route's `alias` as the model override; route once, then reuse it for every parallel question; **never** an external-plugin researcher agent that another installed tool registered.
3
6
 
4
7
  - **Treat your own prior knowledge as hypothesis, not fact**: training is stale. "As of my training" is a warning flag, not a citation.
5
8
  - **Chase primary, high-trust sources**: official docs, source code, specs, first-party APIs; never a secondary write-up. Follow every claim back to the source that owns it; resolve *current* docs rather than reciting from memory.
@@ -1,11 +1,13 @@
1
1
  # TDD
2
2
  Use TDD wherever behaviour has a definable input→output: business logic, parsers, validators, transforms, algorithms, state machines, API contracts. The gate: can you write `expect(fn(input)).toBe(output)` before `fn` exists? If yes, go test-first. Skip it for UI layout/styling, config, glue, one-off scripts, trivial CRUD, and exploratory spikes; add tests after if they earn their keep.
3
3
 
4
- Before writing any test, name the **seams** (the public boundaries where behaviour is observable without reaching inside) and confirm them. Tests live at seams, never against internals. Work in vertical slices, one behaviour at a time: one test → one implementation → repeat. Never write all tests then all code (that tests *imagined* behaviour and locks in structure you don't yet understand).
4
+ Before writing any test, name the **seams** (the public boundaries where behaviour is observable without reaching inside) and confirm them; the PLAN lists each one with its one line, `<seam>: catches <what a test here fails on>; misses <what gets through it>`, so a coverage gap shows before any code is written (`misses` is never empty: a seam that seems to miss nothing has not been looked at). Tests live at seams, never against internals. Work in vertical slices, one behaviour at a time: one test → one implementation → repeat. Never write all tests then all code (that tests *imagined* behaviour and locks in structure you don't yet understand).
5
5
 
6
- - **RED**: one failing test that pins the intended behaviour as a specification (`should reject empty email`, not `test1`). Run it; SEE it fail for the *right reason*: a real assertion mismatch, not an import/syntax error. If it passes before you write code, STOP: the feature already exists or the test is wrong. Commit `test(...): add failing test for X`.
6
+ - **RED**: one failing test that pins the intended behaviour as a specification (`should reject empty email`, not `test1`). Run it; SEE it fail for the *right reason*: a real assertion mismatch, not an import/syntax error. If it passes before you write code, STOP: the feature already exists or the test is wrong. When the red comes from a forced change (you mutated code or a fixture to make the test fail), prove the change landed before trusting the run: diff the mutated file against a clean copy, or re-read it, and see the mutation there. A mutation that silently failed to apply leaves the code as it was, so the run proves nothing: a red then has another cause, and a green says nothing about the test. Commit `test(...): add failing test for X`.
7
7
  - **GREEN**: the minimal code to pass. No cleverness, no speculative features, no anticipating future tests. If unrelated tests break, stop and investigate coupling. Commit `feat(...): implement X`.
8
8
  - **REFACTOR**: only with tests green, clean up; rerun. Tests MUST still pass or you undo and go smaller. Commit if changed: `refactor(...): clean up X`.
9
9
 
10
+ Every RED, GREEN and REFACTOR run executes focused tests only (the files at hand); the full suite runs once per ticket, at Verify.
11
+
10
12
  A good test verifies behaviour through the public interface and survives refactors. Reject **implementation-coupled** tests (mock internal collaborators, assert private state, hit the DB not the interface; break on refactor though behaviour didn't) and **tautological** tests (recompute the expected value the way the code does; expected values come from an independent source: a known-good literal, a worked example, the spec). Prefer integration tests exercising real seams over isolated unit mocks. **Mock only at system boundaries** (external APIs, the DB, the clock, randomness, the filesystem) and never your own code: needing to mock an internal collaborator is a design smell, so inject the dependency instead. Design for testability: accept dependencies rather than construct them, return results rather than fire side effects, keep the seam's surface small. The discipline is absolute: **no implementation code before a failing test exists.**
11
13
 
@@ -1,8 +1,12 @@
1
1
  # Goal-backward verification
2
2
 
3
+ **First, route it.** Run `node <hooks>/lib/model-route.js verify --session <your full model id> <project root>` and obey its `route`. Your full model id is in your system prompt or environment ("The exact model ID is ..."); pass `unknown` only if it is truly absent. Obey it exactly: never decide inline versus dispatch by hand, never read the config yourself to decide. On `route: dispatch` spawn the `agent` it returns with model `alias` (`agent_type=<agent>` on Codex) and use its report; on `route: inline` run the protocol below in the session. Finding `<hooks>`, the flags and the two exceptions are in **Route every step through model-route.js** in `../techniques.md`. (If you are the `trailhead-verify` agent, skip this gate: you are the dispatched step.)
4
+
3
5
  Prove the change **delivers what the ticket promised**, not merely that steps ran or the tests pass. This is distinct from **Code review** (which hunts bugs and quality issues adversarially) and from **Acceptance testing** (which proves it does what the *user* wanted, interactively): this is a **goal-backward** reasoning check against the ticket's own stated intent.
4
6
 
5
- Run it in the `build`/`bug` **Verify** step, after the tests / the plan's criterion have run, as a subagent on `config.models.verify` (dispatch the **`trailhead-verify`** agent, read-only, passing `config.models.verify` as the model override; inline when the model is unset or equals the session model, and inline on a `subagentToolkit == none` host). Like the other reviews it **never blocks the session**: run it in the background and collect it by polling (see *Reviews never block the session* in `../techniques.md`).
7
+ Run it in the `build`/`bug` **Verify** step, after the tests / the plan's criterion have run, as the routed subagent on `config.models.verify` (on `route: dispatch` the **`trailhead-verify`** agent, read-only, with the route's `alias` as the model override; a `subagentToolkit == none` host routes inline). Like the other reviews it **never blocks the session**: run it in the background and collect it by polling (see *Reviews never block the session* in `../techniques.md`).
8
+
9
+ **Test record.** When the orchestrator passes a test record (`test record: ...`, the full-suite outcome of Verify), read it and never re-run the full suite (a focused test to probe one finding is fine); with no record passed, test as today (without `testing.full` no test record is passed).
6
10
 
7
11
  1. **Restate the goal.** From the ticket's `## Question` and the `PLAN`'s verification criteria, list what "done" means as concrete, checkable claims. If the ticket named acceptance criteria, those are the claims. **When the ticket had an approved mockup**, add one explicit claim: the built UI is faithful to the approved mockup (layout, spacing, sizing, colour, typography, copy, and every state it shows). Being read-only and running before Acceptance testing, check it to the extent statically evaluable: does the built code carry the mockup's specified copy, states, and structural elements? A dropped element or missing state surfaces as `gaps_found`, and **defer the visual, pixel-level verdict to Acceptance testing's mockup-fidelity check** (a screenshot, not the diff, settles that) rather than forcing it here.
8
12
  2. **Work backward from each claim to the ticket's own commits.** Take the change from the **Ticket diff** rule (canonical in `code-review.md`; restated here because this protocol is loaded on its own): the commits carrying this ticket's `Refs: #<n>` trailer, computed with exactly `git -C <repo> log --reverse --basic-regexp --grep='^Refs: #<n>$' --format=%H HEAD` and read per commit, oldest first, with `git -C <repo> show --remerge-diff <sha>`; recompute it on every run, never use a `<base>...HEAD` range, and treat an empty set as an error surfaced to the caller, never a `met`. For every claim, find the commit(s) and lines that satisfy it, record it as **unmet** if the code does not satisfy it, or as **inconclusive** (with a reason-class, see the Verdict) if you could not actually evaluate it: the check would not run, the evidence was unreadable, or the criterion is not falsifiable as written. Read the actual diff and the resulting code, not the commit messages.
@@ -2,13 +2,15 @@
2
2
 
3
3
  The ticket engines call these by name. Each technique's full protocol lives in its own file under `techniques/`: **read that file the first time a session needs the technique**, so only the ones in play load into context (don't preload them). They are `trailhead`'s own, self-contained; each technique is dispatched to its **trailhead-owned agent** (`trailhead-<technique>`, shipped as committed `agents/trailhead-*.md`) via the `Agent` tool, with the technique's full protocol injected at spawn.
4
4
 
5
- **Dispatch each technique to its trailhead-owned agent.** Every technique subagent carries its full protocol in the prompt (from its `techniques/` file), so the agent definition stays **thin**: identity + tool policy, model-agnostic. Dispatch each technique to **its own `trailhead-<technique>` agent** (set `subagent_type` accordingly): the Plan step → `trailhead-plan`, Execute → `trailhead-executor`, Research → `trailhead-research`, Code review → `trailhead-code-review`, Debug → `trailhead-debug`, Fix → `trailhead-fix`, Codebase map → `trailhead-codebase-map`, Goal-backward verification → `trailhead-verify`. These agents ship with trailhead (committed `agents/trailhead-*.md`) and are registered wherever trailhead is installed as a Claude Code **plugin** (its `agents/` load automatically), or its agent files are otherwise present as subagent types. **If a `trailhead-<technique>` agent is NOT registered in the current environment** (the `Agent`/`Task` tool reports the type unknown, or it is absent from the available-agents list, as on a skills-only install that did not register the agents), **run that technique inline in the main session**, following its `techniques/` protocol directly. Do **not** divert to a host built-in (`general-purpose`/`Explore`) or any external-plugin agent as the engine target. Inline is a graceful degradation: the technique still runs, only on the session model rather than its `config.models.<key>` pin (the same inline path used when that model equals the session model). The dispatch rule is therefore: the real `trailhead-<technique>` agent when it is registered, else inline in the main session, never a foreign agent. **Pass the `config.models.<key>` model as the `Agent`-tool `model` override at spawn** so config stays authoritative (the `.md` is model-agnostic; the override wins), running inline instead when that model is unset or equals the session model. Execute and Fix inject `trailhead-work/references/executor-brief.md` into their agent as the protocol text, not the whole step (the orchestrator part of the step stays with the session). On Claude Code the `Agent` tool's `model` parameter accepts only the short family aliases (`opus` / `sonnet` / `haiku` / `fable`), not the full ids `config.models.*` stores, so **map the configured id to its family alias only for the literal `model` value passed at spawn**: `claude-opus-*` -> `opus`, `claude-sonnet-*` -> `sonnet`, `claude-haiku-*` -> `haiku`, `claude-fable-*` -> `fable`; if an id matches none of these, don't guess: warn and inherit the session model (per `configuration-reference.md`). The alias names a family, not a version, so the host resolves it to that family's current model: the exact version the id pinned cannot be conveyed to the `Agent` tool and is not honoured at subagent spawn. **Keep the inline-vs-subagent decision on the full id**, running inline only when `config.models.<key>` is unset or its full id equals the session model's, never on the alias, so a deliberate same-family but different-version pin (e.g. `claude-opus-5` under a `claude-opus-4-8` session) still dispatches instead of collapsing to inline. (Codex projects `models.codex.*`, OpenAI ids, separately and is unaffected by this.) **Never dispatch to an external-plugin agent** (a `gsd-*`, `*-reviewer`, `*-researcher`, `*-mapper`, or similarly named agent another installed tool registered): it follows *its own* protocol and artifact conventions (a foreign code-review agent writes its own review file, not trailhead's `VERIFY` ticket comment), silently swapping trailhead's engine for another tool's. Host built-ins (`Explore` / `general-purpose`) stay available in general but are **no longer the engine-dispatch target**. The tool policy is baked into each agent's `tools:` field, mirroring the old split: **read-only** (`trailhead-plan`, `trailhead-code-review`, `trailhead-codebase-map`, `trailhead-verify`: reads, searches, runs tests; no Edit/Write) vs **full** (`trailhead-executor`, `trailhead-research`, `trailhead-debug`, `trailhead-fix`: can Edit/Write/commit). **Search-command hygiene (every fan-out subagent, and any built-in `Explore`/`general-purpose` fan-out the engine leans on).** Prefer the Grep and Read tools over shelling out to search or read a file; when a Bash `grep`/read is genuinely needed, pass the path as an **explicit argument** (`grep -niE "<patterns>" <abs>/<file>`, `wc -l <abs>/<file>`) and run git as `git -C <abs> ...`. **Never `cd` (or `pushd`/`env -C`) into the target directory first, in any shape**: not `cd <abs> && grep <relative>`, not a bare `cd <abs>` line followed by a relative `grep`/`wc` on the next line or after a `;`, not inside a heredoc or any other multi-statement block, and not as its own separate Bash call or turn (the shell working directory persists across Bash calls, so a standalone `cd <abs>` still leaves a later relative read non-static). Any `cd` makes the post-`cd` working directory non-static, so under bypass permissions with a `Read()` deny rule the harness cannot resolve the read target against the allow rules and falls back to a manual approval prompt (defeating headless operation); a path-explicit target (absolute-path argument, `git -C`) stays statically determinable and runs uninterrupted regardless of how the statements are separated. When the engine spawns a built-in `Explore`/`general-purpose` search, state this rule in its spawn prompt. On a `subagentToolkit == none` host there is no fan-out and this is moot: every technique runs inline in the one session. (Codex is **not** such a host: it has the native multi_agent toolkit, so techniques fan out there too, dispatched by `agent_type` to the projected `~/.codex/agents/trailhead-*.toml`; see the Codex adapter's §D.)
5
+ **Dispatch each technique to its trailhead-owned agent.** Every technique subagent carries its full protocol in the prompt (from its `techniques/` file), so the agent definition stays **thin**: identity + tool policy, model-agnostic. Dispatch each technique to **its own `trailhead-<technique>` agent** (set `subagent_type` accordingly): the Plan step → `trailhead-plan`, Execute → `trailhead-executor`, Research → `trailhead-research`, Code review → `trailhead-code-review`, Debug → `trailhead-debug`, Fix → `trailhead-fix`, Codebase map → `trailhead-codebase-map`, Goal-backward verification → `trailhead-verify`, Grilling → `trailhead-grill` (one round per spawn, the session stays the HITL orchestrator; see **Who runs a round** in `techniques/grilling.md`). These agents ship with trailhead (committed `agents/trailhead-*.md`) and are registered wherever trailhead is installed as a Claude Code **plugin** (its `agents/` load automatically), or its agent files are otherwise present as subagent types. **If a `trailhead-<technique>` agent is NOT registered in the current environment** (the `Agent`/`Task` tool reports the type unknown, or it is absent from the available-agents list, as on a skills-only install that did not register the agents), **run that technique inline in the main session**, following its `techniques/` protocol directly. Every engine agent is spawned with **fresh context, never a fork** of the orchestrator conversation, on every host: on Claude Code the `trailhead-*` subagent type, never the `fork` type; on Codex the no-fork spawn parameter (`fork_turns="none"` or `fork_context=false`, per the adapter), since a full-history fork inherits the conversation and ignores the model/effort override. Do **not** divert to a host built-in (`general-purpose`/`Explore`) or any external-plugin agent as the engine target. Inline is a graceful degradation: the technique still runs, only on the session model rather than its `config.models.<key>` pin (the same inline path used when that model equals the session model). The dispatch rule is therefore: the real `trailhead-<technique>` agent when it is registered, else inline in the main session, never a foreign agent. **Whether a step spawns, and on which model, is decided by `model-route.js`, never by hand**: see **Route every step through model-route.js** below. The route's `alias` is the `Agent`-tool `model` override at spawn, so config stays authoritative (the `.md` is model-agnostic; the override wins). **An unset key is not `inherit session`:** the helper resolves it to its Balanced default (`configuration-reference.md`), so with nothing configured the planner still spawns on the strong tier even from a standard-tier session; never spawn with defaults. Execute and Fix inject `trailhead-work/references/executor-brief.md` into their agent as the protocol text, not the whole step (the orchestrator part of the step stays with the session). On Claude Code the `Agent` tool's `model` parameter accepts only the short family aliases (`opus` / `sonnet` / `haiku` / `fable`), not the full ids `config.models.*` stores, so **map the configured id to its family alias only for the literal `model` value passed at spawn**: `claude-opus-*` -> `opus`, `claude-sonnet-*` -> `sonnet`, `claude-haiku-*` -> `haiku`, `claude-fable-*` -> `fable`; if an id matches none of these, don't guess: warn and inherit the session model (per `configuration-reference.md`). The alias names a family, not a version, so the host resolves it to that family's current model: the exact version the id pinned cannot be conveyed to the `Agent` tool and is not honoured at subagent spawn. The helper makes the inline-versus-dispatch decision on the full id, never on the alias, so a deliberate same-family but different-version pin (e.g. `claude-opus-5` under a `claude-opus-4-8` session) still dispatches instead of collapsing to inline. (Codex projects `models.codex.*`, OpenAI ids, separately and is unaffected by this.) **Never dispatch to an external-plugin agent** (a `gsd-*`, `*-reviewer`, `*-researcher`, `*-mapper`, or similarly named agent another installed tool registered): it follows *its own* protocol and artifact conventions (a foreign code-review agent writes its own review file, not trailhead's `VERIFY` ticket comment), silently swapping trailhead's engine for another tool's. Host built-ins (`Explore` / `general-purpose`) stay available in general but are **no longer the engine-dispatch target**. The tool policy is baked into each agent's `tools:` field, mirroring the old split: **read-only** (`trailhead-plan`, `trailhead-code-review`, `trailhead-codebase-map`, `trailhead-verify`: reads, searches, runs tests; no Edit/Write; `trailhead-grill`: reads and searches only, no Bash) vs **full** (`trailhead-executor`, `trailhead-research`, `trailhead-debug`, `trailhead-fix`: can Edit/Write/commit). **Search-command hygiene (every fan-out subagent, and any built-in `Explore`/`general-purpose` fan-out the engine leans on).** Prefer the Grep and Read tools over shelling out to search or read a file; when a Bash `grep`/read is genuinely needed, pass the path as an **explicit argument** (`grep -niE "<patterns>" <abs>/<file>`, `wc -l <abs>/<file>`) and run git as `git -C <abs> ...`. **Never `cd` (or `pushd`/`env -C`) into the target directory first, in any shape**: not `cd <abs> && grep <relative>`, not a bare `cd <abs>` line followed by a relative `grep`/`wc` on the next line or after a `;`, not inside a heredoc or any other multi-statement block, and not as its own separate Bash call or turn (the shell working directory persists across Bash calls, so a standalone `cd <abs>` still leaves a later relative read non-static). Any `cd` makes the post-`cd` working directory non-static, so under bypass permissions with a `Read()` deny rule the harness cannot resolve the read target against the allow rules and falls back to a manual approval prompt (defeating headless operation); a path-explicit target (absolute-path argument, `git -C`) stays statically determinable and runs uninterrupted regardless of how the statements are separated. When the engine spawns a built-in `Explore`/`general-purpose` search, state this rule in its spawn prompt. On a `subagentToolkit == none` host there is no fan-out and this is moot: every technique runs inline in the one session. (Codex is **not** such a host: it has the native multi_agent toolkit, so techniques fan out there too, dispatched by `agent_type` to the projected `~/.codex/agents/trailhead-*.toml`; see the Codex adapter's §D.)
6
6
 
7
- **Reviews never block the session (engine-wide, every host).** The three **review** techniques, **Code review** (build/bug Verify), **Goal-backward verification** (build/bug Verify), and **Cross-AI plan review** (build Plan), always run as a **background job collected by polling**, never a blind open-ended blocking wait. This is a hard default: not a `config` toggle, and not a Codex-only or fallback posture. Because trailhead works one ticket at a time and Resolve depends on the review's outcome, "background" here means a **poll loop with a one-line progress note, the session responsive and interruptible between polls**, the outcome always awaited; what is banned is the blind freeze, never "proceed without the result." On Claude Code the `Agent` tool already backgrounds the review and notifies on completion, so poll its progress instead of awaiting it blindly; on Codex, collect it through the §D bounded-wait poll loop (external plan-review CLIs via §H). **Acceptance testing is excluded**: it is interactive by design (guided UAT / browser-drive with the user in the loop), not a blind wait to background.
7
+ **Route every step through model-route.js.** Before dispatching any engine step, run `node <hooks>/lib/model-route.js <key> --session <your full model id> [--session-effort <e>] [--host codex] [--no-subagents] <project root>` and obey its `route`. `<hooks>` is the first of `../../hooks/lib/` and `../trailhead/hooks/lib/` (relative to the `_shared/` dir, as an absolute path) that contains it: the same two-path lookup `load-first.md` uses for `model-defaults-review.js`. Your full model id is in your system prompt or environment ("The exact model ID is ..."); pass `unknown` only if it is truly absent (a trailing `[...]` suffix such as `[1m]` is ignored). Pass `--session-effort` whenever the session's reasoning effort is known, `--host codex` on Codex and `--no-subagents` on a subagent-less host. The key per step: Plan -> `plan`, Execute -> `execute`, Fix -> `execute` (spawn `trailhead-fix` in place of the returned `agent`), Code review -> `review`, Goal-backward verification -> `verify`, Research -> `research`, Debug -> `debug`, Codebase map -> `codebase-map`, Grilling -> `grill` (through **Who runs a round** in `techniques/grilling.md`). The helper prints one JSON line `{"route","agent","model","alias","suggest","warning"}`. `route: inline`: run the protocol in the session; when the helper returns `suggest`, show it as one line, and a `warning` is shown once. `route: dispatch`: spawn the step's agent and use its `agent` and `alias` verbatim: `agent` as the subagent and `alias` as the `Agent` model override on Claude Code (a family, not the exact version), `agent_type=<agent>` on Codex (where `alias` is null). Never substitute another agent or model. If the agent is not registered in this environment, run inline, per the dispatch rule above. Never decide inline versus dispatch by hand, and never read the config yourself to decide. Two cases sit outside the route, and only these two: an accepted `lean` skip (the user chose a lighter inline pass, so there is no subagent step to route), and an autonomous run with more than one slot (`trailhead-work/references/auto.md`: every step dispatches, so on `route: inline` it spawns the agent with no model override, inheriting the session model, which is the resolved one; the helper is still run).
8
8
 
9
- **Malformed review report (every engine-agent report).** The leading `status:` line, and the fields each report must carry, are machine-checked before the engine branches on them. This MUST run on every Code review and Goal-backward verification report and on every plan, executor, fix, debug, research and codebase-map report, whether it came from the subagent, an inline pass, or the lean inline pass; the report-status checker is `trailhead-report-status.js`, one JSON line `{"action","verdict","problem","report","fields","body"}` with `action` one of `accept` / `normalise` / `reask`. The return contract of each family (statuses, required fields, invariants) is stated once in the agents' `Return contract` blocks; the checker enforces it.
9
+ **Reviews never block the session (engine-wide, every host).** The three **review** techniques, **Code review** (build/bug Verify), **Goal-backward verification** (build/bug Verify), and **Cross-AI plan review** (build Plan), always run as a **background job collected by polling**, never a blind open-ended blocking wait. This is a hard default: not a `config` toggle, and not a Codex-only or fallback posture. Because trailhead works one ticket at a time and Resolve depends on the review's outcome, "background" here means a **poll loop with a one-line progress note, the session responsive and interruptible between polls**, the outcome always awaited (up to its bound: a Cross-AI plan review call runs under the `run` helper's deadline, so a stalled reviewer ends as a dropped reviewer, recorded in `PLAN-REVIEW`, never an open wait; Code review and Goal-backward verification are still awaited to their outcome); what is banned is the blind freeze, never "proceed without the result." On Claude Code the `Agent` tool already backgrounds the review and notifies on completion, so poll its progress instead of awaiting it blindly; on Codex, collect it through the §D bounded-wait poll loop (external plan-review CLIs via §H). **Acceptance testing is excluded**: it is interactive by design (guided UAT / browser-drive with the user in the loop), not a blind wait to background.
10
10
 
11
- 1. **Check.** Per report run `mktemp -d` once (a private 0700 random directory); write the raw report to `<that dir>/report.md` with the Write tool at the literal printed path; run `node <templates>/trailhead-report-status.js --technique <verify|code-review|plan|executor|fix|debug|research|codebase-map> --file <that dir>/report.md`, where `<templates>` is the first of `${CLAUDE_PLUGIN_ROOT}/templates/` (skipped when unset), `${CLAUDE_CONFIG_DIR:-~/.claude}/trailhead/templates/` and `~/.codex/skills/trailhead/templates/` that contains it; then `rm -rf <that dir>` with the literal absolute path. Cleanup runs after every path: accept, normalise, each re-ask check, a second failure, and a checker failure. Never a fixed or predictable path, never reused. The checker also reads stdin, but this protocol does not use it (a heredoc would put the report into the shell-scanned command and needs a delimiter the report cannot contain).
11
+ **Malformed review report (every engine-agent report).** The leading `status:` line, and the fields each report must carry, are machine-checked before the engine branches on them. This MUST run on every Code review and Goal-backward verification report and on every plan, executor, fix, debug, research, codebase-map and grill report (a dispatched `trailhead-grill` round), whether it came from the subagent, an inline pass, or the lean inline pass; the report-status checker is `trailhead-report-status.js`, one JSON line `{"action","verdict","problem","report","fields","body"}` with `action` one of `accept` / `normalise` / `reask`. The return contract of each family (statuses, required fields, invariants) is stated once in the agents' `Return contract` blocks; the checker enforces it. An inline `grill` round writes no report, so it is not checked: only a dispatched `trailhead-grill` report is checked, because the checker guards the handoff from agent to orchestrator and an inline round has none.
12
+
13
+ 1. **Check.** A background agent whose host writes its transcript to a task output file (Claude Code's `<tasks dir>/<id>.output`, named in the completion notice): run `node <templates>/trailhead-report-status.js --technique <technique> --task-output <that output file>` at the literal path, once the agent has finished; the checker reads the agent's last assistant message itself, so no temp dir, no copy and no cleanup. Any other report (inline, foreground, or a host without that file): per report run `mktemp -d` once (a private 0700 random directory); write the raw report to `<that dir>/report.md` with the Write tool at the literal printed path; run `node <templates>/trailhead-report-status.js --technique <verify|code-review|plan|executor|fix|debug|research|codebase-map|grill> --file <that dir>/report.md`, where `<templates>` is the first of `${CLAUDE_PLUGIN_ROOT}/templates/` (skipped when unset), `${CLAUDE_CONFIG_DIR:-~/.claude}/trailhead/templates/` and `~/.codex/skills/trailhead/templates/` that contains it; then `rm -rf <that dir>` with the literal absolute path. Cleanup runs after every path: accept, normalise, each re-ask check, a second failure, and a checker failure. Never a fixed or predictable path, never reused. The checker also reads stdin, but this protocol does not use it (a heredoc would put the report into the shell-scanned command and needs a delimiter the report cannot contain). Each of these is its own Bash call: never wrapped in `bash -c` / `sh -c`, never chained with `;`, `&&` or a pipe to other commands (git, `jq`, the work-state write), because the host cannot statically check such a script and asks the user to approve every one.
12
14
  2. **Accept or normalise.** A review verdict contradicted by its own ledger or claim classes is normalised to the stricter verdict. The checker-returned `report` replaces the raw report where the technique places it (the layout below); the engine branches only on the returned `verdict`, and reads the returned `fields` (and, for a `ready` plan, `body`) instead of re-deriving them from the prose of the report.
13
15
  3. **Reask.** Ask once: continue the same agent where the host allows it, else re-dispatch the same `trailhead-<technique>` agent on the same model override with the malformed report quoted, using this corrective prompt verbatim (placeholders filled): "Your report could not be accepted: `<problem>` (missing: no `status:` line; unknown-token: `<found>` is not an allowed token; 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). Return the complete report again with its first line exactly `status: <token>`, where `<token>` is one of `<allowed tokens>`, and no other `status:` line<for code-review: , and its second line exactly `critical: <n> · warning: <n> · info: <n>`><for fields: , with each of `<required fields>` as a top-level `<label>:` line and `<the broken invariant>` holding, with nothing after the last field>. Do not post it anywhere." For code-review the prompt always restates both required lines; for `fields` it names the missing or empty fields and the invariant that broke.
14
16
  4. **Re-check the reply** the same way. Still a reask: no verdict, nothing posted for that round, no third ask, no inference from the body. Surface a HITL checkpoint in the chat naming the technique and the problem; Resolve stays blocked, and so does any step that needed the report; for a review the previous `VERIFY` comment stays latest.
@@ -18,14 +20,14 @@ The ticket engines call these by name. Each technique's full protocol lives in i
18
20
 
19
21
  | Technique | File | In one line |
20
22
  |---|---|---|
21
- | **Grilling** | `techniques/grilling.md` | interrogate the human, one question at a time, to converge on a decision |
23
+ | **Grilling** | `techniques/grilling.md` | interrogate the human, one question at a time, to converge on a decision; rounds run on `models.grill` (`trailhead-grill`), the session asks |
22
24
  | **Domain vocabulary** | `techniques/domain-vocabulary.md` | build a precise shared glossary so each term means one thing |
23
25
  | **Prototype** | `techniques/prototype.md` | throwaway artifact for "how should it look/behave"; routes UI mockups (disk / artifacts / stitch) |
24
26
  | **Research** | `techniques/research.md` | a focused subagent gathers a decision-ready fact from primary sources |
25
27
  | **TDD** | `techniques/tdd.md` | RED → GREEN → REFACTOR at the seams; no implementation before a failing test |
26
28
  | **Codebase map** | `techniques/codebase-map.md` | one-time fan-out of 5 read-only readers, distilled into the repo's `trailhead:codebase` issue |
27
29
  | **Debug** | `techniques/debug.md` | scientific method: reproduce → localise → falsifiable hypotheses → confirm cause → verify |
28
- | **Code review** | `techniques/code-review.md` | review the ticket's own commits (its `Refs: #<n>` set) on 4 axes, adversarially verify each finding before reporting |
30
+ | **Code review** | `techniques/code-review.md` | review the ticket's own commits (its `Refs: #<n>` set) on 5 axes, adversarially verify each finding before reporting; the brief lists the repo's standards files; a pipeline-touching diff also loads `techniques/code-review-pipeline.md` |
29
31
  | **Goal-backward verification** | `techniques/verify.md` | check the change delivers what the ticket promised, working backward from its stated goal |
30
32
  | **Acceptance testing** | `techniques/acceptance-testing.md` | prove it does what the *user* wanted: ask who drives (AI or human, or skip), then automated → browser-drive → guided UAT |
31
33
  | **Cross-AI plan review** | `techniques/plan-review.md` | send a PLAN to external AI CLIs, converge on their concerns (opt-in via `config.plan_review`) |
@@ -9,7 +9,7 @@ argument-hint: "[bug|todo|idea|seed|note] [--of <ticket>] <text>"
9
9
 
10
10
  ## Load first, in order
11
11
 
12
- Before doing anything, load `<skill base>/../_shared/load-first.md` and follow it: the shared-core load contract (the six core files, in order, then the effective config). `<skill base>` is this skill's **base directory** (Claude Code prints it as `Base directory for this skill:` when the skill loads; on another host, the directory holding this `SKILL.md`): resolve the path to an **absolute path** and open it with the **Read tool** on that absolute path. Never `cd` into a skills directory and never read a relative path from the shell (a `cd` compound followed by a read is denied by the search guard). The same rule covers every later `../_shared/...` or `references/...` path in this skill: resolve it against the base directory to an absolute path before reading. `_shared/` is a **sibling** of this cluster's own directory (at `../_shared/`), never a child of it; its absence from a listing of the cluster dir is expected, not a missing core.
12
+ Before doing anything, read two files, in order, and nothing else from `_shared/`: `<skill base>/../_shared/capture-core.md` (the compact capture reference: the standing rules, one lookup, the labels and create commands, the ticket body), then `<skill base>/references/capture.md`. `<skill base>` is this skill's **base directory** (Claude Code prints it as `Base directory for this skill:` when the skill loads; on another host, the directory holding this `SKILL.md`): resolve each path to an **absolute path** and open it with the **Read tool** on that absolute path. Never `cd` into a skills directory and never read a relative path from the shell (a `cd` compound followed by a read is denied by the search guard). `_shared/` is a **sibling** of this cluster's own directory (at `../_shared/`), never a child of it; its absence from a listing of the cluster dir is expected, not a missing core.
13
13
 
14
14
  A capture writes at most one issue; no isolation workspace is set up.
15
15
 
@@ -17,6 +17,4 @@ A capture writes at most one issue; no isolation workspace is set up.
17
17
 
18
18
  The **first word** of the arguments is the verb (`bug`, `todo`, `idea`, `seed`, or `note`); the rest is the text (or `--of <ticket> <text>` for `bug`).
19
19
 
20
- - **`bug`** / **`todo`** / **`idea`** / **`seed`** / **`note`** to **`references/capture.md`**: the full per-verb protocol and the note/idea/seed/todo spectrum. Read that file and follow it.
21
-
22
- The cross-cluster situational references and technique bodies these captures call live in `_shared/`; this cluster names each by its `../_shared/...` path where it needs one.
20
+ - **`bug`** / **`todo`** / **`idea`** / **`seed`** / **`note`** to **`references/capture.md`**: the full per-verb protocol and the note/idea/seed/todo spectrum. Follow it.
@@ -1,12 +1,12 @@
1
1
  # Capture: the verbs in detail
2
2
 
3
- Capture without breaking flow, but with the **tracker** as destination, not scattered files. The verbs are named in this cluster's `../SKILL.md`; here is the detail of each. One capture = one action + one confirmation line; it resolves nothing.
3
+ Capture without breaking flow, but with the **tracker** as destination, not scattered files. Here is the detail of each verb. One capture = one action + one confirmation line; it resolves nothing.
4
4
 
5
- **Confirmation line.** Confirm the capture by the ticket's name and its number, both visible (e.g. *Add login rate limit* (#45), never the name alone nor a bare `#number`), and where it landed. **If you point at working it later, lead with `/clear`**: the confirmation is a place you point at the next ticket, so the [Session handoff](../../_shared/session-handoff.md)'s `/clear`-first rule applies: a compact, future-conditional one-liner is fine (a capture is not a resolution, so no full labelled block), along the lines of *"to work it later: `/clear`, then `/trailhead:work 45`; or, for the lighter path, `/trailhead:quick 45`"* (in the user's conversation language). Never a bare `/trailhead:work 45` with no `/clear`, and (the same post-`/clear` invariant the [Session handoff](../../_shared/session-handoff.md) states) **never place after `/clear` an action the agent must perform that depends on context `/clear` erases**: a capture confirmation points only at user-runnable commands, which satisfies this by construction, so keep it that way. **Whenever the confirmation does point at working the ticket (a `seed` parked on its trigger, or a fog `note`/`idea` that is not a ticket yet, is the case where it does not), anchor the next step to `/trailhead:work <n>`, always:** it is the recommendation (the full engine, which can split and keeps the map record), and the confirmation names it first. `/trailhead:quick <n>` is the **lighter alternative** (a single alternative, not two): it works that same ticket whole right now, always size-triaged so on a small change it offers to skip the heavy steps (plan pass, TDD, full code review, goal-backward verify) on an explicit yes, grills only if needed, skips the map narrative book-keeping, and never splits; a map ticket stays on its map, so never call it "off the map". **Describe it by that purpose (the lighter, faster path), never as merely "without splitting"**: not splitting is a side effect, not what `quick` is for. It is offered *alongside* `work`, **never in its place** and **never rendered twice on one line**. This holds for a **whiteboard** capture too, where both apply (`quick` is the no-split variant of `work`): the anchor stays `/trailhead:work <n>` for consistency. Same spirit as chart's codified close (#147). Render the command in this host's command forms (the **Host command forms** rule in `../../_shared/load-first.md`).
5
+ **Confirmation line.** Confirm the capture by the ticket's name and its number, both visible (e.g. *Add login rate limit* (#45), never the name alone nor a bare `#number`), and where it landed. **If you point at working it later, lead with `/clear`**: the confirmation is a place you point at the next ticket, so the `/clear`-first rule in `capture-core.md` applies: a compact, future-conditional one-liner is fine (a capture is not a resolution, so no full labelled block), along the lines of *"to work it later: `/clear`, then `/trailhead:work 45`; or, for the lighter path, `/trailhead:quick 45`"* (in the user's conversation language). Never a bare `/trailhead:work 45` with no `/clear`, and (the same post-`/clear` invariant: nothing the agent must do after a `/clear`) **never place after `/clear` an action the agent must perform that depends on context `/clear` erases**: a capture confirmation points only at user-runnable commands, which satisfies this by construction, so keep it that way. **Whenever the confirmation does point at working the ticket (a `seed` parked on its trigger, or a fog `note`/`idea` that is not a ticket yet, is the case where it does not), anchor the next step to `/trailhead:work <n>`, always:** it is the recommendation (the full engine, which can split and keeps the map record), and the confirmation names it first. `/trailhead:quick <n>` is the **lighter alternative** (a single alternative, not two): it works that same ticket whole right now, always size-triaged so on a small change it offers to skip the heavy steps (plan pass, TDD, full code review, goal-backward verify) on an explicit yes, grills only if needed, skips the map narrative book-keeping, and never splits; a map ticket stays on its map, so never call it "off the map". **Describe it by that purpose (the lighter, faster path), never as merely "without splitting"**: not splitting is a side effect, not what `quick` is for. It is offered *alongside* `work`, **never in its place** and **never rendered twice on one line**. This holds for a **whiteboard** capture too, where both apply (`quick` is the no-split variant of `work`): the anchor stays `/trailhead:work <n>` for consistency. Same spirit as chart's codified close (#147). Render the command in this host's command forms (the host command forms rule in `capture-core.md`).
6
6
 
7
- **Language (do not skip):** the captured ticket (title + body) is written in `config.ticket.language` (default `en`), **regardless of the language you are chatting in**. A capture fired in Italian still lands as an English ticket. This is the easiest place to slip, because a capture echoes the user's one-line phrasing; translate it, do not mirror it. See the *Ticket language* rule in `../../_shared/ticket-language.md`.
7
+ **Language (do not skip):** the captured ticket (title + body) is written in `config.ticket.language` (default `en`), **regardless of the language you are chatting in**. A capture fired in Italian still lands as an English ticket. This is the easiest place to slip, because a capture echoes the user's one-line phrasing; translate it, do not mirror it. See the ticket language rule in `capture-core.md`.
8
8
 
9
- **Map or whiteboard:** a capture that produces a **ticket** (`todo`, `bug`, `seed`, and `idea` when it is sharp enough) must land on a **map** or on the **whiteboard** (map-less). **When a map is open, ask the user which** before creating it (active map vs whiteboard; several maps open -> the ask also picks the map). **Map eligibility is "is the map open", not "is it still in progress".** An **exhausted** map (destination reached) that is **still open** stays a valid capture target: a capture thematically tied to it is offered that map on the which-map ask, never silently dropped to the whiteboard just because the destination was reached (that would sever the thematic link). The exhausted state is a reason to *offer to close the map* (the dashboard's `exhausted · closeable` flag, see the dashboard freshness rule in `../../_shared/substrate.md`), never a reason to drop it as a capture target. Only a **closed** map is no longer eligible: then the whiteboard is the right fallback. Whiteboard tickets get the `trailhead:whiteboard` label and **no** map label or `Parent:`; map tickets get the map's `trailhead:map-<n>` label + native sub-issue edge. **No map open at all -> whiteboard, no ask** (nowhere else to go). The ask is also skipped for a `bug --of` whose origin resolves to an open map or the whiteboard (see the `bug` bullet in the capture forms). `note` and a non-sharp `idea` stay map-fog and skip this; with no open map they fall back to the global notes location (`$HOME/.claude/notes/`), or a now-sharp `idea` becomes a whiteboard ticket. **When a capture creates a whiteboard ticket, refresh the pinned dashboard** afterwards (a structural event: the whiteboard surface the dashboard indexes grew, and a whiteboard ticket has no native progress bar, so the dashboard is the only place it shows); a capture landing on a *map* does not (native sub-issue progress bars track map tickets). See *The whiteboard* and the dashboard freshness rule in `../../_shared/substrate.md`.
9
+ **Map or whiteboard:** a capture that produces a **ticket** (`todo`, `bug`, `seed`, and `idea` when it is sharp enough) must land on a **map** or on the **whiteboard** (map-less). **When a map is open, ask the user which** before creating it (active map vs whiteboard; several maps open -> the ask also picks the map). **Map eligibility is "is the map open", not "is it still in progress".** An **exhausted** map (destination reached) that is **still open** stays a valid capture target: a capture thematically tied to it is offered that map on the which-map ask, never silently dropped to the whiteboard just because the destination was reached (that would sever the thematic link). The exhausted state is a reason to *offer to close the map* (the dashboard's `exhausted · closeable` flag, see the dashboard freshness rule: a structural event refreshes it, a map ticket's resolve does not), never a reason to drop it as a capture target. Only a **closed** map is no longer eligible: then the whiteboard is the right fallback. Whiteboard tickets get the `trailhead:whiteboard` label and **no** map label or `Parent:`; map tickets get the map's `trailhead:map-<n>` label + native sub-issue edge. **No map open at all -> whiteboard, no ask** (nowhere else to go). **A capture fired mid-ticket** (scope that surfaced while working another ticket) follows this routing instead: the current map when the new work serves its destination and is not a line its *Out of scope* rules out, or another open map when the topic plainly is that map's (read the maps' titles and destinations, not their tickets), no ask; otherwise the which-map ask above, listing every open map by name and number (the current one first) and then the whiteboard; never the whiteboard silently while a map is open. Under an autonomous run a case that needs the ask is a human decision: record it as a pending follow-up, never guess. The ticket carries a `Surfaced from: <ticket name+link>` line, and you say in one line what you opened and where it landed. The ask is also skipped for a `bug --of` whose origin resolves to an open map or the whiteboard (see the `bug` bullet in the capture forms). `note` and a non-sharp `idea` stay map-fog and skip this; with no open map they fall back to the global notes location (`$HOME/.claude/notes/`), or a now-sharp `idea` becomes a whiteboard ticket. **When a capture creates a whiteboard ticket, refresh the pinned dashboard** afterwards with `node <templates>/trailhead-dashboard.js --update-only` (it never opens a dashboard of its own, see `capture-core.md`) (a structural event: the whiteboard surface the dashboard indexes grew, and a whiteboard ticket has no native progress bar, so the dashboard is the only place it shows); a capture landing on a *map* does not (native sub-issue progress bars track map tickets). The whiteboard has no native progress bar, which is why its births refresh the dashboard.
10
10
 
11
11
  - **`note`** → a verbatim line in `## Not yet specified` (outside a project → `$HOME/.claude/notes/`). No questions, no rewriting.
12
12
  - **`idea`** → fog by default in `## Not yet specified`; becomes a ticket only if already phrasable as a sharp question. An idea in the fog is **not executable**: to work it, it must first **graduate** into a ticket (which happens when the frontier reaches it, or when you can now phrase it sharply). A `todo`, by contrast, is born a `build` ticket and is workable immediately.
@@ -24,3 +24,28 @@ Capture without breaking flow, but with the **tracker** as destination, not scat
24
24
  | **todo** | a frontier ticket | **now** | *I'm doing it* |
25
25
 
26
26
  Discriminator: not even work → `note`; maybe, once it's clear → `idea`; yes, when X happens → `seed`; yes, now → `todo`. The close pairs: `note` vs `idea`: raw text vs a work-candidate meant to graduate; `idea` vs `seed`: waiting on *clarity* vs waiting on a *condition*; `seed` vs `todo`: committed *later* vs committed *now*.
27
+
28
+ ## Scripted capture for automations
29
+
30
+ For a run with no chat session (CI, cron, a bot), capture through the helper instead of hand-building labels: `node <templates>/trailhead-capture.js --type bug|todo|idea|seed --title <t> (--body <b> | --body-file <path|->) --repo <owner/name> [--map <n> | --of <n>] [--trigger <text>] [--unverified] [--dedupe-key <key>] [--no-dashboard]`. `--trigger` is required for `seed` and rejected otherwise; `--of` is only for `bug` and excludes `--map`; `--unverified` is rejected for `idea` (fog is already off the frontier); `--dedupe-key` matches `^[A-Za-z0-9._:-]{1,100}$`. Nothing is created when a pre-check fails.
31
+
32
+ For GitHub workflows, the composite Action `actions/capture` (`uses: ToRvaLDz/trailhead/actions/capture@<tag>`) is the ready-made wrapper: it maps its inputs 1:1 to these flags, runs this helper from the action checkout and exposes `issue`, `url` and `status` as outputs. It adds no logic of its own and needs `permissions: issues: write`.
33
+
34
+ **Output and exit codes.** One JSON line on stdout, always:
35
+ - `{"status":"created",...}`, exit 0: `issue`, `url`, `type`, `destination` (`map-<n>` or `whiteboard`; `none` for an `idea` without `--map`), `labels`, `trust` (`ok`, `allowlisted` or `actions-token`), `edge` (map tickets), `dashboard`, `warnings`. Printed only once the labels are read back and the trust is verified.
36
+ - `{"status":"existing",...}`, exit 0: the dedupe key matched an open issue, nothing was created.
37
+ - `{"status":"partial",...}`, exit 3: the issue is created and labelled but the native sub-issue edge failed after one retry (for example the 100 sub-issue cap). The `trailhead:map-<n>` label keeps it on the map frontier; the warning points at the prune command (`/trailhead:prune`). The exit is non-zero so a caller checking only the status never mistakes it for success.
38
+ - `{"status":"error","reason":...}`, exit 1 (`"trust":"insufficient"` on a trust failure, `issue` and `url` when an issue was created before the failure).
39
+ - `{"status":"usage","reason":...}`, exit 2, the message also on stderr.
40
+
41
+ **Trust.** The identity running the helper must be one the label guard Action never strips: write, maintain or admin access, or a login in the repository's own `TRAILHEAD_LABEL_ALLOWLIST` variable (the helper never consults its local environment). An identity `gh api user` cannot read (an Actions `GITHUB_TOKEN` or a GitHub App token) is verified after the create from the server-side record of who applied the labels: `github-actions[bot]` (its events trigger no workflow), an allowlisted or a write actor pass; anyone else, or unreadable events, exits 1 with `trust: insufficient` and no edge, leaving a plain inbox issue.
42
+
43
+ **Shapes.** `bug` is `trailhead:ticket` + `trailhead:bug`, `todo` is `trailhead:ticket` + `trailhead:build`, `seed` adds `trailhead:seed` + `trailhead:blocked` and a `Trigger:` line with a `## Blocked by` section. `--map <n>` adds `trailhead:map-<n>`, the `Parent:` first line and the native edge; otherwise `trailhead:whiteboard`. `--unverified` adds `trailhead:unverified`. `--of <n>` inherits the origin's map or the whiteboard (else exit 1, `origin destination unresolved`) and adds a `Regression of:` line.
44
+
45
+ **`idea` is parked fog, not a ticket.** It becomes a `trailhead:fog` issue (off the frontier, a clarification space) with no type label, no map label, no edge and no dashboard refresh; with `--map` its body opens with a `Parent:` line. It shows in the inbox's parked-fog list and the map render's fog count, but not in the map body's own `Not yet specified` list.
46
+
47
+ **Dedupe.** With `--dedupe-key` the body carries `<!-- trailhead-dedupe: <key> -->` and a rerun returns `existing`. The search is the fast path; when it returns its 1000-result cap the helper scans every open issue instead. The one residual is search-index lag: a near-simultaneous run may not yet see an issue created seconds earlier.
48
+
49
+ **Dashboard.** Only a whiteboard ticket refreshes the dashboard (`--update-only`, never opening one), unless `--no-dashboard`. A refresh failure leaves the status unchanged and reports `dashboard: "failed"`. Map tickets and fog report `not-applicable`.
50
+
51
+ **Settle residual.** There is no settle window: the labelling actor is verified as one the guard never acts on before any edge and before `created` is printed, so no asynchronous strip can follow a `created`.