@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
@@ -14,8 +14,8 @@ A ticket whose body carries a `UAT of: [<parent name>](<parent url>)` line is a
14
14
  ## `decision`: HITL, *default for decision tickets*
15
15
  Close a choice, but widen before you narrow:
16
16
 
17
- 1. **Diverge (only if the option space isn't already clear).** Mapping the frontier usually framed the options already; when it did, check that the framed set is still current and complete before grilling: re-read it against the ticket and against what has landed since (the map's `Decisions so far`, tickets closed after it was framed), add any missing candidate (the status quo, "leave it as it is", included when it is a real option), post the additions as an `OPTIONS` comment, then grill. When it didn't (the ticket names a question but the candidate answers aren't laid out) open the space *first*: brainstorm the plausible options, name each concretely, and post them as an `OPTIONS` comment. Divergence has no judgement yet: surface possibilities, don't rank them. If a missing **fact** is what's blocking clarity, spin a `research` ticket and block on it; if the question is "how could it look/behave", spin a `prototype`. Don't grill a space you haven't opened.
18
- 2. **Converge.** Run the **Grilling** + **Domain vocabulary** techniques over the options to pick one. Before the final confirmation, run the **Holistic review before the final confirmation** (Grilling technique). Record the decision and the why. The delegate option (agent picks) is never offered here, the choice stays the human's, but **defer is offered** (in prose, per the recipe in `../../_shared/choices.md`) when the choice is genuinely blocked on a prerequisite.
17
+ 1. **Diverge (only if the option space isn't already clear).** Mapping the frontier usually framed the options already; when it did, check that the framed set is still current and complete before grilling: re-read it against the ticket and against what has landed since (the map's `Decisions so far`, tickets closed after it was framed), add any missing candidate (the status quo, "leave it as it is", included when it is a real option), post the additions as an `OPTIONS` comment (the record only; the ask itself follows **The ask is answerable from the chat alone**), then grill. When it didn't (the ticket names a question but the candidate answers aren't laid out) open the space *first*: brainstorm the plausible options, name each concretely, and post them as an `OPTIONS` comment. Divergence has no judgement yet: surface possibilities, don't rank them. If a missing **fact** is what's blocking clarity, spin a `research` ticket and block on it; if the question is "how could it look/behave", spin a `prototype`. Don't grill a space you haven't opened.
18
+ 2. **Converge.** Each time you ask, show the options in chat per **The ask is answerable from the chat alone** (Grilling technique). Run the **Grilling** + **Domain vocabulary** techniques (first run the routing step at the top of `../../_shared/techniques/grilling.md`, then the rounds as **Who runs a round** there says) over the options to pick one. Before the final confirmation, run the **Holistic review before the final confirmation** (Grilling technique). Record the decision and the why. The delegate option (agent picks) is never offered here, the choice stays the human's, but **defer is offered** (in prose, per the recipe in `../../_shared/choices.md`) when the choice is genuinely blocked on a prerequisite.
19
19
  3. **Carry it into build.** A decision that only gets recorded builds nothing: when a fog area graduated into this ticket, its entry left *Not yet specified*, so once the ticket closes the work it implies lives nowhere unless a ticket carries it. So, before the close, list the work the answer makes buildable (the code, the content, the setup it commits the project to) and give every piece a home: an **existing** open ticket that already covers it (a `build`, `bug` or `task` on the map, its body extended with a `Decided in: <name+link>` line when it lacks one), or a **new `build` ticket** on the same map (on the whiteboard for a whiteboard decision), create-then-wire per `../../_shared/substrate.md`, its `## Question` naming the deliverable and its body a `Decided in: <name+link>` line back to this ticket, labelled `trailhead:blocked` and wired to the open tickets it really waits on (another unresolved decision, the build it extends). Size each one to a session, as at charting; a sprawling answer gets several builds, not one. Put the proposed split (titles, one line of scope each, blockers) in front of the human inside the **final confirmation** of step 2, so the scope of what gets built is theirs too, then create the tickets. The resolution comment carries one `Builds: <name+link>, <name+link>` line listing every ticket (new or existing) that carries the work. Only a decision whose answer builds nothing (a pure policy, a naming, a ruling that a feature is dropped) writes `Builds: none (<why>)` instead, with the reason. **Never `gh issue close` a decision whose resolution comment lacks the `Builds:` line**: the line is what lets the map, and `/trailhead:audit`, tell a carried decision from a stranded one.
20
20
 
21
21
  The two phases are distinct on purpose: divergence generates, grilling chooses. Never let the agent invent the options *and* pick for the human: the choice stays theirs.
@@ -31,21 +31,21 @@ The ticket that *builds*. Lean cycle, all in **ticket comments**:
31
31
 
32
32
  **Size triage (`effort`-gated, before Discuss).** When `config.effort` is `lean`, run the **size triage and ask-to-skip offer defined canonically under effort in `../../_shared/configuration-reference.md`** before Discuss: classify the change, and on trivial/small surface the one consolidated offer (its skippable steps here: the plan subagent -> a short inline plan; TDD; Cross-AI plan review; the full Code-review subagent -> a lighter inline pass; the Goal-backward verification subagent -> a quick inline goal check), going lean **only on an explicit yes**. On `normal`, on a no/silence/ambiguity, and always under `effort: standard` (the default), run the full cycle below unchanged. The correctness spine (atomic `Refs:` commits, the tests / the plan's criterion, the post-commit self-check, Resolve) is never in the offer.
33
33
 
34
- 1. **Discuss**: **never auto-grill.** Start from Plan. If reading the ticket surfaces blocking ambiguity, **stop and ask the user** whether they want a **Grilling** round: don't launch it on your own initiative. Only on their assent run the short round (ending with the **Holistic review before the final confirmation** of the Grilling technique) and post a `DISCUSS` comment with the closed choices, recorded after the review. The user may also ask for it themselves at any time. Advisory choices raised here **offer** the delegate/defer options, per the injection recipe in `../../_shared/choices.md`.
35
- 2. **Plan**: a `PLAN` comment: the steps, the seams, the files touched, the verification criteria. Apply **TDD** per `config.tdd` (`seams`/`on`/`off`). Produce this plan via a *planner subagent* on `config.models.plan` (dispatch the **`trailhead-plan`** agent, read-only, passing `config.models.plan` as the model override; inline when the model is unset or equals the session model; Execute then runs as its own subagent, see below). **Report (orchestrator).** Run the report-status check on the planner's report (`--technique plan`, the *Malformed review report* protocol in `../../_shared/techniques.md`) and branch on the checker's `verdict`: `status: ready` -> post the checker's `body` verbatim as the `PLAN` comment; `status: needs_input` -> ask the user the `questions:` (the checker's `fields`), then re-spawn the planner with the answers; never answer them yourself or guess. If `config.plan_review` is on, run **Cross-AI plan review** before Execute: send the plan to external AI CLIs and converge on their concerns. Now that the files/seam are known, run the **undeclared-coupling check** (`../../_shared/teamwork.md`) against in-progress tickets. **Plan to sufficiency:** stop once you can name the steps, seams, and files with confidence, and never re-read a file already read in this planning pass; act when you have enough to plan, rather than re-deriving what the pass already established. **Coverage check before `ready`.** Before the PLAN is returned (the short inline plan too), map every `Done when` criterion of the ticket (its `Question` when it has none) to the step(s) and the verification criterion that deliver it, and keep that mapping in the PLAN's verification criteria: a criterion no step delivers is a gap, so add the step, or return `needs_input` when closing it needs a decision. Where the PLAN picks between approaches, the pick is stress-tested per **Stress-test the pick before offering it** in `../../_shared/choices.md`, its `Weak point:` line kept in the PLAN. **The single exception:** under an accepted `lean` skip (see the Size triage above), produce a short inline plan here instead of the planner subagent, and skip Cross-AI plan review; absent that accepted skip, this step runs in full as described.
34
+ 1. **Discuss**: **never auto-grill.** Start from Plan. If reading the ticket surfaces blocking ambiguity, **stop and ask the user** whether they want a **Grilling** round: don't launch it on your own initiative. Only on their assent run the short round (first run the routing step at the top of `../../_shared/techniques/grilling.md`, then the rounds as **Who runs a round** there says) (ending with the **Holistic review before the final confirmation** of the Grilling technique) and post a `DISCUSS` comment with the closed choices, recorded after the review. The user may also ask for it themselves at any time. Advisory choices raised here **offer** the delegate/defer options, per the injection recipe in `../../_shared/choices.md`.
35
+ 2. **Plan**: **First, route it.** Run `node <hooks>/lib/model-route.js plan --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 `agent` with model `alias` (`agent_type=<agent>` on Codex) and check its report as below; on `route: inline` write the plan in the session. Finding `<hooks>`, the flags and the only two exceptions are in **Route every step through model-route.js** in `../../_shared/techniques.md`. The step produces a `PLAN` comment: the steps, the seams (each with its one-line catches/misses, per **TDD**), the files touched, the verification criteria. Apply **TDD** per `config.tdd` (`seams`/`on`/`off`). Produce this plan via the route: a *planner subagent* on `config.models.plan` (the read-only **`trailhead-plan`** agent) on dispatch, the session on inline; Execute then runs as its own subagent, see below. **Report (orchestrator).** Run the report-status check on the planner's report (`--technique plan`, the *Malformed review report* protocol in `../../_shared/techniques.md`) and branch on the checker's `verdict`: `status: ready` -> post the checker's `body` verbatim as the `PLAN` comment; `status: needs_input` -> ask the user the `questions:` (the checker's `fields`; when the user opts to grill them, first run the routing step at the top of `../../_shared/techniques/grilling.md`, then the rounds as **Who runs a round** there says), then re-spawn the planner with the answers; never answer them yourself or guess. If `config.plan_review` is on, run **Cross-AI plan review** before Execute: send the plan to external AI CLIs and converge on their concerns. Now that the files/seam are known, run the **undeclared-coupling check** (`../../_shared/teamwork.md`) against in-progress tickets. **Plan to sufficiency:** stop once you can name the steps, seams, and files with confidence, and never re-read a file already read in this planning pass; act when you have enough to plan, rather than re-deriving what the pass already established. **Coverage check before `ready`.** Before the PLAN is returned (the short inline plan too), map every `Done when` criterion of the ticket (its `Question` when it has none) to the step(s) and the verification criterion that deliver it, and keep that mapping in the PLAN's verification criteria: a criterion no step delivers is a gap, so add the step, or return `needs_input` when closing it needs a decision. Where the PLAN picks between approaches, the pick is stress-tested per **Stress-test the pick before offering it** in `../../_shared/choices.md`, its `Weak point:` line kept in the PLAN. **The single exception:** under an accepted `lean` skip (see the Size triage above), produce a short inline plan here instead of the planner subagent, and skip Cross-AI plan review; absent that accepted skip, this step runs in full as described.
36
36
  3. **Execute**: the orchestrator runs the HITL and tracker moves around an **executor subagent on `config.models.execute`**; the implementing protocol itself lives in `executor-brief.md` (this directory), the only protocol text the agent receives from this step. The agent cannot ask the user and never writes to the tracker, pushes or creates a branch.
37
37
  - **Prototype FIRST (orchestrator).** **If this ticket changes user-facing UI (a new screen, a redesign, a changed layout), run the Prototype technique FIRST, before any real UI code**: surface a mockup to react to (its clickable link always in the same message as the approval question, per the Prototype technique; the guard also accepts a link inside the approval question itself, the prose stays preferred), routed by `config.design` (disk / artifacts / stitch) and gated by `config.design.approval` (`explicit` waits for a go-ahead, `auto` proceeds after surfacing). **Route by `config.design` and never shortcut a hosted mode to a local disk mockup**: when `config.design` is `artifacts` (or the legacy `claude.ai/design`) or `stitch`, the mockup is built there (reusing a cached `design.project`); `disk` is only for `config.design: disk` or the technique's explicit, announced fallback, never a convenience default. Read `config.design` before producing anything. For a redesign or new screen the default is to offer mockups; don't jump straight to code on a UI ticket (full statement in the **Prototype** technique, `../../_shared/techniques/prototype.md`).
38
38
  - **Branch and worktree (orchestrator).** Following the repo's `trailhead:conventions` `git:`, straight to `main` by default, or a feature branch + PR when `git: pr` (the orchestrator creates the branch before the spawn and opens the PR; the executor only commits, never pushes, switches branch or writes to the tracker). **Under `isolation: worktree`, the work happens in this ticket's `git worktree`** (`trailhead/t<n>` branch), integrating to the trunk at Resolve (see `../../_shared/teamwork.md`).
39
- - **Dispatch.** Spawn the **`trailhead-executor`** agent (full tools that can Edit/Write/commit), passing `config.models.execute` as the model override and `executor-brief.md` as the only protocol text, plus the runtime inputs: the ticket number, the PLAN, the base SHA, the `config.tdd` value, the approved mockup link if any, the accepted mockup deviations, and, for a resumed spawn, the packages the user approved. Inline when the model is unset or equals the session model: the session then does both parts. Inline, the session reads `executor-brief.md` and applies it, asking the user directly at the points the brief says to report. The agent implements with **atomic commits** (conventional commits, each with a `Refs: #<n>` trailer for this ticket, see **Principles** in `../../_shared/principles.md`; the trailer is also what scopes Verify's review and goal check, per **Ticket diff** in `../../_shared/techniques/code-review.md`; one commit = one verifiable step); the commits land on `main`, visible.
39
+ - **Dispatch.** Spawn the **`trailhead-executor`** agent (full tools that can Edit/Write/commit) only as the route says. **First, route it.** Run `node <hooks>/lib/model-route.js execute --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 (flags, `<hooks>` lookup and the two exceptions: **Route every step through model-route.js** in `../../_shared/techniques.md`). On `route: dispatch` spawn it with the route's `alias` as the model override (`agent_type=<agent>` on Codex) and `executor-brief.md` as the only protocol text, plus the runtime inputs: the ticket number, the PLAN, the base SHA, the `config.tdd` value, the approved mockup link if any, the accepted mockup deviations, and, for a resumed spawn, the packages the user approved. On `route: inline` the session does both parts: it reads `executor-brief.md` and applies it, asking the user directly at the points the brief says to report. The agent implements with **atomic commits** (conventional commits, each with a `Refs: #<n>` trailer for this ticket, see **Principles** in `../../_shared/principles.md`; the trailer is also what scopes Verify's review and goal check, per **Ticket diff** in `../../_shared/techniques/code-review.md`; one commit = one verifiable step); the commits land on `main`, visible.
40
40
  - **Fidelity (orchestrator).** The approved mockup is reproduced with **100% fidelity** (layout, spacing, sizing, colour, type, copy, and every state), never a loose approximation. **Make fidelity verifiable, not a hope:** the executor returns the enumerated fidelity list in its report and the orchestrator posts it as a ticket comment (noting any element raised as infeasible with its resolution), so Verify works from a durable checklist instead of reconstructing intent. At Verify, **Acceptance testing** checks the built screen against the approved mockup (element by element, state by state) and **Goal-backward verification** carries fidelity as an explicit goal claim (see `../../_shared/techniques/acceptance-testing.md` and `../../_shared/techniques/verify.md`).
41
41
  - **Infeasible element (orchestrator).** When the report marks an element infeasible, raise it with the user and settle it: revise and re-approve the mockup, or the user accepts a named deviation. Then re-spawn the executor with the settled mockup and the accepted deviations; never let it deviate silently.
42
42
  - **Flagged package (orchestrator).** The **verify-before-install gate** (slopsquatting barrier) runs in the executor (legitimacy, typosquat similarity, age, downloads, repository metadata). On a flagged package in the report, block-and-ask: stop and ask the user, never installing on your own judgement. Only on an explicit yes, re-spawn the executor with that package passed as user-approved (it then re-runs the exact install command prefixed with `TRAILHEAD_VERIFIED_INSTALL=1`); on a no it is not installed and the plan is revisited with the user. On a host with a hook bus the `trailhead-install-guard` `PreToolUse` hook enforces the gate, blocking a named public-registry install (npm / PyPI / crates.io / Go / RubyGems) until it is vetted.
43
43
  - **Surfaced scope (orchestrator).** New scope surfacing mid-execute, reported by the executor in `surfaced scope:`, is captured/split, not worked here.
44
44
  - **Report (orchestrator).** Run the report-status check on the agent's report (`--technique executor`, the *Malformed review report* protocol in `../../_shared/techniques.md`) and branch on the checker's `verdict`, reading its `fields`: never reconstruct a result from the prose. `status: done` -> Verify. `status: partial` or `blocked` -> act on each `blockers:` entry with the moves above (flagged package, infeasible element; a foreign change in the way is raised with the user), then re-spawn. Post `fidelity:` as the fidelity comment and route `surfaced scope:` as below. Check `commits:` against the repo: it must equal the `Refs: #<n>` commit set since the base SHA, in both directions, and every SHA must resolve (`git cat-file -e <sha>^{commit}`); a mismatch is surfaced to the user, never patched over.
45
- 4. **Verify**: a `VERIFY` comment: **start with the Stale-base preflight** (`trailhead-base-preflight.js --sync`, **Stale-base preflight** in `../../_shared/teamwork.md`): preflight, then sync, then re-run the tests, all before anything is reviewed (a rebase rewrites SHAs the review ledger cites, and the Code review and goal check must see the synced tree); then run the tests / the plan's criterion; then the **Code review** technique, scoped to the ticket's own commits per its **Ticket diff** rule (`../../_shared/techniques/code-review.md`: the `Refs: #<n>` set, read per commit, recomputed each round, an empty set surfaced as an error, never a `<base>...HEAD` range) (its subagent reviews *adversarially*: it doesn't defer to a `CLAUDE.md` convention or a "settled decision" memory), and carries the review's counts line and findings ledger, per code-review.md, **run in the background and collected by polling, never a blind blocking wait** (a hard engine default on every host, see *Reviews never block the session* in `../../_shared/techniques.md`); run the report-status check before branching on status (each report, from the subagent or inline, goes through the *Malformed review report* protocol in `../../_shared/techniques.md`, which also fixes the layout of the round's single `VERIFY` comment); **read the review's leading `status:` tag to branch deterministically: `status: clean` -> proceed to Resolve, `status: issues_found` -> iterate the fix-and-re-review loop (or run the HITL checkpoint) before resolving**; then the **Goal-backward verification** technique on `config.models.verify` (does the change deliver what the ticket promised, backward from its `Question` and the `PLAN`'s criteria; the read-only `trailhead-verify` subagent, on the same **Ticket diff** set), **run in the background and collected by polling** like Code review, branching on its leading `status:` tag (`status: met` -> proceed, `status: gaps_found` -> loop back to Execute for the gap or run the HITL checkpoint before resolving, `status: inconclusive` as the leading tag (all gaps cleared) -> a per-reason-class HITL checkpoint with no auto-retry, never auto-resolving over the unevaluated goal); then **Acceptance testing** if the change is user-facing, opening with the UAT setup (`trailhead-uat-setup.js`, after the preflight) and then the drive-mode ask (`../../_shared/techniques/acceptance-testing.md`): AI-driven, or **walk the user through a guided UAT conversationally, one step at a time**, never a checklist dumped in a comment (or skip (waive acceptance), recorded as waived, if the user so chooses): interactive by design, so it runs inline, not backgrounded. Report the outcome honestly (if a test fails, say so). **The single exception:** under an accepted `lean` skip, run a lighter inline review pass instead of the full Code-review subagent and a quick inline goal check instead of the full Goal-backward verification subagent (or skip either only when the change is truly cosmetic); the lighter inline review still uses the same finding IDs, counts line, and ledger, and the same no-`open` gate, per code-review.md; the tests / criterion and the honest outcome report still run regardless. At a HITL checkpoint here, an advisory sub-choice (which fix, how to proceed) **offers** the delegate option, per the injection recipe in `../../_shared/choices.md`; the accept/resolve decision stays the human's.
46
- 5. **Resolve**: a resolution comment with what was done → `gh issue close` → update `Decisions so far` on the map (default path: `node <templates>/trailhead-issue-body.js append --issue <map> --section "Decisions so far" --line "<pointer>"`, a body read-modify-write that obeys the **Fail-safe body rewrites** rule in `../../_shared/substrate.md` for you; `<templates>` is the same three-dir lookup as for `trailhead-work-state.js` (**Work state file** in `../../_shared/teamwork.md`); when the helper is missing, do the read-modify-write by hand per that rule). **Disposition gate (before the close)**: the latest `VERIFY` comment's findings ledger covers every finding the counts line counts and none of them is `open`; the resolution comment links that ledger rather than duplicating it; otherwise do not `gh issue close` (per code-review.md's Disposition gate). **Stale-base preflight (before integrating).** Run the preflight per **Stale-base preflight** in `../../_shared/teamwork.md` (`git: main`: right before the trunk push; `git: pr` without isolation: on the ticket branch, immediately before the push and PR; `worktree`/`clone`: on the main checkout, then on the ticket branch) and integrate only on `current`/`synced` with the tests green and `checksOk`. **Pending-UAT guard (before the close).** If Verify left acceptance testing **not yet run** (it would otherwise be deferred), **pose the two-way choice to the user and wait for an explicit answer, never open a ticket unilaterally**: (a) run the UAT now, in this session, with no ticket (the stale-base preflight first, then the UAT setup, then the drive-mode ask, `../../_shared/techniques/acceptance-testing.md`), or (b) open a UAT follow-up ticket to run it later. (Under an autonomous run the guard takes option (b) itself for the human-only steps, reusing an open follow-up instead of opening a second one: see `auto.md`.) On (a), run the guided walkthrough now and resolve on a confirmed pass, or on an explicit waiver if the user skips at the ask (recorded as unconfirmed; the resolution comment states it) (a failing step becomes a fix or a `bug` follow-up and an incomplete/blocked step is marked pending and tracked, per the guided-UAT rules in `../../_shared/techniques/acceptance-testing.md`, not a silent close); on (b), **open the follow-up ticket carrying the owed UAT first**, linked back to this parent (a `task` on the frontier or a `trailhead:seed` gated on the run, per *Pending UAT must stay tracked* in `../../_shared/techniques/acceptance-testing.md`). **Never open a UAT ticket without asking, never `gh issue close` on a chosen-deferred UAT with nothing opened, and never let silence or an ambiguous reply default to either path.** When acceptance was already completed at Verify, or was **explicitly waived** by the user at the drive-mode ask (a waiver is not a deferral: nothing is owed, so nothing is opened; the resolution comment states the waiver), this guard doesn't fire. **Post-commit self-check (before the comment goes out).** For each commit the comment cites, confirm the hash resolves (`git cat-file -e <hash>^{commit}`, or `git show --stat <hash>`) and that every file the comment names as touched actually appears in that commit's diff (`git -c core.quotepath=false show --name-only --format= <hash>`, with `core.quotepath=false` so a non-ASCII filename compares plainly instead of in git's C-quoted form); correct or drop any citation that fails the check, so a hallucinated or mis-stated commit never lands in the tracker record. **If this change materially altered the codebase** (a new module/layer, dependency, seam, or build/test command), **patch the affected facet of the `trailhead:codebase` issue** (see **Codebase** in `../../_shared/substrate.md`; look it up per its singleton fail-closed rule) in place (a line or two, not a re-map). When a structural event is due (a whiteboard ticket resolved, a map exhausted), refresh the dashboard with `node <templates>/trailhead-dashboard.js` (one call, whole body from the live tracker; by hand per `../../_shared/substrate-commands.md` when the helper is missing). Then close the session with the **Session handoff** ritual (`../../_shared/session-handoff.md`): one `node <templates>/trailhead-work-state.js clear --repo <working-root>` removes the work state and the session-ticket marker, then `/clear` + next command.
45
+ 4. **Verify**: a `VERIFY` comment: **start with the Stale-base preflight** (`trailhead-base-preflight.js --sync`, **Stale-base preflight** in `../../_shared/teamwork.md`): preflight, then sync (the preflight never runs the suite itself), all before anything is reviewed (a rebase rewrites SHAs the review ledger cites, and the Code review and goal check must see the synced tree); then, when `testing.full` is configured, Verify runs the full suite once on the synced tree (the single run for that tree, together with the plan's criterion), through `node <templates>/trailhead-test-run.js run --repo <working-root> --config-root <project root>`, and records the helper's JSON as a one-line **test record** in the `VERIFY` test outcome: `test record: <command> · tree <sha> · <green | failed (exit <n>)> · source <run | cache>` (built from `command`, `tree`, `exitCode`, `source`), passed verbatim to the Code review and Goal-backward verification agents, which never re-run the full suite. A failed record is read back from the latest `VERIFY` comment: on the same tree (`git -C <repo> rev-parse HEAD^{tree}`) reuse it instead of invoking the helper (it caches only greens); a fix round that changes the tree gets one new run on the new tree. The helper's `unconfigured` falls back to the project's own command, run once on the synced tree; no test record is built and the reviewing agents get none, so they test as today (Execute and Fix focused runs still use `--kind targeted --files <a,b>`); then the **Code review** technique, scoped to the ticket's own commits per its **Ticket diff** rule (`../../_shared/techniques/code-review.md`: the `Refs: #<n>` set, read per commit, recomputed each round, an empty set surfaced as an error, never a `<base>...HEAD` range) (its subagent reviews *adversarially*: it doesn't defer to a `CLAUDE.md` convention or a "settled decision" memory), and carries the review's counts line and findings ledger, per code-review.md, **run in the background and collected by polling, never a blind blocking wait** (a hard engine default on every host, see *Reviews never block the session* in `../../_shared/techniques.md`); run the report-status check before branching on status (each report, from the subagent or inline, goes through the *Malformed review report* protocol in `../../_shared/techniques.md`, which also fixes the layout of the round's single `VERIFY` comment); **read the review's leading `status:` tag to branch deterministically: `status: clean` -> proceed to Resolve, `status: issues_found` -> iterate the fix-and-re-review loop (or run the HITL checkpoint) before resolving**; then the **Goal-backward verification** technique on `config.models.verify` (does the change deliver what the ticket promised, backward from its `Question` and the `PLAN`'s criteria; the read-only `trailhead-verify` subagent, on the same **Ticket diff** set), **run in the background and collected by polling** like Code review, branching on its leading `status:` tag (`status: met` -> proceed, `status: gaps_found` -> loop back to Execute for the gap or run the HITL checkpoint before resolving, `status: inconclusive` as the leading tag (all gaps cleared) -> a per-reason-class HITL checkpoint with no auto-retry, never auto-resolving over the unevaluated goal); then **Acceptance testing** if the change is user-facing, opening with the UAT setup (`trailhead-uat-setup.js`, after the preflight) and then the drive-mode ask (`../../_shared/techniques/acceptance-testing.md`): AI-driven, or **walk the user through a guided UAT conversationally, one step at a time**, never a checklist dumped in a comment (or skip (waive acceptance), recorded as waived, if the user so chooses): interactive by design, so it runs inline, not backgrounded. Report the outcome honestly (if a test fails, say so). **The single exception:** under an accepted `lean` skip, run a lighter inline review pass instead of the full Code-review subagent and a quick inline goal check instead of the full Goal-backward verification subagent (or skip either only when the change is truly cosmetic); the lighter inline review still uses the same finding IDs, counts line, and ledger, and the same no-`open` gate, per code-review.md; the tests / criterion and the honest outcome report still run regardless. At a HITL checkpoint here, an advisory sub-choice (which fix, how to proceed) **offers** the delegate option, per the injection recipe in `../../_shared/choices.md`; the accept/resolve decision stays the human's.
46
+ 5. **Resolve**: a resolution comment with what was done → `gh issue close` → update `Decisions so far` on the map (default path: `node <templates>/trailhead-issue-body.js append --issue <map> --section "Decisions so far" --line "<pointer>"`, a body read-modify-write that obeys the **Fail-safe body rewrites** rule in `../../_shared/substrate.md` for you; `<templates>` is the same three-dir lookup as for `trailhead-work-state.js` (**Work state file** in `../../_shared/teamwork.md`); when the helper is missing, do the read-modify-write by hand per that rule). **Disposition gate (before the close)**: the latest `VERIFY` comment's findings ledger covers every finding the counts line counts and none of them is `open`; the resolution comment links that ledger rather than duplicating it; otherwise do not `gh issue close` (per code-review.md's Disposition gate). **Stale-base preflight (before integrating).** Run the preflight per **Stale-base preflight** in `../../_shared/teamwork.md` (`git: main`: right before the trunk push; `git: pr` without isolation: on the ticket branch, immediately before the push and PR; `worktree`/`clone`: on the main checkout, then on the ticket branch) and integrate only on `current`/`synced` with the tests green and `checksOk`. **Pending-UAT guard (before the close).** If Verify left acceptance testing **not yet run** (it would otherwise be deferred), **pose the two-way choice to the user and wait for an explicit answer, never open a ticket unilaterally**: (a) run the UAT now, in this session, with no ticket (the stale-base preflight first, then the UAT setup, then the drive-mode ask, `../../_shared/techniques/acceptance-testing.md`), or (b) open a UAT follow-up ticket to run it later. (Under an autonomous run the guard takes option (b) itself for the human-only steps, reusing an open follow-up instead of opening a second one: see `auto.md`.) On (a), run the guided walkthrough now and resolve on a confirmed pass, or on an explicit waiver if the user skips at the ask (recorded as unconfirmed; the resolution comment states it) (a failing step becomes a fix or a `bug` follow-up and an incomplete/blocked step is marked pending and tracked, per the guided-UAT rules in `../../_shared/techniques/acceptance-testing.md`, not a silent close); on (b), **open the follow-up ticket carrying the owed UAT first**, linked back to this parent (a `task` on the frontier or a `trailhead:seed` gated on the run, per *Pending UAT must stay tracked* in `../../_shared/techniques/acceptance-testing.md`). **Never open a UAT ticket without asking, never `gh issue close` on a chosen-deferred UAT with nothing opened, and never let silence or an ambiguous reply default to either path.** When acceptance was already completed at Verify, or was **explicitly waived** by the user at the drive-mode ask (a waiver is not a deferral: nothing is owed, so nothing is opened; the resolution comment states the waiver), this guard doesn't fire. **Post-commit self-check (before the comment goes out).** For each commit the comment cites, confirm the hash resolves (`git cat-file -e <hash>^{commit}`, or `git show --stat <hash>`) and that every file the comment names as touched actually appears in that commit's diff (`git -c core.quotepath=false show --name-only --format= <hash>`, with `core.quotepath=false` so a non-ASCII filename compares plainly instead of in git's C-quoted form); correct or drop any citation that fails the check, so a hallucinated or mis-stated commit never lands in the tracker record. **If this change materially altered the codebase** (a new module/layer, dependency, seam, or build/test command), **patch the affected facet of the `trailhead:codebase` issue** (see **Codebase** in `../../_shared/substrate.md`; look it up per its singleton fail-closed rule) in place (a line or two, not a re-map). The patch passes `--stamp <short sha>` (`git -C <working-root> rev-parse --short <commit>`), always a commit reachable from the trunk: the trunk tip after integration, so a rebase at the preflight cannot orphan it. When a PR is not merged yet at Resolve (`git: pr`), the stamp is the fork point (`git merge-base HEAD origin/<trunk>`), so a squash or rebase merge cannot orphan it either; the PR's own changes then read as stale until the next patch, a false alarm and never a false `current`. When the helper is missing and the patch is made by hand, update the facet's `_as of <short sha>_` stamp line too. When a structural event is due (a whiteboard ticket resolved, a map exhausted), refresh the dashboard with `node <templates>/trailhead-dashboard.js` (one call, whole body from the live tracker; by hand per `../../_shared/substrate-commands.md` when the helper is missing). **Unblock dependents (every ticket, map or whiteboard).** Right after the close, whatever the ticket type and whether or not there is a map (independent of the map-only `Decisions so far` update above): run `node <templates>/trailhead-unblock.js --issue <n> --repo <owner/name>` (same `<templates>` lookup), which removes `trailhead:blocked` from every open dependent whose last open blocker just closed and prints one JSON line (`{"status":"ok","unblocked":[...],"stillBlocked":[...]}` or `{"status":"error","reason":...}`). When the helper is missing or returns `error`, do it by hand: for each open dependent carrying `trailhead:blocked` in `gh api repos/{owner}/{repo}/issues/<n>/dependencies/blocking`, read `gh api repos/{owner}/{repo}/issues/<dep>/dependencies/blocked_by` and, if every blocker there is closed, `gh issue edit <dep> --remove-label "trailhead:blocked"`; report a failure in one line. A ticket unblocked here joins the frontier, so the next-step handoff can name it. Then close the session with the **Session handoff** ritual (`../../_shared/session-handoff.md`): one `node <templates>/trailhead-work-state.js clear --repo <working-root>` removes the work state and the session-ticket marker, then `/clear` + next command.
47
47
 
48
- New scope that surfaces mid-build (while planning, executing, or testing) → **capture or split, never expand this ticket in flight** (see **Scope that surfaces while working a ticket**, served by the capture cluster). If the user defers that in-the-moment choice rather than settling it, mark the follow-up **pending** so the **Pending follow-up guard** re-poses it at handoff (`../../_shared/session-handoff.md`), instead of losing it to scrollback.
48
+ New scope that surfaces mid-build (while planning, executing, or testing) → **capture or split, never expand this ticket in flight** (see **Scope that surfaces while working a ticket**, `../../_shared/surfaced-scope.md`). If the user defers that in-the-moment choice rather than settling it, mark the follow-up **pending** so the **Pending follow-up guard** re-poses it at handoff (`../../_shared/session-handoff.md`), instead of losing it to scrollback.
49
49
 
50
50
  ## `bug`: the fix with diagnosis first
51
51
  A defect to correct. Unlike `build`, you don't start from implementation: first you **understand**.
@@ -59,9 +59,9 @@ Then run the cycle, all in ticket comments:
59
59
  **Size triage (`effort`-gated).** Under `config.effort: lean`, run the **size triage and ask-to-skip offer defined canonically under effort in `../../_shared/configuration-reference.md`**, classifying the fix as in the `build` engine. On trivial/small, offer to skip only the *heavy verification* a tiny fix does not warrant: lighten or skip **TDD**, run a **lighter inline Code review** instead of the full subagent, and a **quick inline goal check** instead of the full **Goal-backward verification** subagent, going lean **only on an explicit yes**. **Diagnose is never in the offer**: a bug always needs a confirmed root cause before Fix, whatever its size. On a no/silence/ambiguity, and under `effort: standard` (default), the full cycle runs.
60
60
 
61
61
  1. **Repro**: a `REPRO` comment: how to reproduce, expected vs observed behaviour.
62
- 2. **Diagnose**: run the **Debug** technique. Dispatch `trailhead-debug` and run the report-status check on its report (`--technique debug`, the *Malformed review report* protocol in `../../_shared/techniques.md`); branch on the checker's `verdict`, reading its `fields`: `status: found` -> its `answer:` (the cause) and `evidence:` (the falsification test and its result) feed the `DIAGNOSIS`; `status: inconclusive` -> the ticket stays in diagnosis, next hypotheses, never on to Fix; `status: needs_input` -> ask the user the `open questions:`, then re-spawn with the answers. A `DIAGNOSIS` comment with the root cause **stated as a falsifiable hypothesis plus the falsification test that confirmed it**. **Pre-fix checkpoint: only a confirmed cause proceeds to Fix** (a cause that has not survived its falsification test stays in diagnosis, looping the hypothesis steps); the full gate lives in the **Debug** technique (`../../_shared/techniques/debug.md`).
62
+ 2. **Diagnose**: **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 (flags, `<hooks>` lookup and the two exceptions: **Route every step through model-route.js** in `../../_shared/techniques.md`). On `route: dispatch` spawn `agent` with model `alias` (`agent_type=<agent>` on Codex); on `route: inline` run the **Debug** technique in the session. Then run the report-status check on the report (`--technique debug`, the *Malformed review report* protocol in `../../_shared/techniques.md`); branch on the checker's `verdict`, reading its `fields`: `status: found` -> its `answer:` (the cause) and `evidence:` (the falsification test and its result) feed the `DIAGNOSIS`; `status: inconclusive` -> the ticket stays in diagnosis, next hypotheses, never on to Fix; `status: needs_input` -> ask the user the `open questions:`, then re-spawn with the answers. A `DIAGNOSIS` comment with the root cause **stated as a falsifiable hypothesis plus the falsification test that confirmed it**. **Pre-fix checkpoint: only a confirmed cause proceeds to Fix** (a cause that has not survived its falsification test stays in diagnosis, looping the hypothesis steps); the full gate lives in the **Debug** technique (`../../_shared/techniques/debug.md`).
63
63
  3. **Fix**: the orchestrator runs the HITL and tracker moves around an **executor subagent on `config.models.execute`**; the implementing protocol itself lives in `executor-brief.md` (this directory), the only protocol text the agent receives from this step. The agent cannot ask the user and never writes to the tracker, pushes or creates a branch.
64
- - **Dispatch.** Spawn the **`trailhead-fix`** agent (full tools), passing `config.models.execute` as the model override and `executor-brief.md` as the only protocol text, plus the runtime inputs: the ticket number, the base SHA, the confirmed `DIAGNOSIS`, the `config.tdd` value, the approved mockup link if any, the accepted mockup deviations, and, for a resumed spawn, the packages the user approved. Inline when unset/same as session: the session then does both parts. Inline, the session reads `executor-brief.md` and applies it, asking the user directly at the points the brief says to report. The agent implements the correction with **atomic commits** (each with a `Refs: #<n>` trailer for this ticket, see **Principles** in `../../_shared/principles.md`; the trailer is also what scopes Verify's review and goal check, per **Ticket diff** in `../../_shared/techniques/code-review.md`), and where sensible a test that fails before and passes after (**TDD**).
64
+ - **Dispatch.** Spawn the **`trailhead-fix`** agent (full tools) only as the route says. **First, route it.** Run `node <hooks>/lib/model-route.js execute --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 (flags, `<hooks>` lookup and the two exceptions: **Route every step through model-route.js** in `../../_shared/techniques.md`). Fix routes on the `execute` key: on `route: dispatch` spawn `trailhead-fix` in place of the returned `agent`, with the route's `alias` as the model override (`agent_type=trailhead-fix` on Codex) and `executor-brief.md` as the only protocol text, plus the runtime inputs: the ticket number, the base SHA, the confirmed `DIAGNOSIS`, the `config.tdd` value, the approved mockup link if any, the accepted mockup deviations, and, for a resumed spawn, the packages the user approved. On `route: inline` the session does both parts: it reads `executor-brief.md` and applies it, asking the user directly at the points the brief says to report. The agent implements the correction with **atomic commits** (each with a `Refs: #<n>` trailer for this ticket, see **Principles** in `../../_shared/principles.md`; the trailer is also what scopes Verify's review and goal check, per **Ticket diff** in `../../_shared/techniques/code-review.md`), and where sensible a test that fails before and passes after (**TDD**).
65
65
  - **Branch and worktree (orchestrator).** Per the repo's `trailhead:conventions` `git:`, `main` by default, else a branch + PR: the orchestrator creates the branch before the spawn and opens the PR under `git: pr`; the agent only commits. Under `isolation: worktree`, the work happens in this ticket's `git worktree`.
66
66
  - **UI fix: Prototype and approval (orchestrator).** Where the fix changes a screen (a user-facing UI fix), run the Prototype technique FIRST, before any real UI code: surface a mockup, routed by `config.design` (never shortcut a hosted mode to a local disk mockup) and gated by `config.design.approval`, per the technique (`../../_shared/techniques/prototype.md`). Once approved, the executor reproduces it with 100% fidelity.
67
67
  - **Fidelity (orchestrator).** The executor returns the enumerated fidelity list in its report and the orchestrator posts it as a ticket comment, so Verify (Acceptance testing and Goal-backward verification) checks against a durable list.
@@ -69,10 +69,10 @@ Then run the cycle, all in ticket comments:
69
69
  - **Flagged package (orchestrator).** The **verify-before-install gate** runs in the executor (typosquat similarity, age, downloads, repository metadata). On a flagged package in the report, block-and-ask: stop and ask the user before anything is installed. Only on an explicit yes, re-spawn the executor with that package passed as user-approved; on a no it is not installed and the plan is revisited with the user.
70
70
  - **Surfaced scope (orchestrator).** New scope the executor reports in `surfaced scope:` is captured/split, never worked in this ticket.
71
71
  - **Report (orchestrator).** Run the report-status check on the agent's report (`--technique fix`, the *Malformed review report* protocol in `../../_shared/techniques.md`) and branch on the checker's `verdict`, reading its `fields`: never reconstruct a result from the prose. `status: done` -> Verify. `status: partial` or `blocked` -> act on each `blockers:` entry with the moves above (flagged package, infeasible element; a foreign change in the way is raised with the user), then re-spawn. Post `fidelity:` as the fidelity comment and route `surfaced scope:` as below. Check `commits:` against the repo: it must equal the `Refs: #<n>` commit set since the base SHA, in both directions, and every SHA must resolve (`git cat-file -e <sha>^{commit}`); a mismatch is surfaced to the user, never patched over.
72
- 4. **Verify**: a `VERIFY` comment: **start with the Stale-base preflight** (`trailhead-base-preflight.js --sync`, **Stale-base preflight** in `../../_shared/teamwork.md`): preflight, then sync, then re-run the tests, all before anything is reviewed (a rebase rewrites SHAs the review ledger cites, and the Code review and goal check must see the synced tree); then the repro now passes, no regressions; **Code review** on the fix, scoped to the ticket's own commits per its **Ticket diff** rule (`../../_shared/techniques/code-review.md`: the `Refs: #<n>` set, read per commit, recomputed each round, an empty set surfaced as an error, never a `<base>...HEAD` range), and carries the review's counts line and findings ledger, per code-review.md, **run in the background and collected by polling, never a blind blocking wait** (a hard engine default on every host, see *Reviews never block the session* in `../../_shared/techniques.md`); run the report-status check before branching on status (each report, from the subagent or inline, goes through the *Malformed review report* protocol in `../../_shared/techniques.md`, which also fixes the layout of the round's single `VERIFY` comment); **read the review's leading `status:` tag to branch deterministically (`status: clean` -> Resolve, `status: issues_found` -> iterate the loop or run the checkpoint first)**; then the **Goal-backward verification** technique on `config.models.verify` (does the fix deliver what the ticket promised, backward from its `Question`; the read-only `trailhead-verify` subagent, on the same **Ticket diff** set), background and collected by polling, branching on its leading `status:` tag (`status: met` -> proceed, `status: gaps_found` -> loop back to Fix for the gap or run the checkpoint, `status: inconclusive` as the leading tag (all gaps cleared) -> a per-reason-class HITL checkpoint with no auto-retry, never auto-resolving over the unevaluated goal); **Acceptance testing** if the bug was user-facing, opening with the UAT setup (`trailhead-uat-setup.js`, after the preflight) and then the drive-mode ask (`../../_shared/techniques/acceptance-testing.md`): AI-driven, or **walk the user through a guided UAT conversationally, step by step**, not a checklist to self-serve (or skip (waive acceptance), recorded as waived, if the user so chooses): interactive by design, so it runs inline, not backgrounded. At a HITL checkpoint here, an advisory sub-choice (which fix, how to proceed) **offers** the delegate option, per the injection recipe in `../../_shared/choices.md`; the accept/resolve decision stays the human's.
73
- 5. **Resolve**: a resolution comment (cause + fix) → `gh issue close` → update `Decisions so far` (default path: `node <templates>/trailhead-issue-body.js append --issue <map> --section "Decisions so far" --line "<pointer>"`, same helper and fallback as the `build` Resolve step; a body read-modify-write that obeys the **Fail-safe body rewrites** rule in `../../_shared/substrate.md`). **Disposition gate (before the close)**: the latest `VERIFY` comment's findings ledger covers every finding the counts line counts and none of them is `open`; the resolution comment links that ledger rather than duplicating it; otherwise do not `gh issue close` (per code-review.md's Disposition gate). **Stale-base preflight (before integrating).** Run the preflight per **Stale-base preflight** in `../../_shared/teamwork.md` (`git: main`: right before the trunk push; `git: pr` without isolation: on the ticket branch, immediately before the push and PR; `worktree`/`clone`: on the main checkout, then on the ticket branch) and integrate only on `current`/`synced` with the tests green and `checksOk`. **Pending-UAT guard (before the close).** If Verify left acceptance testing **not yet run** (it would otherwise be deferred), **pose the two-way choice to the user and wait for an explicit answer, never open a ticket unilaterally**: (a) run the UAT now, in this session, with no ticket (the stale-base preflight first, then the UAT setup, then the drive-mode ask, `../../_shared/techniques/acceptance-testing.md`), or (b) open a UAT follow-up ticket to run it later. (Under an autonomous run the guard takes option (b) itself for the human-only steps, reusing an open follow-up instead of opening a second one: see `auto.md`.) On (a), run the guided walkthrough now and resolve on a confirmed pass, or on an explicit waiver if the user skips at the ask (recorded as unconfirmed; the resolution comment states it) (a failing step becomes a fix or a `bug` follow-up and an incomplete/blocked step is marked pending and tracked, per the guided-UAT rules in `../../_shared/techniques/acceptance-testing.md`, not a silent close); on (b), **open the follow-up ticket carrying the owed UAT first**, linked back to this parent (a `task` on the frontier or a `trailhead:seed` gated on the run, per *Pending UAT must stay tracked* in `../../_shared/techniques/acceptance-testing.md`). **Never open a UAT ticket without asking, never `gh issue close` on a chosen-deferred UAT with nothing opened, and never let silence or an ambiguous reply default to either path.** When acceptance was already completed at Verify, or was **explicitly waived** by the user at the drive-mode ask (a waiver is not a deferral: nothing is owed, so nothing is opened; the resolution comment states the waiver), this guard doesn't fire. **Run the post-commit self-check before posting, exactly as in the `build` Resolve step** (each cited hash resolves and the files it names appear in that commit's diff; correct or drop any citation that fails). **If the fix materially altered the codebase**, patch the affected facet of the `trailhead:codebase` issue (see **Codebase** in `../../_shared/substrate.md`, looked up per its singleton fail-closed rule; also a body read-modify-write, same **Fail-safe body rewrites** rule) in place (a line or two; the same `trailhead-issue-body.js` helper does it with `--section "<facet>"`). When a structural event is due, refresh the dashboard with `node <templates>/trailhead-dashboard.js`. Then close the session with the **Session handoff** ritual (`../../_shared/session-handoff.md`): one `node <templates>/trailhead-work-state.js clear --repo <working-root>` removes the work state and the session-ticket marker, then `/clear` + next command.
72
+ 4. **Verify**: a `VERIFY` comment: **start with the Stale-base preflight** (`trailhead-base-preflight.js --sync`, **Stale-base preflight** in `../../_shared/teamwork.md`): preflight, then sync (the preflight never runs the suite itself), all before anything is reviewed (a rebase rewrites SHAs the review ledger cites, and the Code review and goal check must see the synced tree); then, when `testing.full` is configured, Verify runs the full suite once on the synced tree (the single run for that tree), through `node <templates>/trailhead-test-run.js run --repo <working-root> --config-root <project root>`, and records the helper's JSON as a one-line **test record** in the `VERIFY` test outcome: `test record: <command> · tree <sha> · <green | failed (exit <n>)> · source <run | cache>` (built from `command`, `tree`, `exitCode`, `source`), passed verbatim to the Code review and Goal-backward verification agents, which never re-run the full suite. A failed record is read back from the latest `VERIFY` comment: on the same tree (`git -C <repo> rev-parse HEAD^{tree}`) reuse it instead of invoking the helper (it caches only greens); a fix round that changes the tree gets one new run on the new tree. The helper's `unconfigured` falls back to the project's own command, run once on the synced tree; no test record is built and the reviewing agents get none, so they test as today (Execute and Fix focused runs still use `--kind targeted --files <a,b>`); the repro must now pass with no regressions; then the **Code review** on the fix, scoped to the ticket's own commits per its **Ticket diff** rule (`../../_shared/techniques/code-review.md`: the `Refs: #<n>` set, read per commit, recomputed each round, an empty set surfaced as an error, never a `<base>...HEAD` range), and carries the review's counts line and findings ledger, per code-review.md, **run in the background and collected by polling, never a blind blocking wait** (a hard engine default on every host, see *Reviews never block the session* in `../../_shared/techniques.md`); run the report-status check before branching on status (each report, from the subagent or inline, goes through the *Malformed review report* protocol in `../../_shared/techniques.md`, which also fixes the layout of the round's single `VERIFY` comment); **read the review's leading `status:` tag to branch deterministically (`status: clean` -> Resolve, `status: issues_found` -> iterate the loop or run the checkpoint first)**; then the **Goal-backward verification** technique on `config.models.verify` (does the fix deliver what the ticket promised, backward from its `Question`; the read-only `trailhead-verify` subagent, on the same **Ticket diff** set), background and collected by polling, branching on its leading `status:` tag (`status: met` -> proceed, `status: gaps_found` -> loop back to Fix for the gap or run the checkpoint, `status: inconclusive` as the leading tag (all gaps cleared) -> a per-reason-class HITL checkpoint with no auto-retry, never auto-resolving over the unevaluated goal); **Acceptance testing** if the bug was user-facing, opening with the UAT setup (`trailhead-uat-setup.js`, after the preflight) and then the drive-mode ask (`../../_shared/techniques/acceptance-testing.md`): AI-driven, or **walk the user through a guided UAT conversationally, step by step**, not a checklist to self-serve (or skip (waive acceptance), recorded as waived, if the user so chooses): interactive by design, so it runs inline, not backgrounded. At a HITL checkpoint here, an advisory sub-choice (which fix, how to proceed) **offers** the delegate option, per the injection recipe in `../../_shared/choices.md`; the accept/resolve decision stays the human's.
73
+ 5. **Resolve**: a resolution comment (cause + fix) → `gh issue close` → update `Decisions so far` (default path: `node <templates>/trailhead-issue-body.js append --issue <map> --section "Decisions so far" --line "<pointer>"`, same helper and fallback as the `build` Resolve step; a body read-modify-write that obeys the **Fail-safe body rewrites** rule in `../../_shared/substrate.md`). **Disposition gate (before the close)**: the latest `VERIFY` comment's findings ledger covers every finding the counts line counts and none of them is `open`; the resolution comment links that ledger rather than duplicating it; otherwise do not `gh issue close` (per code-review.md's Disposition gate). **Stale-base preflight (before integrating).** Run the preflight per **Stale-base preflight** in `../../_shared/teamwork.md` (`git: main`: right before the trunk push; `git: pr` without isolation: on the ticket branch, immediately before the push and PR; `worktree`/`clone`: on the main checkout, then on the ticket branch) and integrate only on `current`/`synced` with the tests green and `checksOk`. **Pending-UAT guard (before the close).** If Verify left acceptance testing **not yet run** (it would otherwise be deferred), **pose the two-way choice to the user and wait for an explicit answer, never open a ticket unilaterally**: (a) run the UAT now, in this session, with no ticket (the stale-base preflight first, then the UAT setup, then the drive-mode ask, `../../_shared/techniques/acceptance-testing.md`), or (b) open a UAT follow-up ticket to run it later. (Under an autonomous run the guard takes option (b) itself for the human-only steps, reusing an open follow-up instead of opening a second one: see `auto.md`.) On (a), run the guided walkthrough now and resolve on a confirmed pass, or on an explicit waiver if the user skips at the ask (recorded as unconfirmed; the resolution comment states it) (a failing step becomes a fix or a `bug` follow-up and an incomplete/blocked step is marked pending and tracked, per the guided-UAT rules in `../../_shared/techniques/acceptance-testing.md`, not a silent close); on (b), **open the follow-up ticket carrying the owed UAT first**, linked back to this parent (a `task` on the frontier or a `trailhead:seed` gated on the run, per *Pending UAT must stay tracked* in `../../_shared/techniques/acceptance-testing.md`). **Never open a UAT ticket without asking, never `gh issue close` on a chosen-deferred UAT with nothing opened, and never let silence or an ambiguous reply default to either path.** When acceptance was already completed at Verify, or was **explicitly waived** by the user at the drive-mode ask (a waiver is not a deferral: nothing is owed, so nothing is opened; the resolution comment states the waiver), this guard doesn't fire. **Run the post-commit self-check before posting, exactly as in the `build` Resolve step** (each cited hash resolves and the files it names appear in that commit's diff; correct or drop any citation that fails). **If the fix materially altered the codebase**, patch the affected facet of the `trailhead:codebase` issue (see **Codebase** in `../../_shared/substrate.md`, looked up per its singleton fail-closed rule; also a body read-modify-write, same **Fail-safe body rewrites** rule) in place (a line or two; the same `trailhead-issue-body.js` helper does it with `--section "<facet>"`). The patch passes `--stamp <short sha>` (`git -C <working-root> rev-parse --short <commit>`), always a commit reachable from the trunk: the trunk tip after integration, so a rebase at the preflight cannot orphan it. When a PR is not merged yet at Resolve (`git: pr`), the stamp is the fork point (`git merge-base HEAD origin/<trunk>`), so a squash or rebase merge cannot orphan it either; the PR's own changes then read as stale until the next patch, a false alarm and never a false `current`. When the helper is missing and the patch is made by hand, update the facet's `_as of <short sha>_` stamp line too. When a structural event is due, refresh the dashboard with `node <templates>/trailhead-dashboard.js`. **Unblock dependents (every ticket, map or whiteboard).** Right after the close, whatever the ticket type and whether or not there is a map (independent of the map-only `Decisions so far` update above): run `node <templates>/trailhead-unblock.js --issue <n> --repo <owner/name>` (same `<templates>` lookup), which removes `trailhead:blocked` from every open dependent whose last open blocker just closed and prints one JSON line (`{"status":"ok","unblocked":[...],"stillBlocked":[...]}` or `{"status":"error","reason":...}`). When the helper is missing or returns `error`, do it by hand: for each open dependent carrying `trailhead:blocked` in `gh api repos/{owner}/{repo}/issues/<n>/dependencies/blocking`, read `gh api repos/{owner}/{repo}/issues/<dep>/dependencies/blocked_by` and, if every blocker there is closed, `gh issue edit <dep> --remove-label "trailhead:blocked"`; report a failure in one line. A ticket unblocked here joins the frontier, so the next-step handoff can name it. Then close the session with the **Session handoff** ritual (`../../_shared/session-handoff.md`): one `node <templates>/trailhead-work-state.js clear --repo <working-root>` removes the work state and the session-ticket marker, then `/clear` + next command.
74
74
 
75
- A **blocking** bug that halts other work is worked immediately; an isolated one is a normal frontier ticket. New scope surfacing while fixing → capture or split, never expand this ticket in flight (see **Scope that surfaces while working a ticket**). A follow-up raised but left unopened is carried to the **Pending follow-up guard** at handoff (`../../_shared/session-handoff.md`), re-posed at Resolve rather than dropped.
75
+ A **blocking** bug that halts other work is worked immediately; an isolated one is a normal frontier ticket. New scope surfacing while fixing → capture or split, never expand this ticket in flight (see **Scope that surfaces while working a ticket**, `../../_shared/surfaced-scope.md`). A follow-up raised but left unopened is carried to the **Pending follow-up guard** at handoff (`../../_shared/session-handoff.md`), re-posed at Resolve rather than dropped.
76
76
 
77
77
  ## `task`: HITL or AFK
78
78
  Manual work that must happen before a *decision* can be made: signing up for a service to judge its API, provisioning access, moving data to see its shape. The agent drives it alone where it can (AFK); otherwise it hands the human a precise checklist (HITL). Resolved when the work is done; the answer records what was done and the resulting facts (where credentials live, new URLs, row counts) later tickets depend on.
@@ -64,15 +64,22 @@ DIR="$(j '.workspace.current_dir')"
64
64
  [ -z "$DIR" ] && DIR="$PWD"
65
65
 
66
66
  # --- repo root, nome progetto, tipo di checkout ---
67
+ # Un GIT_DIR (o simili) ereditato batte -C e sposterebbe ogni chiamata git su un
68
+ # altro repo (#349): via le variabili di posizione del repo prima del primo git.
69
+ # Stessa lista di templates/trailhead-git-env.js.
70
+ unset GIT_ALTERNATE_OBJECT_DIRECTORIES GIT_OBJECT_DIRECTORY GIT_DIR GIT_WORK_TREE \
71
+ GIT_IMPLICIT_WORK_TREE GIT_GRAFT_FILE GIT_INDEX_FILE GIT_NO_REPLACE_OBJECTS \
72
+ GIT_REPLACE_REF_BASE GIT_PREFIX GIT_SHALLOW_FILE GIT_COMMON_DIR
73
+
67
74
  # Il nome del progetto e' SEMPRE quello del repo principale, anche da un
68
75
  # worktree o da un clone: `git-common-dir` punta al `.git` del repo principale
69
76
  # (per un worktree linkato) o al proprio (per originale/clone), quindi il suo
70
77
  # genitore e' la root del progetto. Cosi' un worktree non mostra piu' il
71
78
  # basename della sua cartella (es. `wt-main`), ma il progetto vero.
72
79
  ROOT="$(git -C "$DIR" rev-parse --show-toplevel 2>/dev/null || printf '%s' "$DIR")"
73
- GIT_DIR="$(git -C "$DIR" rev-parse --absolute-git-dir 2>/dev/null)"
80
+ GITDIR_ABS="$(git -C "$DIR" rev-parse --absolute-git-dir 2>/dev/null)"
74
81
  COMMON="$(git -C "$DIR" rev-parse --path-format=absolute --git-common-dir 2>/dev/null)"
75
- [ -z "$COMMON" ] && [ -n "$GIT_DIR" ] && COMMON="$GIT_DIR" # git vecchio senza --path-format
82
+ [ -z "$COMMON" ] && [ -n "$GITDIR_ABS" ] && COMMON="$GITDIR_ABS" # git vecchio senza --path-format
76
83
  if [ -n "$COMMON" ]; then
77
84
  PROJ="$(basename "$(dirname "$COMMON")")"
78
85
  else
@@ -84,7 +91,7 @@ fi
84
91
  # per-ticket della convenzione trailhead (`../<repo>-t<n>`, repo a se' stante,
85
92
  # cartella `-t<n>`).
86
93
  CHECKOUT=""
87
- if [ -n "$GIT_DIR" ] && [ -n "$COMMON" ] && [ "$GIT_DIR" != "$COMMON" ]; then
94
+ if [ -n "$GITDIR_ABS" ] && [ -n "$COMMON" ] && [ "$GITDIR_ABS" != "$COMMON" ]; then
88
95
  CHECKOUT="(WT)"
89
96
  elif printf '%s' "$(basename "$ROOT")" | grep -qE -- '-t[0-9]+$'; then
90
97
  CHECKOUT="(C)"
@@ -10,9 +10,13 @@
10
10
  // index --repo <dir>
11
11
  // fastpath --repo <dir> [--index <file>] <n> [<n>...]
12
12
  // attribute --repo <dir> [--index <file>] --ticket <n> --claim <spec> [--claim <spec>...]
13
+ // facets --repo <dir> --body-file <file>
13
14
  // A claim spec is a comma list of `<path>:<s>-<e>` (HEAD line range) or
14
15
  // `<path>:@<g>` (deletion gap after HEAD line g). `index` prints the trailer
15
16
  // index; save it and pass it back with --index so a whole audit scans once.
17
+ // `facets` is read-only: for each `## ` facet of a trailhead:codebase issue body
18
+ // it compares the `_as of <sha>_` stamp with HEAD over the files the facet cites
19
+ // (auditing.md step 7b). #360.
16
20
  //
17
21
  // Git is only ever run through execFileSync('git', ['-C', repo, ...]), never a
18
22
  // shell, with LC_ALL=C so output parsing is stable on localized machines.
@@ -20,6 +24,8 @@
20
24
  const fs = require('fs');
21
25
  const path = require('path');
22
26
  const { execFileSync } = require('child_process');
27
+ const { gitEnv } = require('./trailhead-git-env.js');
28
+ const { facetsOf } = require('./trailhead-issue-body.js');
23
29
 
24
30
  // Tokens `#<digits>` delimited by start/end, comma or whitespace, so `#19`
25
31
  // never matches inside `#192`. Input is the value of a parsed `Refs:` trailer.
@@ -76,7 +82,7 @@ function hunkTouches(hunk, tracked, anchors) {
76
82
  // --- git plumbing ----------------------------------------------------------------
77
83
  // LC_ALL=C: stable (non-localized) output. Literal pathspecs: a ticket file name
78
84
  // is never interpreted as a pathspec pattern.
79
- const GIT_ENV = { ...process.env, LC_ALL: 'C', GIT_LITERAL_PATHSPECS: '1' };
85
+ const GIT_ENV = gitEnv(process.env, { LC_ALL: 'C', GIT_LITERAL_PATHSPECS: '1' });
80
86
 
81
87
  function git(repo, args) {
82
88
  return execFileSync('git', ['-C', repo, '-c', 'core.quotePath=false', ...args], {
@@ -615,15 +621,58 @@ function attribute(repo, n, claims, index) {
615
621
  }
616
622
  }
617
623
 
624
+ // --- facet freshness (auditing.md step 7b) ----------------------------------------------
625
+ // Backticked tokens of a facet's text that can be repo paths: a trailing `:<line>`
626
+ // or `:<line>-<line>` and a trailing `/` are stripped; absolute paths, `..`,
627
+ // whitespace and a leading `-` or `:` are dropped, so nothing odd reaches git's argv.
628
+ function citedTokens(text) {
629
+ const found = [];
630
+ for (const m of String(text).matchAll(/`([^`\n]+)`/g)) {
631
+ const token = m[1].trim().replace(/:\d+(?:-\d+)?$/, '').replace(/\/+$/, '');
632
+ if (!token || token.startsWith('/') || /^[-:]/.test(token) || token.includes('..') || /\s/.test(token)) continue;
633
+ if (!found.includes(token)) found.push(token);
634
+ }
635
+ return found;
636
+ }
637
+
638
+ // Freshness of one facet against HEAD. Status: current | stale | unstamped |
639
+ // unresolved (stamp unknown, or not an ancestor of HEAD) | no-paths (no cited
640
+ // file exists at the stamp, so there is nothing to compare).
641
+ function facetStatus(ctx, facet, head) {
642
+ const out = { name: facet.name, sha: facet.sha, status: 'unstamped', paths: [], changed: [] };
643
+ if (!facet.sha) return out;
644
+ let full;
645
+ try {
646
+ full = git(ctx.repo, ['rev-parse', '--verify', '-q', `${facet.sha}^{commit}`]).trim();
647
+ } catch (err) {
648
+ return { ...out, status: 'unresolved' };
649
+ }
650
+ if (!SHA_RE.test(full) || !isAncestor(ctx, full, head)) return { ...out, status: 'unresolved' };
651
+ const paths = citedTokens(facet.text).filter((p) => objectExists(ctx, `${full}:${p}`));
652
+ if (!paths.length) return { ...out, status: 'no-paths' };
653
+ // Plain paths: GIT_LITERAL_PATHSPECS already makes them literal. --no-renames
654
+ // lists a renamed or deleted cited file under its old name.
655
+ const changed = git(ctx.repo, ['diff', '--name-only', '-z', '--no-renames', full, head, '--', ...paths])
656
+ .split('\0').filter(Boolean).sort();
657
+ return { ...out, status: changed.length ? 'stale' : 'current', paths, changed };
658
+ }
659
+
660
+ function facetsReport(repo, bodyFile) {
661
+ const facets = facetsOf(fs.readFileSync(bodyFile, 'utf8'));
662
+ const head = git(repo, ['rev-parse', 'HEAD']).trim();
663
+ const ctx = makeCtx(repo);
664
+ return { head, facets: facets.map((f) => facetStatus(ctx, f, head)) };
665
+ }
666
+
618
667
  // --- CLI ----------------------------------------------------------------------------------
619
668
  class UsageError extends Error {}
620
669
 
621
670
  function parseArgs(argv) {
622
671
  const [command, ...rest] = argv;
623
- if (!['index', 'fastpath', 'attribute'].includes(command)) {
624
- throw new UsageError('usage: trailhead-audit-walk.js <index|fastpath|attribute> --repo <dir> ...');
672
+ if (!['index', 'fastpath', 'attribute', 'facets'].includes(command)) {
673
+ throw new UsageError('usage: trailhead-audit-walk.js <index|fastpath|attribute|facets> --repo <dir> ...');
625
674
  }
626
- const opts = { command, repo: null, index: null, ticket: null, claims: [], tickets: [] };
675
+ const opts = { command, repo: null, bodyFile: null, index: null, ticket: null, claims: [], tickets: [] };
627
676
  const value = (flag, i) => {
628
677
  if (i >= rest.length) throw new UsageError(`usage: ${flag} requires a value`);
629
678
  return rest[i];
@@ -636,12 +685,14 @@ function parseArgs(argv) {
636
685
  const arg = rest[i];
637
686
  if (arg === '--repo') opts.repo = value(arg, ++i);
638
687
  else if (arg === '--index') opts.index = value(arg, ++i);
688
+ else if (arg === '--body-file' && command === 'facets') opts.bodyFile = value(arg, ++i);
639
689
  else if (arg === '--ticket' && command === 'attribute') opts.ticket = number(value(arg, ++i), '--ticket');
640
690
  else if (arg === '--claim' && command === 'attribute') opts.claims.push(value(arg, ++i));
641
691
  else if (command === 'fastpath' && !arg.startsWith('--')) opts.tickets.push(number(arg, 'ticket'));
642
692
  else throw new UsageError(`usage: unknown argument: ${arg}`);
643
693
  }
644
694
  if (command === 'fastpath' && !opts.tickets.length) throw new UsageError('usage: fastpath needs at least one ticket number');
695
+ if (command === 'facets' && !opts.bodyFile) throw new UsageError('usage: facets needs --body-file <file>');
645
696
  if (command === 'attribute' && opts.ticket === null) throw new UsageError('usage: attribute needs --ticket <n>');
646
697
  if (command === 'attribute' && !opts.claims.length) throw new UsageError('usage: attribute needs at least one --claim <spec>');
647
698
  return opts;
@@ -657,6 +708,7 @@ function readIndexFile(file) {
657
708
 
658
709
  function run(opts, repo) {
659
710
  if (opts.command === 'index') return buildTrailerIndex(repo);
711
+ if (opts.command === 'facets') return facetsReport(repo, path.resolve(opts.bodyFile));
660
712
  const index = opts.index ? readIndexFile(opts.index) : buildTrailerIndex(repo);
661
713
  if (opts.command === 'attribute') return attribute(repo, opts.ticket, opts.claims, index);
662
714
  return { shallow: index.shallow === true, tickets: opts.tickets.map((n) => fastPath(repo, n, index)) };
@@ -56,11 +56,12 @@
56
56
  // status is a result (parked statuses included), 1 on status error, 2 on a usage error.
57
57
 
58
58
  const { spawnSync } = require('child_process');
59
+ const { gitEnv } = require('./trailhead-git-env.js');
59
60
  const nodeFs = require('fs');
60
61
  const path = require('path');
61
62
 
62
63
  function defaultGit(args) {
63
- const env = { ...process.env, GIT_EDITOR: 'true', GIT_TERMINAL_PROMPT: '0' };
64
+ const env = gitEnv(process.env, { GIT_EDITOR: 'true', GIT_TERMINAL_PROMPT: '0' });
64
65
  const r = spawnSync('git', args, { encoding: 'utf8', env, maxBuffer: 64 * 1024 * 1024 });
65
66
  return { status: r.status === null ? 1 : r.status, stdout: r.stdout || '', stderr: r.stderr || (r.error ? String(r.error.message) : '') };
66
67
  }
@@ -27,6 +27,7 @@
27
27
  // Exit 0 on ok, 1 on an error, 2 on a usage error (stderr).
28
28
 
29
29
  const { spawnSync } = require('child_process');
30
+ const { gitEnv } = require('./trailhead-git-env.js');
30
31
 
31
32
  const LIMIT = 1000;
32
33
  const FRONTIER_SEARCH = 'no:assignee -label:trailhead:blocked -label:trailhead:unverified -label:trailhead:paused';
@@ -39,7 +40,7 @@ const MAP_LABEL_RE = /^trailhead:map-(\d+)$/;
39
40
  const TYPES = ['decision', 'research', 'prototype', 'build', 'bug', 'task'];
40
41
 
41
42
  function defaultGh(args) {
42
- const r = spawnSync('gh', args, { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
43
+ const r = spawnSync('gh', args, { encoding: 'utf8', env: gitEnv(), maxBuffer: 64 * 1024 * 1024 });
43
44
  return { status: r.status === null ? 1 : r.status, stdout: r.stdout || '', stderr: r.stderr || (r.error ? String(r.error.message) : '') };
44
45
  }
45
46
 
@@ -43,11 +43,12 @@
43
43
  // Exit 0 on ok, 1 on an error, 2 on a usage error (stderr).
44
44
 
45
45
  const { spawnSync } = require('child_process');
46
+ const { gitEnv } = require('./trailhead-git-env.js');
46
47
  const nodeFs = require('fs');
47
48
  const path = require('path');
48
49
 
49
50
  function defaultGit(args) {
50
- const r = spawnSync('git', args, { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
51
+ const r = spawnSync('git', args, { encoding: 'utf8', env: gitEnv(), maxBuffer: 64 * 1024 * 1024 });
51
52
  return { status: r.status === null ? 1 : r.status, stdout: r.stdout || '', stderr: r.stderr || (r.error ? String(r.error.message) : '') };
52
53
  }
53
54
 
@@ -40,6 +40,7 @@ const fs = require('fs');
40
40
  const os = require('os');
41
41
  const path = require('path');
42
42
  const { spawnSync } = require('child_process');
43
+ const { gitEnv } = require('./trailhead-git-env.js');
43
44
 
44
45
  const CONFIG_REL = path.join('.trailhead', 'config.json');
45
46
  const CHECK_TIMEOUT_MS = 300000;
@@ -99,8 +100,12 @@ function readChecks(file, fs_) {
99
100
  return { state: 'set', list: value, warnings };
100
101
  }
101
102
 
103
+ // Su Codex lo script vive in <codexHome>/skills/trailhead/<dir>: la config globale sta lì, non in ~/.claude.
104
+ const CODEX_INSTALL = /[\\/]skills[\\/]trailhead[\\/](?:hooks|templates)$/;
105
+ const codexHome = () => (CODEX_INSTALL.test(__dirname) ? path.resolve(__dirname, '..', '..', '..') : null);
106
+
102
107
  function globalConfigPath(env) {
103
- const base = env.CLAUDE_CONFIG_DIR || path.join(env.HOME || os.homedir(), '.claude');
108
+ const base = codexHome() || env.CLAUDE_CONFIG_DIR || path.join(env.HOME || os.homedir(), '.claude');
104
109
  return path.join(base, 'trailhead', 'config.json');
105
110
  }
106
111
 
@@ -111,9 +116,9 @@ function tail(text) {
111
116
 
112
117
  // Runs git at `repo`; never throws on a non-zero exit.
113
118
  function makeGit(repo, env) {
114
- const gitEnv = { ...process.env, ...env, GIT_EDITOR: 'true', GIT_TERMINAL_PROMPT: '0' };
119
+ const spawnEnv = gitEnv({ ...process.env, ...env }, { GIT_EDITOR: 'true', GIT_TERMINAL_PROMPT: '0' });
115
120
  const G = (args) => {
116
- const r = spawnSync('git', ['-C', repo, ...args], { encoding: 'utf8', env: gitEnv, maxBuffer: 64 * 1024 * 1024 });
121
+ const r = spawnSync('git', ['-C', repo, ...args], { encoding: 'utf8', env: spawnEnv, maxBuffer: 64 * 1024 * 1024 });
117
122
  if (r.error) throw r.error;
118
123
  const res = { ok: r.status === 0, out: (r.stdout || '').trim(), err: (r.stderr || '').trim() };
119
124
  G.last = res;
@@ -195,7 +200,7 @@ function sync(G, { trunk, head, base, ahead, behind, warnings }) {
195
200
 
196
201
  function runChecks(list, repo, env) {
197
202
  const results = list.map((command) => {
198
- const r = spawnSync('sh', ['-c', command], { cwd: repo, encoding: 'utf8', env: { ...process.env, ...env }, timeout: CHECK_TIMEOUT_MS, maxBuffer: 16 * 1024 * 1024 });
203
+ const r = spawnSync('sh', ['-c', command], { cwd: repo, encoding: 'utf8', env: gitEnv({ ...process.env, ...env }), timeout: CHECK_TIMEOUT_MS, maxBuffer: 16 * 1024 * 1024 });
199
204
  const timedOut = r.error && r.error.code === 'ETIMEDOUT';
200
205
  const output = (r.stdout || '') + (r.stderr || '') + (timedOut ? `\n(timed out after ${CHECK_TIMEOUT_MS / 1000}s)` : r.error && !timedOut ? `\n${r.error.message}` : '');
201
206
  return { command, ok: r.status === 0, code: r.status, output: tail(output) };
@@ -297,7 +302,7 @@ function main(argv, { cwd, stdout, env, fs: fsMod } = {}) {
297
302
  return 0;
298
303
  }
299
304
 
300
- module.exports = { parseArgs, readChecks, main };
305
+ module.exports = { globalConfigPath, parseArgs, readChecks, main };
301
306
 
302
307
  if (require.main === module) {
303
308
  const code = main(process.argv.slice(2), { cwd: process.cwd(), stdout: process.stdout, env: process.env });