@marcomigozzi/trailhead 0.14.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/README.it.md +158 -34
  2. package/README.md +147 -33
  3. package/bin/auto-intro-docs.test.js +72 -0
  4. package/bin/auto-merge-docs.test.js +33 -0
  5. package/bin/auto-merge-ticket-docs.test.js +32 -0
  6. package/bin/auto-review-rounds-docs.test.js +45 -0
  7. package/bin/auto-run-docs.test.js +60 -0
  8. package/bin/auto-scope-docs.test.js +33 -0
  9. package/bin/auto-slots-docs.test.js +45 -0
  10. package/bin/auto-uat-docs.test.js +47 -0
  11. package/bin/base-preflight-docs.test.js +65 -0
  12. package/bin/execute-split-docs.test.js +146 -0
  13. package/bin/grilling-holistic-review-docs.test.js +90 -0
  14. package/bin/isolation-answer-docs.test.js +41 -0
  15. package/bin/isolation-copy-docs.test.js +28 -0
  16. package/bin/lib/codex-projection.js +6 -6
  17. package/bin/lib/codex-projection.test.js +43 -2
  18. package/bin/needs-you-docs.test.js +41 -0
  19. package/bin/pick-stress-test-docs.test.js +105 -0
  20. package/bin/report-status-docs.test.js +195 -0
  21. package/bin/resolve-helpers-docs.test.js +36 -0
  22. package/bin/scope-confirm-skip-docs.test.js +70 -0
  23. package/bin/trailhead.js +6 -6
  24. package/bin/trailhead.test.js +355 -16
  25. package/bin/uat-setup-docs.test.js +65 -0
  26. package/package.json +1 -1
  27. package/plugins/trailhead/.claude-plugin/plugin.json +1 -1
  28. package/plugins/trailhead/agents/trailhead-code-review.md +15 -2
  29. package/plugins/trailhead/agents/trailhead-codebase-map.md +20 -1
  30. package/plugins/trailhead/agents/trailhead-debug.md +20 -1
  31. package/plugins/trailhead/agents/trailhead-executor.md +29 -2
  32. package/plugins/trailhead/agents/trailhead-fix.md +29 -2
  33. package/plugins/trailhead/agents/trailhead-plan.md +20 -1
  34. package/plugins/trailhead/agents/trailhead-research.md +22 -3
  35. package/plugins/trailhead/agents/trailhead-verify.md +15 -2
  36. package/plugins/trailhead/commands/auto.md +2 -2
  37. package/plugins/trailhead/hooks/lib/auto-merge.test.js +467 -0
  38. package/plugins/trailhead/hooks/lib/auto-queue.test.js +245 -0
  39. package/plugins/trailhead/hooks/lib/auto-run.test.js +318 -0
  40. package/plugins/trailhead/hooks/lib/auto-slot.test.js +278 -0
  41. package/plugins/trailhead/hooks/lib/base-preflight.test.js +513 -0
  42. package/plugins/trailhead/hooks/lib/checkout-in.test.js +173 -0
  43. package/plugins/trailhead/hooks/lib/commit-message-check.js +1 -1
  44. package/plugins/trailhead/hooks/lib/commit-message-check.test.js +3 -2
  45. package/plugins/trailhead/hooks/lib/commit-msg-module-repo.test.js +116 -0
  46. package/plugins/trailhead/hooks/lib/commit-msg-sync.test.js +100 -9
  47. package/plugins/trailhead/hooks/lib/dashboard.test.js +472 -0
  48. package/plugins/trailhead/hooks/lib/fixtures/commit-msg-legacy-js +104 -0
  49. package/plugins/trailhead/hooks/lib/isolation-answer.test.js +258 -0
  50. package/plugins/trailhead/hooks/lib/isolation-copy.test.js +298 -0
  51. package/plugins/trailhead/hooks/lib/issue-body.test.js +470 -0
  52. package/plugins/trailhead/hooks/lib/merge-ticket.test.js +394 -0
  53. package/plugins/trailhead/hooks/lib/needs-you.test.js +177 -0
  54. package/plugins/trailhead/hooks/lib/plan-review-rounds.test.js +319 -0
  55. package/plugins/trailhead/hooks/lib/report-status.test.js +366 -0
  56. package/plugins/trailhead/hooks/lib/shell-scan.js +36 -1
  57. package/plugins/trailhead/hooks/lib/shell-scan.test.js +14 -0
  58. package/plugins/trailhead/hooks/lib/statusline-context.test.js +140 -0
  59. package/plugins/trailhead/hooks/lib/uat-backfill.test.js +150 -0
  60. package/plugins/trailhead/hooks/lib/uat-followup.test.js +169 -0
  61. package/plugins/trailhead/hooks/lib/uat-setup.test.js +340 -0
  62. package/plugins/trailhead/hooks/lib/unblock.test.js +207 -0
  63. package/plugins/trailhead/hooks/lib/work-state.test.js +45 -0
  64. package/plugins/trailhead/hooks/mod/README.md +209 -36
  65. package/plugins/trailhead/hooks/mod/auto-run-view.ts +93 -0
  66. package/plugins/trailhead/hooks/mod/auto-run.ts +165 -0
  67. package/plugins/trailhead/hooks/mod/map-pane-view.ts +304 -26
  68. package/plugins/trailhead/hooks/mod/map-pane.tsx +763 -160
  69. package/plugins/trailhead/hooks/mod/register.tsx +312 -40
  70. package/plugins/trailhead/hooks/mod/repo-pin.ts +167 -0
  71. package/plugins/trailhead/hooks/mod/session-registry.ts +65 -0
  72. package/plugins/trailhead/hooks/mod/turn-state.ts +14 -0
  73. package/plugins/trailhead/hooks/mod/whiteboard-follow.ts +98 -0
  74. package/plugins/trailhead/hooks/mod/work-state-locate.ts +191 -0
  75. package/plugins/trailhead/hooks/mod/work-state-prompt.ts +13 -15
  76. package/plugins/trailhead/hooks/mod/work-state-view.ts +38 -5
  77. package/plugins/trailhead/hooks/mod/worktree-porcelain.ts +17 -0
  78. package/plugins/trailhead/hooks/trailhead-bash-guards.js +8 -8
  79. package/plugins/trailhead/hooks/trailhead-bash-guards.test.js +22 -16
  80. package/plugins/trailhead/hooks/trailhead-install-guard.js +169 -34
  81. package/plugins/trailhead/hooks/trailhead-install-guard.test.js +180 -1
  82. package/plugins/trailhead/hooks/trailhead-labels-guard.js +231 -0
  83. package/plugins/trailhead/hooks/trailhead-labels-guard.test.js +212 -0
  84. package/plugins/trailhead/hooks/trailhead-search-guard.js +43 -7
  85. package/plugins/trailhead/hooks/trailhead-search-guard.test.js +66 -2
  86. package/plugins/trailhead/hooks/trailhead-secret-read-guard.js +296 -22
  87. package/plugins/trailhead/hooks/trailhead-secret-read-guard.test.js +253 -0
  88. package/plugins/trailhead/skills/_shared/choices.md +11 -4
  89. package/plugins/trailhead/skills/_shared/configuration-reference.md +17 -5
  90. package/plugins/trailhead/skills/_shared/load-first.md +3 -3
  91. package/plugins/trailhead/skills/_shared/multi-map.md +2 -2
  92. package/plugins/trailhead/skills/_shared/session-handoff.md +1 -1
  93. package/plugins/trailhead/skills/_shared/substrate-commands.md +47 -14
  94. package/plugins/trailhead/skills/_shared/substrate.md +9 -4
  95. package/plugins/trailhead/skills/_shared/teamwork.md +17 -8
  96. package/plugins/trailhead/skills/_shared/techniques/acceptance-testing.md +18 -10
  97. package/plugins/trailhead/skills/_shared/techniques/code-review.md +1 -1
  98. package/plugins/trailhead/skills/_shared/techniques/codebase-map.md +1 -1
  99. package/plugins/trailhead/skills/_shared/techniques/debug.md +2 -0
  100. package/plugins/trailhead/skills/_shared/techniques/grilling.md +4 -2
  101. package/plugins/trailhead/skills/_shared/techniques/plan-review.md +3 -1
  102. package/plugins/trailhead/skills/_shared/techniques/research.md +4 -2
  103. package/plugins/trailhead/skills/_shared/techniques/verify.md +1 -1
  104. package/plugins/trailhead/skills/_shared/techniques.md +12 -2
  105. package/plugins/trailhead/skills/trailhead/SKILL.md +1 -1
  106. package/plugins/trailhead/skills/trailhead-capture/SKILL.md +1 -1
  107. package/plugins/trailhead/skills/trailhead-capture/references/capture.md +3 -3
  108. package/plugins/trailhead/skills/trailhead-chart/SKILL.md +2 -2
  109. package/plugins/trailhead/skills/trailhead-chart/references/charting.md +5 -5
  110. package/plugins/trailhead/skills/trailhead-chart/references/inbox.md +2 -2
  111. package/plugins/trailhead/skills/trailhead-manage/SKILL.md +1 -1
  112. package/plugins/trailhead/skills/trailhead-manage/references/auditing.md +1 -1
  113. package/plugins/trailhead/skills/trailhead-view/SKILL.md +9 -6
  114. package/plugins/trailhead/skills/trailhead-work/SKILL.md +31 -18
  115. package/plugins/trailhead/skills/trailhead-work/references/auto.md +70 -22
  116. package/plugins/trailhead/skills/trailhead-work/references/executor-brief.md +29 -0
  117. package/plugins/trailhead/skills/trailhead-work/references/ticket-engines.md +29 -13
  118. package/plugins/trailhead/templates/statusline-trailhead.sh +77 -12
  119. package/plugins/trailhead/templates/trailhead-auto-merge.js +318 -0
  120. package/plugins/trailhead/templates/trailhead-auto-queue.js +186 -0
  121. package/plugins/trailhead/templates/trailhead-auto-run.js +294 -0
  122. package/plugins/trailhead/templates/trailhead-auto-slot.js +239 -0
  123. package/plugins/trailhead/templates/trailhead-base-preflight.js +305 -0
  124. package/plugins/trailhead/templates/trailhead-checkout-in.js +88 -0
  125. package/plugins/trailhead/templates/trailhead-commit-msg +38 -102
  126. package/plugins/trailhead/templates/trailhead-commit-msg-sync.js +58 -27
  127. package/plugins/trailhead/templates/trailhead-commit-msg.cjs +114 -0
  128. package/plugins/trailhead/templates/trailhead-dashboard.js +360 -0
  129. package/plugins/trailhead/templates/trailhead-isolation-answer.js +227 -0
  130. package/plugins/trailhead/templates/trailhead-isolation-copy.js +239 -0
  131. package/plugins/trailhead/templates/trailhead-issue-body.js +459 -0
  132. package/plugins/trailhead/templates/trailhead-merge-ticket.js +366 -0
  133. package/plugins/trailhead/templates/trailhead-needs-you.js +193 -0
  134. package/plugins/trailhead/templates/trailhead-plan-review-rounds.js +289 -0
  135. package/plugins/trailhead/templates/trailhead-report-status.js +274 -0
  136. package/plugins/trailhead/templates/trailhead-uat-backfill.js +114 -0
  137. package/plugins/trailhead/templates/trailhead-uat-followup.js +128 -0
  138. package/plugins/trailhead/templates/trailhead-uat-setup.js +385 -0
  139. package/plugins/trailhead/templates/trailhead-unblock.js +111 -0
  140. package/plugins/trailhead/templates/trailhead-work-state.js +25 -7
  141. package/plugins/trailhead/tests/auto-run-ui.test.ts +444 -0
  142. package/plugins/trailhead/tests/auto-run-view.test.ts +111 -0
  143. package/plugins/trailhead/tests/map-pane-ui.test.ts +2519 -382
  144. package/plugins/trailhead/tests/map-pane-view.test.ts +600 -18
  145. package/plugins/trailhead/tests/map-pane-write.test.ts +51 -0
  146. package/plugins/trailhead/tests/repo-pin-late-repo.test.ts +77 -0
  147. package/plugins/trailhead/tests/repo-pin.test.ts +123 -0
  148. package/plugins/trailhead/tests/session-registry.test.ts +50 -0
  149. package/plugins/trailhead/tests/whiteboard-follow.test.ts +90 -0
  150. package/plugins/trailhead/tests/work-state-locate.test.ts +33 -0
  151. package/plugins/trailhead/tests/work-state-prompt-hook.test.ts +276 -2
  152. package/plugins/trailhead/tests/work-state-prompt.test.ts +29 -35
  153. package/plugins/trailhead/tests/work-state-ui.test.ts +73 -1
  154. package/plugins/trailhead/tests/work-state-view.test.ts +44 -1
  155. package/plugins/trailhead/tests/work-state-worktree.test.ts +310 -0
  156. package/plugins/trailhead/types/index.d.ts +62 -3
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  🇬🇧 **English** · 🇮🇹 [Italiano](README.it.md)
8
8
 
9
- [![website](https://img.shields.io/badge/website-trailhead.marcomigozzi.it-3C7A5A?style=flat-square)](https://trailhead.marcomigozzi.it) [![site CI](https://img.shields.io/github/actions/workflow/status/ToRvaLDz/trailhead/site-ci.yml?branch=main&style=flat-square&label=site%20CI)](https://github.com/ToRvaLDz/trailhead/actions/workflows/site-ci.yml) [![npm](https://img.shields.io/npm/v/@marcomigozzi/trailhead?style=flat-square&label=npm&color=CB3837)](https://www.npmjs.com/package/@marcomigozzi/trailhead) [![license](https://img.shields.io/github/license/ToRvaLDz/trailhead?style=flat-square&color=3C7A5A)](LICENSE) ![hosts](https://img.shields.io/badge/hosts-Claude%20Code%20%C2%B7%20Codex-1E2A54?style=flat-square) ![commands](https://img.shields.io/badge/commands-24%20verbs-0e8a16?style=flat-square) ![guardrails](https://img.shields.io/badge/guardrails-7%20hooks-e8710a?style=flat-square)
9
+ [![website](https://img.shields.io/badge/website-trailhead.marcomigozzi.it-3C7A5A?style=flat-square)](https://trailhead.marcomigozzi.it) [![site CI](https://img.shields.io/github/actions/workflow/status/ToRvaLDz/trailhead/site-ci.yml?branch=main&style=flat-square&label=site%20CI)](https://github.com/ToRvaLDz/trailhead/actions/workflows/site-ci.yml) [![npm](https://img.shields.io/npm/v/@marcomigozzi/trailhead?style=flat-square&label=npm&color=CB3837)](https://www.npmjs.com/package/@marcomigozzi/trailhead) [![license](https://img.shields.io/github/license/ToRvaLDz/trailhead?style=flat-square&color=3C7A5A)](LICENSE) ![hosts](https://img.shields.io/badge/hosts-Claude%20Code%20%C2%B7%20Codex-1E2A54?style=flat-square) ![commands](https://img.shields.io/badge/commands-24%20verbs-0e8a16?style=flat-square) ![guardrails](https://img.shields.io/badge/guardrails-9%20hooks-e8710a?style=flat-square)
10
10
 
11
11
  </div>
12
12
 
@@ -26,17 +26,18 @@ Two approaches to agent-driven project work each nail one half of the problem:
26
26
 
27
27
  Working across both, one pattern kept recurring: **use a Wayfinder-style map and onboarding to decide the shape of the work, then a GSD-style engine to build each piece, without ever leaving the issue tracker.** `trailhead` is that pattern made into a set of cohesion-clustered skills over a shared core, its own self-contained take on both ideas rather than a wrapper around either.
28
28
 
29
- > **trailhead is self-contained** in the sense that it has **zero external-plugin dependencies**: trailhead-owned agents and the host's built-ins are fine, and no other skill or plugin is required. Wayfinder and GSD are *inspiration*, not dependencies. Every technique those systems package as a separate skill (grilling, TDD, systematic debugging, codebase mapping, code review) is **built in here as an inline protocol**. trailhead's core invokes no other skill; each engine technique dispatches to **trailhead's own `trailhead-*` agent** (`trailhead-executor`, `trailhead-plan`, `trailhead-research`, `trailhead-code-review`, `trailhead-debug`, plus `trailhead-fix` and `trailhead-codebase-map`), authored once as a committed `agents/trailhead-*.md` file and **projected to each host** (Codex gets `~/.codex/agents/trailhead-*.toml` generated from that same source). These agents are **thin** (identity + tool policy + a config-driven model, with the technique protocol injected at spawn) and run on the host's native subagent toolkit (Claude Code's built-in `Agent` tool, or Codex's `multi_agent` toolkit). The only thing to install is trailhead itself. There is one opt-in exception: hosted mockup mode. When enabled, `artifacts` builds the mockup as a Design canvas **Artifact** on claude.ai through Claude Code's built-in `Artifact` tool (Claude Code only, nothing to install), with your design system installed on the canvas. A second hosted mode, `stitch`, uses the Google Stitch MCP and works on **both Claude Code and Codex**, so it's the cross-host option; if its MCP isn't connected, it offers a one-time install and falls back to local-disk mockups if you decline. Where the `Artifact` tool isn't available (Codex, API-key sessions), it offers to switch to `stitch` before falling to local disk.
29
+ > **trailhead is self-contained** in the sense that it has **zero external-plugin dependencies**: trailhead-owned agents and the host's built-ins are fine, and no other skill or plugin is required. Wayfinder and GSD are *inspiration*, not dependencies. Every technique those systems package as a separate skill (grilling, TDD, systematic debugging, codebase mapping, code review) is **built in here as an inline protocol**. trailhead's core invokes no other skill; each engine technique dispatches to **trailhead's own `trailhead-*` agent** (`trailhead-executor`, `trailhead-plan`, `trailhead-research`, `trailhead-code-review`, `trailhead-debug`, plus `trailhead-fix` and `trailhead-codebase-map`), authored once as a committed `agents/trailhead-*.md` file and **projected to each host** (Codex gets `~/.codex/agents/trailhead-*.toml` generated from that same source). These agents are **thin** (identity + tool policy + a config-driven model, with the technique protocol injected at spawn) and run on the host's native subagent toolkit (Claude Code's built-in `Agent` tool, or Codex's `multi_agent` toolkit). The only thing to install is trailhead itself. There is one opt-in exception: hosted mockups. `design: artifacts` uses Claude Code's built-in `Artifact` tool (nothing to install), and `design: stitch` uses the Google Stitch MCP (offered as a one-time install). See [Mockups before code](#-mockups-before-code).
30
30
 
31
31
  The design choices that fall out of it:
32
32
 
33
33
  - **Everything lives on GitHub Issues.** The map is the parent issue; each ticket is a child issue; discussion, plan, and verification are *comments*. There is no `.planning/` directory: the repo holds code only, and the plan is always visible to anyone with the repo.
34
34
  - **Execution happens inside the map.** Unlike pure Wayfinder ("plan, don't do"), build tickets graduate from the fog and are executed as children of the map. The destination is the **working artifact** (a deployed app), not a spec document, though a project can override that and stop at the spec.
35
+ - **Hand it a map and come back.** `/trailhead:auto` drains a map or the whiteboard unattended, in up to four parallel slots, and leaves a run report, the tickets that need you, and local commits for you to push. See [Autonomous run](#-autonomous-run-auto).
35
36
  - **Lean by default.** The heavy machinery (codebase mapping, systematic debugging) runs once where it earns its keep; the per-ticket cycle stays light.
36
37
 
37
38
  - **Built for teams, because the plan is shared.** The tracker is the single source of truth, so the map is a **shared workspace**, not one person's local file. Many people (and their agents) work it at once, and the mechanics keep them from colliding:
38
39
  - **Claiming.** You take a ticket by assigning it to yourself; the assignee *is* the lock. A collision stops you and asks, rather than clobbering someone else's work. Whoever claims a ticket owns it end to end and closes it, no separate approver.
39
- - **A shared frontier, one ticket per session.** The **frontier** (open, unassigned, unblocked tickets) is the common queue of what's takeable now; each person picks a different one. Resolving a ticket ends the session, so work stays in reviewable, single-ticket chunks.
40
+ - **A shared frontier, one ticket per session.** The **frontier** (open, unassigned, unblocked, not paused tickets) is the common queue of what's takeable now; each person picks a different one. Resolving a ticket ends the session, so work stays in reviewable, single-ticket chunks.
40
41
  - **Isolation for concurrent sessions on one machine.** Two agents editing one checkout corrupt each other, so the `isolation:` convention (default **`auto`**, which asks at each ticket's work-start whether you'll work more than one at once and then picks the right mode by repo shape) gives each in-flight ticket its own **`git worktree`** (or a full **`clone`** for path-bound apps, or repos with submodules, a worktree can't safely isolate) when you run more than one at once; disjoint `Scope:` lines let non-overlapping work run in parallel and serialise only where it truly overlaps.
41
42
  - **Pause, resume, split.** Any ticket can be **paused** with a checkpoint comment and **resumed** later by anyone (once released), or **split** into children when it outgrows a session, with blockers re-pointed so the frontier stays honest.
42
43
  - **Several maps at once.** A repo can carry **more than one live map** (parallel milestones or features), each with its own scoped frontier, so two people can drive two efforts on the same repo without stepping on each other, and you can park one map to work another.
@@ -68,14 +69,14 @@ npx @marcomigozzi/trailhead --symlink # dev install (Claude; symlink to the c
68
69
  npx @marcomigozzi/trailhead --uninstall # remove everything it added (pair with --claude / --codex)
69
70
  npx @marcomigozzi/trailhead --dir=<path> # target a specific config dir
70
71
  ```
71
- On **Claude Code** it copies the skill (+ its `references/`), the `/trailhead:*` commands, the hooks (into `hooks/`, registered in `settings.json`) including a SessionStart **update check**, and the label-guard + statusline templates, idempotently.
72
+ On **Claude Code** it copies the skill (+ its `references/`), the `/trailhead:*` commands, the hooks (into `hooks/`, registered in `settings.json`) including a SessionStart **update check**, and the templates (label-guard workflow, statusline, the git `commit-msg` hook and the helper scripts the engine runs), idempotently.
72
73
 
73
- On **Codex CLI** (`--codex`) it projects a native Codex skill invoked as **`$trailhead`** (`$trailhead work`, `$trailhead new "idea"`, …), one thin **`$trailhead-<verb>`** skill per verb for discoverability, the six guardrails as native Codex **hooks** (`~/.codex/hooks.json`, enabling `features.hooks`; the four Bash guards share one process behind a single registration; the search guard is Claude-Code-only and isn't projected to Codex), and per-technique subagent model pins from **`models.codex.*`** into `~/.codex/agents/`. Codex **0.145.0+** is required (the installer gates on `codex --version`). The `artifacts` mockup mode (Claude Code's `Artifact` tool) isn't available on Codex; the cross-host `design: stitch` mode (Google Stitch MCP) is the hosted option there, with local disk as the fallback.
74
+ On **Codex CLI** (`--codex`) it projects a native Codex skill invoked as **`$trailhead`** (`$trailhead work`, `$trailhead new "idea"`, …), one thin **`$trailhead-<verb>`** skill per verb for discoverability, the eight guardrails as native Codex **hooks** (`~/.codex/hooks.json`, enabling `features.hooks`; the five Bash guards share one process behind a single registration; the search guard is Claude-Code-only and isn't projected to Codex), and per-technique subagent model pins from **`models.codex.*`** into `~/.codex/agents/`. Codex **0.145.0+** is required (the installer gates on `codex --version`). The `artifacts` mockup mode (Claude Code's `Artifact` tool) isn't available on Codex; the cross-host `design: stitch` mode (Google Stitch MCP) is the hosted option there, with local disk as the fallback.
74
75
 
75
76
  Re-run the install to update, or run **`/trailhead:update`** (Claude) / **`$trailhead update`** (Codex) from inside the agent: it detects how trailhead was installed and installs the newer version where that is safe (a `git pull` for a dev-symlink, an `npx` re-run for npm, or `/plugin update` for the plugin). When a newer version exists, the statusline shows a `⬆ trailhead <version>` flag (Claude).
76
77
 
77
78
  ### After installing
78
- Restart or reload your agent so the commands register, then run **`/trailhead`** to start (smart entry), or `/trailhead:new "<idea>"` to chart a map. **On Codex** the surface is `$trailhead` instead: run **`$trailhead`** for smart entry, `$trailhead new "<idea>"` to chart, or a per-verb `$trailhead-<verb>` skill. Note: once installed, the **commit guard hook runs on every `git commit`** (enforcing Conventional Commits and blocking `Co-Authored-By`); disable the plugin's hooks in settings if you don't want that. Either way trailhead is self-contained: no external skill or plugin is required, no preflight, no version drift.
79
+ Restart or reload your agent so the commands register, then run **`/trailhead`** to start (smart entry), or `/trailhead:new "<idea>"` to chart a map. **On Codex** the surface is `$trailhead` instead: run **`$trailhead`** for smart entry, `$trailhead new "<idea>"` to chart, or a per-verb `$trailhead-<verb>` skill. Note: once installed, the **commit guard hook runs on every `git commit`** (enforcing Conventional Commits and blocking `Co-Authored-By`); disable the plugin's hooks in settings if you don't want that. Either way trailhead is self-contained: no external skill or plugin is required, no external-plugin preflight, no version drift.
79
80
 
80
81
  ---
81
82
 
@@ -87,7 +88,7 @@ A **map** is one GitHub issue labelled `trailhead:map`. It's an index, not a sto
87
88
 
88
89
  What does **not** need its own map is a single, defined piece of work with no open questions, small enough to finish in one session. That is just **one ticket**, a direct `build` (or a `todo`): a *work* you take with `/trailhead:work` and resolve in a sitting, no charting required. **Rule of thumb: decisions to make, or too big for one sitting → a map; "just do X" → a single work.** A feature that starts as a lone work but turns out to need decisions gets promoted to its own map (or split); a map whose questions all evaporate collapses back to a handful of works. And a repo can hold **several maps at once** (parallel milestones or features), each with its own frontier, so you can park one and work another.
89
90
 
90
- Each **ticket** is a child issue with a type label and a one-question body. The **frontier** is the set of open, unassigned tickets whose blockers are all closed: what's takeable right now. You claim a ticket by assigning it to yourself, resolve it with the engine for its type, then record the answer as a comment, close it, and gist it back onto the map.
91
+ Each **ticket** is a child issue with a type label and a one-question body. The **frontier** is the set of open, unassigned tickets whose blockers are all closed and that are not paused: what's takeable right now. You claim a ticket by assigning it to yourself, resolve it with the engine for its type, then record the answer as a comment, close it, and gist it back onto the map.
91
92
 
92
93
  As tickets resolve, the fog clears: questions that were too vague to phrase become sharp enough to ticket, one at a time, until nothing is left to decide or build and the destination is reached.
93
94
 
@@ -106,12 +107,46 @@ These three fill GitHub's **3 pinned-issue slots**, so a repo's trailhead anchor
106
107
  **The lifecycle.** Every project walks the same loop:
107
108
 
108
109
  1. **Start**: `/trailhead:new "<idea>"` (greenfield) or `/trailhead:adopt` (existing code: map the codebase once, then go lean). This *charts the map*: name the destination, then map the frontier breadth-first into the first tickets and the fog.
109
- 2. **Work the frontier, one ticket per session**: `/trailhead:work` takes the next takeable ticket (or one you name) and runs the engine for its type: `research` gathers a fact · `decision` diverges options then grills to choose · `prototype` makes a rough artifact to react to · `build` runs discuss → plan → execute → verify · `bug` runs repro → diagnose → fix → verify · `task` is manual plumbing that unblocks a decision. `/trailhead:auto` chains the frontier unattended, one ticket after another, stopping only at the safety rail, fog, or a human-necessary decision.
110
+ 2. **Work the frontier, one ticket per session**: `/trailhead:work` takes the next takeable ticket (or one you name) and runs the engine for its type: `research` gathers a fact · `decision` diverges options then grills to choose · `prototype` makes a rough artifact to react to · `build` runs discuss → plan → execute → verify · `bug` runs repro → diagnose → fix → verify · `task` is manual plumbing that unblocks a decision. `/trailhead:auto` runs a chosen scope unattended, in up to 4 parallel slots, until the frontier is exhausted or you stop it, parking whatever needs you (a safety-rail action, fog, a human-necessary decision; see below). At a ticket's start, `work` and `quick` show a short brief with a **Proceed / Change something** choice; it is skipped (a one-line note instead) for a ticket you created or just settled in a grilling this session, and `auto` never shows it.
110
111
  3. **Close & unblock**: resolve the ticket (comment + close), gist it into `Decisions so far`, and drop `trailhead:blocked` from any dependent whose last blocker just closed, graduating it onto the frontier.
111
112
  4. **Repeat** until the frontier is empty and the fog has cleared: the destination (a working artifact) is reached.
112
113
 
113
114
  Along the way: **capture** ideas mid-work without derailing, **split** a ticket that grew too big, **pause/resume** across sessions, and let several people work unblocked tickets in parallel.
114
115
 
116
+ ### 🤖 Autonomous run: `auto`
117
+
118
+ `/trailhead:auto [map]` runs a chosen scope unattended, ticket after ticket, resolving each with its normal engine instead of handing back to you in between. It suspends the one-ticket-per-session rule, and it never re-engages on its own: you start every run.
119
+
120
+ `auto` drains a map (or the whiteboard, or everything) unattended, ticket after ticket, in 1 to 4 parallel slots. `/trailhead:work` is the other mode: one ticket per session, with you in the loop (briefs, grilling, UAT choices). Reach for `auto` when the frontier holds well-specified build or bug work you don't need to steer; use `work` for decisions, fuzzy tickets, or when you want to drive. When you come back you find the run report in chat (also posted on each touched map), the tickets that need you labelled `trailhead:needs-you` with a `NEEDS YOU` comment, and local commits waiting for you to push: the run never pushes.
121
+
122
+ While it runs, the chat shows a progress line naming the tickets in the slots and what just resolved or comes next; at the end, the run report. Something like:
123
+
124
+ ```text
125
+ auto: slot 1 #42 add social login (verify) | slot 2 #43 session cookie (execute) | resolved #41, next #44
126
+
127
+ Run report
128
+ Stopped: frontier empty
129
+ Resolved: #41, #42, #43, #44
130
+ Merged: #41, #42, #43, #44 into main; local main is 7 commits ahead of origin (pushing is yours)
131
+ Remains: #45 needs you (which providers to support); #46 blocked by #45
132
+ ```
133
+
134
+ **Main checkout.** With more than one slot, the run merges each verified slot branch into the local trunk of the main checkout (a fast-forward when possible, else a merge commit), so if you are working in that checkout by hand your branch can move under you. Uncommitted tracked work stays protected: the merge stops on a dirty tree and the ticket is parked needs-you. Untracked files do not trip that check (one in the way of a file the merge brings in still stops it, parked the same way).
135
+
136
+ - **It asks first, before claiming anything, with no delegate option.** The run scope (one map, the whiteboard, or everything, each with its frontier count, then which map when you pick one map and several are open; `/trailhead:auto <n>` skips only this ask). The parallel slots (1, 2, 3 or 4, 2 proposed; asked under `<n>` too). The external review rounds (as configured, 1, 2, 3 or until convergence, which stops on a round with no progress, hard ceiling 5; at most four options in the structured menu, the full five as a numbered list on a host without one). The review ask is skipped when plan review is off or no independent reviewer CLI is installed (the host's own CLI does not count), and the choice holds for every `build` ticket of the run, overriding `plan_review.rounds` for that run.
137
+ - **Order.** Under everything it works one map at a time by ascending map number, the whiteboard last. Inside a scope, the oldest workable ticket first. Blocked tickets are never queued, tickets marked needs-you are skipped, and your own interrupted (paused) tickets in the scope are resumed first.
138
+ - **Parallel slots.** With 1 slot, `isolation: auto` and `none` work in the current checkout (an explicit `worktree` or `clone` still applies, and its push or PR is held for you). Above 1 slot, each ticket gets its own `trailhead/t<n>` workspace next to the checkout (`<repo>-t<n>`): a worktree whatever `isolation:` says, an explicit `clone` stays a clone, and a superrepo (submodules) escalates to a clone. It stays one orchestrating session, every engine step runs as a background agent for its slot, and a freed slot takes the next oldest ticket, so tickets of different maps can run together.
139
+ - **Local merge.** Under `git: main`, a verified worktree slot branch is merged into the local trunk (fast-forward, else a merge commit), never pushed, one at a time after the stale-base check; then the ticket resolves and its dependents unblock. A sequential run under an explicit `worktree` or `clone`, a run under `git: pr`, and clone slots hold the branch: the ticket is marked needs-you for integration, with its branch and workspace kept and listed.
140
+ - **Merge tickets.** When a slot branch conflicts, the merge is aborted (the trunk stays untouched), the branch stays separate with its workspace, and the run opens or updates one **merge ticket** per map (or for the whiteboard) the moment the conflict arises: a needs-you task listing each unmerged branch with its ticket, workspace and conflicting files, plus a suggested merge order (oldest first). You close it once every branch is merged or dropped. A scope with no unmerged branch gets none, and held branches open none. The conflicting ticket stays open, so its dependents stay blocked.
141
+ - **Acceptance.** The run drives the agent-performable acceptance steps itself, never picks skip and never fakes a human-only step. Human-only steps go to a **UAT follow-up** (reused if one is open, else a task on the same map or the whiteboard, or a seed when the steps wait on an external trigger) carrying the numbered checklist; then the parent resolves and its dependents unblock. Only the configured `uat.setup` runs, and a failing setup marks the ticket needs-you.
142
+ - **Needs you.** A ticket whose next step needs you is parked at once and the run continues: a `decision` convergence, a scope or destination change, an out-of-scope ruling, routing a deferred line, closing an exhausted map, a claim collision, fog to dissolve, a HITL `task` checklist only you can do, a safety-rail action, a stale-base stop, a failing UAT setup, an error looking up the UAT follow-up, or a plan review whose blocking concerns survive the rounds (quoted in full). It gets a `NEEDS YOU` comment saying what you must do, is unassigned, and carries the `trailhead:needs-you` label. It stays on your frontier and map and `/trailhead:work` still offers it, but runs skip it until you clear it (a `work` or `quick` claim clears it, or remove the label by hand).
143
+ - **Safety rail (always on, hardcoded).** A release, `git push`, opening a PR, a delete or force, or any other outward-facing action marks the ticket needs-you and the run continues. Local commits and the local slot merge are not rail actions.
144
+ - **Failure budget.** A ticket that will not verify gets extra fix rounds (a no-progress rule plus a hardcoded ceiling of 3), then is paused keeping its claim and skipped. One stuck ticket never ends the run.
145
+ - **Records, interruption, resume.** Every record (engine comments, commits, UAT follow-ups, `NEEDS YOU` comments, merge tickets, `PAUSED` checkpoints) is written on the tracker when it arises, so a crash loses nothing; the chat progress line, which names the tickets in the slots, is only a mirror. Stop/Esc is the only mid-run halt, and it writes nothing: every in-flight ticket keeps its claim and Esc leaves the same trace as a crash. To resume, run `/trailhead:auto` again: it first recovers an interrupted merge, then resumes paused or crashed tickets in their own workspaces, writing the missing `PAUSED` checkpoint (naming workspace and branch) on each crashed one as it resumes it, then continues. In the same session it reuses the run's choices; a new session asks again. Needs-you tickets are not reconsidered.
146
+ - **Run report.** Every run ends with one summary in full in chat, linking the records it summarises: why it stopped, what resolved, what merged (merged tickets, branches left unmerged with their conflicting files, merge tickets, and how many commits the local trunk is ahead of `origin`; pushing is yours), and what remains (needs-you tickets and why, paused or stuck tickets, blocked dependents, UAT follow-ups opened). It is also posted as a comment on each touched map with only that map's part; the whiteboard part stays in chat only, and untouched maps get nothing.
147
+
148
+ `auto` is orthogonal to `effort` and `models.*` and has no config surface of its own: the asks replace it.
149
+
115
150
  ### A worked example: "add social login"
116
151
 
117
152
  Destination: *users sign in with Google and GitHub, alongside email/password.* Charting fans it into six tickets (one of each type) wired by their dependencies:
@@ -181,7 +216,7 @@ Every verb is also a namespaced command (`/trailhead:new`, `/trailhead:work`, `/
181
216
  | `/trailhead:pause [note]` | checkpoint the ticket in play so anyone can resume it |
182
217
  | `/trailhead:ticket <type> <title>` | open a ticket on the fly (diverges briefly first: a micro-charting act) |
183
218
  | `/trailhead:split [ticket]` | split an oversized ticket into children, supersede the original |
184
- | `/trailhead:auto [map]` | run the frontier autonomously, one ticket after another, until a stop condition (safety rail / fog / a human-necessary decision) or exhaustion; suspends one-ticket-per-session |
219
+ | `/trailhead:auto [map]` | run a chosen scope (one map, the whiteboard or everything) in up to 4 parallel slots, until exhaustion or a stop; parks what needs you |
185
220
  | `/trailhead:grill [topic]` | run a standalone grilling session on a decision/topic |
186
221
  | `/trailhead:map` | show the low-res map (destination, decisions, frontier, fog) |
187
222
  | `/trailhead:dashboard` | show the repo dashboard: the pinned index of every open map, the whiteboard, and live counts |
@@ -193,11 +228,11 @@ The trailhead mod (Claude Code 2.1.287+) ships inside the plugin and draws the w
193
228
 
194
229
  ![The map panel docked beside the transcript: open maps, frontier with a work button per ticket, in progress, blocked, and a footer with refresh and close](assets/screenshots/map-panel.png)
195
230
 
196
- The map panel lists the open maps to view, the frontier with a `work` button per ticket that clears the context and starts the ticket, who holds what, and what is blocked. The footer has `refresh` and `close`. `/trailhead:panel` toggles it, and it opens by itself once in fullscreen.
231
+ The map panel lists the open maps to view, the tickets an autonomous run parked for you in a `Needs you` group at the top, the frontier with a `work` button per ticket that clears the context and starts the ticket, who holds what (your own ticket is marked `this session`), the pending UAT follow-ups in a `UAT` group (each naming the ticket it tests), the paused ones in a `Paused` group, and what is blocked. A dim line under the Whiteboard row totals the open tickets across every open map plus the whiteboard, the same number `/trailhead:map` states (`at least N` when more open maps exist than the panel reads). The footer has `refresh` and `close`. `/trailhead:panel` toggles it, and it opens by itself once in fullscreen.
197
232
 
198
233
  ![The panel viewing another map, with a viewing and active header and a back to active button](assets/screenshots/map-panel-viewing.png)
199
234
 
200
- A click only views a map, and `back to active` returns to the active one. The active map changes when you work one of its tickets.
235
+ A click only views a map, and `back to active` returns to the active one. The active map changes when you work one of its tickets. A `Whiteboard` row above the maps list, with its `N open / M` counts, shows the repo index instead: every open map (a click opens it) and the whiteboard's loose tickets by state, each frontier one with its `work` button; `map` goes back.
201
236
 
202
237
  ![The Burn-down section of the panel: counts line and a chart of open tickets over time](assets/screenshots/burndown.png)
203
238
 
@@ -215,7 +250,7 @@ The pipeline band above the prompt and the status entry under it show the ticket
215
250
  | `/trailhead:seed <text>` | a blocked ticket (trigger noted) | *I'll do it when X happens* |
216
251
  | `/trailhead:idea <text>` | the fog (`Not yet specified`) | *maybe I'll do it*: graduates to a ticket if it sharpens |
217
252
  | `/trailhead:note <text>` | verbatim text | *remember this*: not necessarily work |
218
- | `/trailhead:bug [--of <ticket>] <text>` | a `bug` ticket | a defect; `--of` records it as a `Regression of:` a closed ticket |
253
+ | `/trailhead:bug [--of <ticket>] <text>` | a `bug` ticket | a defect; `--of` records it as a `Regression of:` a closed ticket and files it on that ticket's map (or the whiteboard) without asking |
219
254
 
220
255
  The four fog/ticket captures form a spectrum of commitment and timing: **note < idea < seed < todo**.
221
256
 
@@ -260,7 +295,7 @@ Each type has its own inline engine: no external skill is invoked.
260
295
  | 🧭 `decision` | a choice | HITL | diverge the options if unclear, then grill to converge on one |
261
296
  | 🔬 `research` | a fact | AFK | a subagent on a throwaway branch (the only type run in parallel) |
262
297
  | 🎨 `prototype` | an approved direction | HITL | a rough throwaway artifact to react to; UI screens go through this (disk, an Artifacts canvas, or Stitch) before UI code |
263
- | 🔨 `build` | working code | HITL/AFK | `discuss → plan → execute → verify`: atomic commits, TDD at seams, **a mockup first for user-facing UI** (Prototype technique, gated by `design.approval`), code review + acceptance testing (asks who drives, AI or human, then browser-drive or conversational step-by-step UAT) |
298
+ | 🔨 `build` | working code | HITL/AFK | `discuss → plan → execute → verify`: atomic commits, TDD at seams, **[a mockup first](#-mockups-before-code) for user-facing UI** (Prototype technique, gated by `design.approval`), code review + [acceptance testing](#-acceptance-testing-uat) (asks who drives, AI or human, or skip to waive it, then browser-drive or conversational step-by-step UAT) |
264
299
  | 🐛 `bug` | corrected code | HITL/AFK | `repro → diagnose → fix → verify`; a defect in closed work is a *new* ticket (`Regression of:`), not a reopen |
265
300
  | 🔧 `task` | an external state change | HITL/AFK | manual work that unblocks a decision (provision access, move data, sign up) |
266
301
 
@@ -270,6 +305,80 @@ When trailhead puts a forced choice to you, an advisory one (not a decision that
270
305
 
271
306
  ---
272
307
 
308
+ ## 🎨 Mockups before code
309
+
310
+ Every ticket that changes user-facing UI (a new screen, a redesign, a changed layout) is **mocked up before any UI code is written**. You react to the mockup, approve it, and the build reproduces it faithfully; acceptance testing later checks the result against it. Where the mockup lives is the `design` key:
311
+
312
+ | `design` | Where the mockup lives | Hosts |
313
+ |---|---|---|
314
+ | **`disk`** (default) | a throwaway static HTML file next to the code, linked from the ticket | any |
315
+ | `artifacts` | a **Design canvas Artifact** on claude.ai: edit it visually, comment on it, export it | Claude Code signed in with a claude.ai account |
316
+ | `stitch` | a **Google Stitch** project (or Stitch design system) | Claude Code and Codex |
317
+
318
+ A UI question that's still open gets **3 structurally different variants** (different layouts, not colours or copy) to pick from, not one take.
319
+
320
+ ### What it looks like with `artifacts`
321
+
322
+ ```text
323
+ you › /trailhead:work # ⑤ build: "login screen"
324
+ trailhead › This ticket changes UI. Mock it up first? → yes
325
+ trailhead › No canvas for this map yet: pick one, paste a URL, or create one?
326
+ you › create one, "Acme app"
327
+ trailhead › Installed your design system "Acme DS" on the canvas.
328
+ Canvas: https://claude.ai/artifact/… · page "#42 Login"
329
+ Artboards: Login-A (split hero) · Login-B (centered card) · Login-C (stepper)
330
+ Which direction?
331
+ you › B, but with the social buttons above the form # or edit it on the canvas yourself
332
+ trailhead › Updated Login-B. Approve? → approve
333
+ trailhead › Re-read the approved artboard (your live edits included) → planning the build
334
+ ```
335
+
336
+ - **One canvas per map**, cached in `design.project`; each ticket gets its own canvas page, each screen (and variant) its own artboard. App screens get a fixed frame (e.g. a 390×844 phone), web pages a fluid one.
337
+ - **Your design system on the canvas.** `design.system` (or your account's default) is installed, so the canvas editor offers its colours, type and components instead of look-alikes.
338
+ - **Approve with the link in front of you.** Every approval ask carries the canvas URL and the artboard names; the [mockup-link guard](#-hooks) blocks one that doesn't.
339
+ - **Edit freely.** Change the canvas directly or leave comments there; on approval trailhead **re-reads** the artboards and builds from *that* version, not from what it last published.
340
+ - **`design.approval`**: `explicit` (default) waits for your go-ahead before UI code; `auto` surfaces the mockup and proceeds.
341
+
342
+ ### `stitch` and `disk`
343
+
344
+ `design: stitch` uses the **Google Stitch MCP**, Google's Gemini-powered UI generator, and is the hosted option that runs on **both Claude Code and Codex**. `design.surface` picks a Stitch project of screens (`canvas`, default) or a shared Stitch design system (`design-system`). If the MCP isn't connected, trailhead offers to install it once (decline and it falls back to disk):
345
+
346
+ ```bash
347
+ claude mcp add --scope user --transport http stitch https://stitch.googleapis.com/mcp
348
+ ```
349
+
350
+ Where `artifacts` can't run (Codex, or an API-key session), trailhead offers to switch to `stitch` before falling back to disk, and says so: it never silently swaps a hosted mode for a local file.
351
+
352
+ **Moving from claude.ai/design.** The standalone Claude Design site closes on 2026-12-14 and its designs now live in Claude as Artifacts. A config that still says `design: claude.ai/design` keeps working as `artifacts` (trailhead tells you once and offers to rewrite the key), but a cached claude.ai/design project can't be reused: migrate your design systems from the Artifacts page banner and let trailhead attach a new canvas.
353
+
354
+ ---
355
+
356
+ ## 🧪 Acceptance testing (UAT)
357
+
358
+ Passing tests prove the code does what the *code* intended; acceptance testing proves it does what *you* wanted. For anything you can see (a new screen, data that now appears, a changed flow), a green suite **does not close the ticket**: Verify runs a UAT too, after tests and code review.
359
+
360
+ Before it starts, trailhead **checks the base**: it measures how far your working branch is behind the trunk, syncs it (fast-forward, rebase or merge; a conflict aborts and restores), re-runs the tests and runs your `preflight.checks`, and the same check precedes integration; anything it cannot verify stops the ticket. Then it **prepares the environment**: the `uat.setup` steps (start the dev server, boot the emulator; a step whose `check` already passes is skipped) run before the ask below, a failed step stops with a clear reason, and `uat.accounts` names the test logins (only `$ENV` references are stored). With no setup configured it can probe the project and propose one to save. `/trailhead:auto` runs the configured setup only.
361
+
362
+ 1. **It asks who drives.** Before any step runs: **AI-driven**, **human-driven** or **skip**. It waits for a clear answer (silence re-asks). If no step is something the agent can do this session (a device it can't reach, credentials it doesn't hold, a judgement of feel or sound), it drops the AI-driven option and asks **human-driven** or **skip**, telling you why in one line. **Skip is a waiver, not a deferral**: you forgo acceptance, the `VERIFY` comment records it as `waived by the user (unconfirmed)`, never as a pass, and no follow-up is opened. The agent never picks it for you, not even under `/trailhead:auto`.
363
+ 2. **AI-driven: it drives the app itself.** For a web app it finds the target URL on its own (a running dev server, the `dev` script, the framework's configured port), proposes it for you to confirm, then navigates, fills, clicks and asserts what's on screen, with a **screenshot as evidence** in the ticket's `VERIFY` comment. **Beyond the browser, it drives whatever device the session can reach**: a phone or emulator over `adb`, an iOS simulator, a smart TV, a desktop app, through the commands and tools you have connected (a device MCP, computer use, the platform's CLI). Only the steps it truly can't do come back to you, one at a time.
364
+ 3. **Human-driven, or what the agent can't reach: a guided walkthrough.** A device that isn't connected, an account it doesn't hold, a call on feel or sound: trailhead walks you through it **one step at a time in the chat** (what to do, where to tap, what you should see), waits for what you actually saw, then moves on. Never a checklist dumped on you to self-serve.
365
+ 4. **Against the approved mockup.** When the ticket had one, the built screen is checked against it **element by element and state by state** (layout, spacing, colour, type, copy; empty, loading, error, disabled…), from the re-read mockup, never from memory. An unforced deviation is a finding, not "close enough".
366
+ 5. **Failures become tickets on the spot.** A failed step records expected vs. seen and opens a `bug` follow-up (or a fix on this ticket) right away, then asks whether to carry on.
367
+ 6. **Nothing slips through as "done".** If the UAT can't run now (the device isn't at hand, the backend isn't deployed), trailhead asks: run it now, or open a follow-up ticket, linked back with a `UAT of:` line and tagged with the `trailhead:uat` label so every pending UAT is findable by one label, that carries the full checklist. A user-facing ticket never closes on a UAT nobody ran unless you explicitly waive it, recorded as unconfirmed.
368
+
369
+ ```text
370
+ trailhead › Tests and review are green. Who drives the UAT: AI or you? → AI
371
+ trailhead › Acceptance will run against http://localhost:5173, correct? → yes
372
+ trailhead › ✓ Sign in with GitHub → lands on /dashboard (screenshot)
373
+ ✓ Login-B vs. the build: 14/14 elements, 4/4 states match
374
+ ✓ Android app on the connected phone (adb): GitHub sign-in → home (screenshot)
375
+ ? Step 6 is the haptic on "Signed in": does it buzz once, softly?
376
+ ```
377
+
378
+ Config: `acceptance.browser` (`auto` default, `on`, `off`) governs browser driving; `off` keeps AI-driven checks via commands and the suite, and still asks who drives. `testing.webapp` / `testing.url` say whether the app is browser-drivable and where. Under `/trailhead:auto`, it goes AI-driven; steps only you can do are never faked: they go to a UAT follow-up carrying the checklist, and the parent ticket resolves; `/trailhead:auto` never picks skip.
379
+
380
+ ---
381
+
273
382
  ## 🏷️ Labels
274
383
 
275
384
  Everything the map needs is expressed as GitHub labels, so state is queryable in the tracker UI:
@@ -277,10 +386,11 @@ Everything the map needs is expressed as GitHub labels, so state is queryable in
277
386
  - **Structural:** `trailhead:map`, `trailhead:ticket`
278
387
  - **Repo-scoped anchors (one each per repo, pinned):** `trailhead:codebase` 🧱 (the distilled codebase map), `trailhead:conventions` 📜 (the way of working), `trailhead:dashboard` 📊 (the pinned index of maps + whiteboard + counts); each title carries its icon (like the map's 🗺) so the pinned anchors stand apart at a glance
279
388
  - **Type (one per ticket):** 🧭 `trailhead:decision` · 🔬 `research` · 🎨 `prototype` · 🔨 `build` · 🐛 `bug` · 🔧 `task`
280
- - **State:** `trailhead:blocked` (has an open blocker) · `seed` (parked on a trigger) · `out-of-scope` (closed, beyond the destination) · `superseded` (closed, split into children)
389
+ - **State:** `trailhead:blocked` (has an open blocker) · `seed` (parked on a trigger) · `out-of-scope` (closed, beyond the destination) · `superseded` (closed, split into children) · `paused` (checkpointed, off the frontier) · `needs-you` (parked by an autonomous run for you: runs skip it, it stays on your frontier) · `unverified` (quarantined until a maintainer adopts it) · `fog` (kept open for clarification)
281
390
  - **Container:** `trailhead:whiteboard` (a loose, map-less ticket, off every map's frontier, on the whiteboard's own)
391
+ - **Marker:** `trailhead:uat` (a pending UAT follow-up; never a frontier filter) · `trailhead:map-<n>` (scopes a ticket to its map)
282
392
 
283
- The **frontier** is then a single query (open, unassigned, not `trailhead:blocked`), no body-parsing needed.
393
+ The **frontier** is then a single query (open, unassigned, not `trailhead:blocked`, not paused), no body-parsing needed.
284
394
 
285
395
  ## 👥 Working as a team
286
396
 
@@ -288,10 +398,10 @@ Many people (and their agent sessions) share one map and work it concurrently:
288
398
 
289
399
  - **Claim = assign to yourself.** The claimer owns the ticket end to end and closes it: there is no approver and no one to wait on. On a claim collision the session stops and asks you, rather than resolving it silently.
290
400
  - **Split** an oversized ticket into children and supersede the original (`/trailhead:split`).
291
- - **Pause/resume** via a `PAUSED` checkpoint comment, so any session can pick a ticket back up.
292
- - **Isolate concurrent sessions with worktrees.** By default (`isolation: auto`) trailhead does this for you: at each ticket's work-start it asks whether you'll run more than one at once and picks the mode by repo shape. To force it eagerly, set `isolation: worktree` in the conventions header, and each executing ticket runs in its own `git worktree` on a `trailhead/t<n>` branch, integrated back to the trunk at Resolve. It implies a branch per ticket even under `git: main` (git won't check the trunk out twice), and it is the right posture for a **monorepo** (a worktree shares the object store, far cheaper than a second clone). One caveat: a worktree isolates *source* but is a fresh path with no installed deps, so a **path-bound package** (React Native/Expo, native toolchains, anything tied to a local `node_modules`) won't build from it. For those there's **`isolation: clone`**: a dedicated per-ticket clone that installs its own deps and builds/UATs like the real app (heavier than a worktree, and the right tool for an Expo/RN submodule). Or keep `isolation: none` and serialize. Otherwise run concurrent sessions in separate clones.
401
+ - **Pause/resume** via a `PAUSED` checkpoint comment and a `trailhead:paused` label (added by pause, removed by resume), so any session can pick a ticket back up. Paused tickets are listed under a `Paused` group in the map, whiteboard and dashboard views.
402
+ - **Isolate concurrent sessions with worktrees.** By default (`isolation: auto`) trailhead does this for you: at each ticket's work-start it asks whether you'll run more than one at once and picks the mode by repo shape. To force it eagerly, set `isolation: worktree` in the conventions header, and each executing ticket runs in its own `git worktree` on a `trailhead/t<n>` branch, integrated back to the trunk at Resolve. It implies a branch per ticket even under `git: main` (git won't check the trunk out twice), and it is the right posture for a **monorepo** (a worktree shares the object store, far cheaper than a second clone). One caveat: a worktree isolates *source* but is a fresh path with no installed deps, so a **path-bound package** (React Native/Expo, native toolchains, anything tied to a local `node_modules`) won't build from it. For those there's **`isolation: clone`**: a dedicated per-ticket clone that installs its own deps and builds/UATs like the real app (heavier than a worktree, and the right tool for an Expo/RN submodule). Or keep `isolation: none` and serialize. Otherwise run concurrent sessions in separate clones. A fresh worktree or clone has no `.env`: list the gitignored files it needs under `isolation.copy` in `.trailhead/config.json` and trailhead copies them in right after creating the workspace, never overwriting (see Configuration).
293
403
  - **A session-ticket marker for your statusline.** When it starts working a ticket, trailhead drops a one-line, gitignored `.trailhead/session-ticket` (`#<n> <title>`) at the working root and clears it at handoff. It's a cheap, offline hint so a statusline (or any tool) can show *which ticket this session is on* without hitting the tracker; trailhead itself still treats the Issues as the source of truth. It also does more than that: while it's present, the git commit-msg hook requires every commit to carry a `Refs: #<n>` trailer matching it.
294
- - **It picks isolation for you by default (`auto`).** With no explicit `isolation:` set (the default is `auto`), the first time you work a ticket trailhead asks whether you'll work more than one at once on this machine this session; if so it uses a `git worktree` (plain repo) or a `clone` (submodule superrepo), otherwise it just works in the current checkout. Nothing is written into the conventions, and you can still force `none`/`worktree`/`clone` explicitly.
404
+ - **It picks isolation for you by default (`auto`).** With no explicit `isolation:` set (the default is `auto`), at a ticket's work-start trailhead asks whether you'll work more than one at once on this machine this session; if so it uses a `git worktree` (plain repo) or a `clone` (submodule superrepo), otherwise it just works in the current checkout. Nothing is written into the conventions: the answer is remembered on this machine in the gitignored `.trailhead/isolation-answer.json` and reused, with a one-line note, while your other assigned tickets stay the same and the last work-start was under 8 hours ago; a change or an expired record re-asks. You can still force `none`/`worktree`/`clone` explicitly, and `/trailhead:auto` and explicit modes never use the remembered answer.
295
405
  - **Scope tickets by path on a monorepo.** A ticket can carry a `Scope:` line naming the package/dir(s) it touches: disjoint scopes parallelize safely, and it scopes build/test and the worktree's commits to the affected package. `/trailhead:adopt` detects a monorepo and proposes both `isolation: worktree` and the `Scope:` convention. Under `isolation: none`, `Scope:` also **serializes** a shared area: at claim time a session holds off if another in-progress ticket's scope overlaps, so two tickets scoped to the same submodule/package can't run at once (how you keep two sessions off one path-bound app).
296
406
  - **Git submodules are handled as separate repos.** Work that lands inside a submodule is committed *inside the submodule* (on a branch) and published before a gitlink bump in the parent points at it. Because superproject worktrees share one submodule git dir, a **submodule-bearing repo resolves to `clone`** (under the default `auto`, or by escalating an explicit `worktree`), so each ticket gets independent submodule state; the clone provisions its submodules at setup (`--recurse-submodules`) and Verify checks `git status` inside each touched submodule. A ticket spanning two submodules stays atomic via a single parent commit that bumps both gitlinks together; its `Scope:` names both, and the same submodule counts as overlapping scope, so two tickets on it serialise. `/trailhead:adopt` detects `.gitmodules` and writes the rule into the conventions.
297
407
  - **State lives on the tickets** (claim, labels, comments; all conflict-free); the map body is a re-readable index, not the source of truth. The durable record of a resolution is the ticket's own comment.
@@ -320,7 +430,7 @@ Three layers: **nearest wins**, key by key (a key unset at one layer inherits th
320
430
  - **Global**: `~/.claude/trailhead/config.json`: your standing defaults across every project.
321
431
  - **Defaults**: the built-in values.
322
432
 
323
- `/trailhead:config` runs a **guided, menu-driven setup**: pick the scope, then walk each setting (⚙️ way of working (git · release · isolation) · 🌐 ticket language · 🧠 models · 🎨 design + approval · 🧪 TDD · 🖥️ acceptance testing · 🧑‍⚖️ plan review · 📊 statusline) as an icon-labelled menu; no hand-editing JSON. Every step is asked (none skipped), and **plan and execute models are always two separate, version-pinned choices**. `config get` prints the effective merged config; `config set <key> <value>` writes one key.
433
+ `/trailhead:config` runs a **guided, menu-driven setup**: pick the scope, then walk each setting (⚙️ way of working (git · release · isolation) · 🌐 ticket language · 🧠 models (a High / Balanced / Low profile, or Manual) · 🎨 design + approval · 🧪 TDD · ⚖️ effort · 🖥️ acceptance testing (browser, UAT setup, a test account) · 🧑‍⚖️ plan review · 📊 statusline) as an icon-labelled menu; no hand-editing JSON. Every step is asked (none skipped), and **plan and execute models are always two separate, version-pinned choices**. `config get` prints the effective merged config; `config set <key> <value>` writes one key.
324
434
 
325
435
  `/trailhead:prune [map]` reclaims a map's sub-issue slots under GitHub's 100-cap (drops closed/orphan edges, keeps every reference). Offered automatically when a map is near or at the cap, and runnable on demand.
326
436
 
@@ -333,26 +443,31 @@ The **📊 statusline** step offers to install trailhead's Claude Code status ba
333
443
  | Key | Values (default **bold**) | Effect |
334
444
  |---|---|---|
335
445
  | `ticket.language` | an ISO 639-1 code (**`en`**) | the language trailhead **writes** its GitHub prose & commit descriptions in, decoupled from the language it converses in |
336
- | `models.{plan,execute,research,review,debug,verify,codebase-map}` | a full **versioned** model id (**inherit session**) | which model runs each activity; `plan` and `execute` are always set separately |
446
+ | `models.{plan,execute,research,review,debug,verify,codebase-map}` | a full **versioned** model id (**Balanced-profile default**; `inherit session` is an explicit choice) | which model runs each activity; `plan` and `execute` are always set separately |
447
+ | `models.codex.{plan,execute,research,review,debug,verify,codebase-map}` | an OpenAI model id, or `{model, effort}` (**inherit session**) | the same per-activity split on Codex, independent of `models.*` (Claude ids); re-run the installer to project it into `~/.codex/agents/` |
337
448
  | `design` | **`disk`** \| `artifacts` \| `stitch` | where UI mockups go: local throwaway HTML, a Design canvas **Artifact** on claude.ai (Claude Code only), or Google Stitch (cross-host, via the Stitch MCP). The legacy `claude.ai/design` value is read as `artifacts` |
338
449
  | `design.project` | canvas Artifact URL / Stitch project id | `artifacts` and `stitch` only: the canvas or Stitch project the mockups land on; set at the first UI screen (pick, paste or create), not by hand. A leftover claude.ai/design project is not reused |
339
450
  | `design.surface` | **`canvas`** \| `design-system` | `stitch` only: a Stitch project of screens (default) or a shared Stitch design system |
340
451
  | `design.system` | Design System Artifact URL | `artifacts` only: the design system installed on the mockup canvas (asked at the first screen if unset) |
341
452
  | `design.approval` | **`explicit`** \| `auto` | wait for mockup approval before UI code, or proceed without blocking |
342
453
  | `tdd` | **`seams`** \| `on` \| `off` | how the `build` engine tests |
343
- | `acceptance.browser` | **`auto`** \| `on` \| `off` | drive the browser in Verify (after asking who drives, AI or human); `off` still runs AI-driven checks via commands, and walks you through a conversational UAT for anything only you can do (step by step in chat, not a checklist to self-serve) |
454
+ | `acceptance.browser` | **`auto`** \| `on` \| `off` | drive the browser in Verify (after asking who drives, AI or human, or skip); `off` still runs AI-driven checks via commands, and walks you through a conversational UAT for anything only you can do (step by step in chat, not a checklist to self-serve) |
344
455
  | `testing.webapp` / `testing.url` | bool / URL | is it browser-drivable, and where |
345
456
  | `plan_review` | **`off`** \| `on` \| CLI list | send `build` PLANs to external AI CLIs (Gemini, Codex, …) for a second opinion and converge on their concerns |
346
457
  | `plan_review.rounds` | integer (**`2`**) | max converge-and-re-review rounds |
347
458
  | `code_review.rounds` | integer (**`2`**) | max fix-and-re-review rounds in Code review before its boundary HITL checkpoint |
348
459
  | `effort` | **`standard`** \| `lean` | how hard the `build`/`bug` engine works a change: `standard` runs every step, `lean` size-triages small changes and offers to skip the heavy ones (plan pass, TDD, cross-AI review, full code review, goal-backward verify). Governs `build`/`bug` only: `quick` always size-triages regardless of this key |
460
+ | `isolation.copy` | array of repo-relative paths (**unset**) | gitignored files and directories (`.env`, `.npmrc`, local config) copied from the main checkout into every new `worktree`/`clone` workspace, never overwriting; unsafe paths (absolute, `..`, `.git`) are rejected. When unset, trailhead suggests the `.env` files that have a tracked `*.example` sibling |
461
+ | `preflight.checks` | array of shell commands (**unset**) | your own project checks, run by the base preflight before UAT and before integration; a non-zero exit stops the ticket (e.g. a check for duplicate migration timestamps) |
462
+ | `uat.setup` | array of `{name, run, check?, note?}` (**unset**) | ordered setup steps run at every UAT start, before the drive-mode ask (start the dev server, boot the emulator); a passing `check` skips a step; `[]` means no setup needed |
463
+ | `uat.accounts` | object of `role: {user, secret}` (**unset**) | named test accounts for UAT; `secret` is always a `$ENV` reference, never a value, so the committed config stays generic (the guided setup asks for the variable name only) |
349
464
 
350
465
  **Models.** Each key runs its activity as a **subagent** on the model you name, so the whole per-activity split applies within a single work session, whatever model that session runs on:
351
466
 
352
467
  - `plan` and `execute` are the build/bug engine's steps. Execute is a subagent too, and it commits to `main`.
353
468
  - `research`, `review`, `debug`, `verify` (the goal-backward Verify check), and the codebase-map fan-out each run on their own key.
354
469
 
355
- Choose `plan` and `execute` separately, each by **full versioned id** (never a bare `opus` or `sonnet`). A key that is unset, or equal to the session model, simply runs inline.
470
+ Choose `plan` and `execute` separately, each by **full versioned id** (never a bare `opus` or `sonnet`). A key left unset uses its Balanced-profile default, and one equal to the session model simply runs inline; set `inherit session` to use the session model.
356
471
 
357
472
  The **main session stays the orchestrator**. It holds the interactive moments (a build's Discuss, and Verify's acceptance/UAT) and it does charting, grilling, and ticket-writing on its own model. No key governs ticket quality, so run charting sessions on your strong model.
358
473
 
@@ -362,34 +477,33 @@ Example config file (the same shape works for the project `.trailhead/config.jso
362
477
  { "ticket": { "language": "en" }, "models": { "plan": "claude-opus-5-5", "execute": "claude-sonnet-5-5" }, "tdd": "seams", "acceptance": { "browser": "auto" } }
363
478
  ```
364
479
 
365
- **Design mockups.** `design: disk` (the default) drops a throwaway static HTML mockup next to the code and links it from the ticket. `design: artifacts` builds the mockup as a **Design canvas Artifact** on claude.ai, through Claude Code's built-in `Artifact` tool (nothing to install), where you refine it visually, comment on it and export it; `design: stitch` uses the **Google Stitch MCP** (`claude mcp add --scope user --transport http stitch https://stitch.googleapis.com/mcp`), Google's Gemini-powered UI generator that runs on **both Claude Code and Codex** (the cross-host hosted option).
366
-
367
- With **`artifacts`**, on the first UI screen trailhead attaches a canvas: **pick an existing one**, **paste a URL**, or **create a new one** (it asks you for the name), and caches its URL in `design.project`. Each screen (and each variant) is its own artboard, grouped in a canvas page per ticket; the design system in `design.system` (your account's default one, or the one you pick) is installed on the canvas so its colours, type and components are at hand. Approval messages carry the canvas URL plus the name of each artboard. Once you approve, trailhead **re-fetches** the artboards (in case you edited them live) before writing any UI code.
480
+ **Design mockups and acceptance testing.** How the `design.*` keys play out (canvas, design system, approval, moving from claude.ai/design) is in [Mockups before code](#-mockups-before-code); how `acceptance.browser` and `testing.*` drive Verify is in [Acceptance testing](#-acceptance-testing-uat).
368
481
 
369
- With **`stitch`**, `design.surface` picks a Stitch project of screens (`canvas`, default) or a shared Stitch design system (`design-system`); trailhead picks, pastes or creates it the same way, and offers once to install the Stitch MCP if it isn't connected (decline and it falls back to local disk).
370
-
371
- **Moving from claude.ai/design.** The standalone Claude Design site closes on 2026-12-14 and its designs now live in Claude as Artifacts. A config that still says `design: claude.ai/design` keeps working as `artifacts` (trailhead tells you once and offers to rewrite the key), but a cached claude.ai/design project can't be reused: migrate your design systems from the Artifacts page banner and let trailhead attach a new canvas. Where the `Artifact` tool isn't available (Codex, or an API-key session), trailhead offers to switch to `stitch` before falling to local disk. `design.approval` decides whether the build waits for your explicit go-ahead (`explicit`), or proceeds right after surfacing the mockup (`auto`).
482
+ **Nested or flat.** The list keys above (`isolation.copy`, `preflight.checks`, `uat.setup`, `uat.accounts`) can be written nested (`{"uat": {"setup": [...]}}`) or flat (`"uat.setup": [...]`); if a file has both, the nested form wins with a warning, and a project value replaces the global one as a whole (lists are not merged). `git`, `release` and the isolation *mode* stay in the conventions header; only `isolation.copy` lives in `config.json`.
372
483
 
373
484
  ---
374
485
 
375
486
  ## 🪝 Hooks
376
487
 
377
- Beyond the skill's instructions, trailhead ships eight of its own hooks (in `hooks/`, self-contained via `${CLAUDE_PLUGIN_ROOT}`) that *enforce* the parts of the discipline the model shouldn't be trusted to remember:
488
+ Beyond the skill's instructions, trailhead ships nine of its own hooks (in `hooks/`, self-contained via `${CLAUDE_PLUGIN_ROOT}`) that *enforce* the parts of the discipline the model shouldn't be trusted to remember:
378
489
 
379
490
  - **Commit guard** (`PreToolUse` on `git commit`): hard-blocks a commit whose subject isn't [Conventional Commits](https://www.conventionalcommits.org), and hard-blocks any `Co-Authored-By` trailer. This mirrors GSD's commit validation.
380
491
  - **Secret guard** (`PreToolUse` on `gh` issue/PR writes): trailhead posts a lot to the tracker (ticket bodies, engine comments, codebase/conventions issues, resolutions). This scans what a `gh issue`/`gh pr`/`gh api` write is about to post (the inline `--body`, a heredoc, or a `--body-file`) and **hard-blocks** it if it matches a credential pattern (private keys, GitHub/AWS/OpenAI/Slack/Google/Stripe tokens, JWTs, or a hardcoded `password`/`secret`/`token=…`). The block is a **clean-and-retry cue, not a dead end**: trailhead redacts the flagged value in place (`<REDACTED>` or an env-var reference) and re-posts automatically, so the cleaned content still lands: the secret just never reaches the tracker. It stays a fail-safe *block* rather than a silent auto-rewrite on purpose: a block never leaks, whereas a redaction that quietly failed to apply would. It never scans reads, only outbound writes.
381
- - **Install guard** (`PreToolUse` on package installs): a slopsquatting barrier. It hard-blocks a named public-registry install (npm / PyPI / crates.io / Go / RubyGems) until the package is vetted (typosquat similarity to popular names, age / first-seen date, download counts, repository metadata present and matching); once vetted, re-run the exact command prefixed with `TRAILHEAD_VERIFIED_INSTALL=1` to proceed. The Execute / Fix engine carries the same block-and-ask.
382
- - **Search guard** (`PreToolUse` on `Bash`): structural backstop for the search-command-hygiene rule (never `cd` then read/search a relative path). It hard-blocks a single Bash command that both changes directory (`cd`/`pushd`/`env -C`) and, in a joined or later statement, reads/searches a relative path (`grep`/`rg`/`ag`/`cat`/`head`/`tail`/`wc`/`nl`/`sed`/`awk`/`less`/`more`, or `git grep`/`log`/`show`/`diff` without a `-C`), so the shape that trips a `Read()` deny rule under bypass permissions (falling back to a manual approval prompt instead of running headless) never runs at all, rather than merely being discouraged in the agent's own prose. Claude Code only; not projected to Codex, which has no such `Read()` deny rule.
492
+ - **Install guard** (`PreToolUse` on package installs): a slopsquatting barrier. It hard-blocks a named public-registry install (npm / PyPI / crates.io / Go / RubyGems) until the package is vetted (typosquat similarity to popular names, age / first-seen date, download counts, repository metadata present and matching); once vetted, re-run the exact command prefixed with `TRAILHEAD_VERIFIED_INSTALL=1` to proceed. The Execute / Fix engine carries the same block-and-ask. It reads commands the way a shell does: text inside quotes or a heredoc (an `echo`, a commit message) is not an install, lockfile installs pass, and wrappers such as `sudo`, `time`, `env` or `xargs` are seen through.
493
+ - **Search guard** (`PreToolUse` on `Bash`): structural backstop for the search-command-hygiene rule (never `cd` then read/search a relative path). It hard-blocks a single Bash command that both changes directory (`cd`/`pushd`/`env -C`) and, in a joined or later statement, reads/searches a relative path (`grep`/`rg`/`ag`/`cat`/`head`/`tail`/`wc`/`nl`/`sed`/`awk`/`less`/`more`, or `git grep`/`log`/`show`/`diff` without a `-C`), so the shape that trips a `Read()` deny rule under bypass permissions (falling back to a manual approval prompt instead of running headless) never runs at all, rather than merely being discouraged in the agent's own prose. Redirection targets (`2>/dev/null`) are not treated as reads, and a `cd` to the session's own directory does not arm it. Claude Code only; not projected to Codex, which has no such `Read()` deny rule.
383
494
  - **Body guard** (`PreToolUse` on `gh` issue/PR body writes): hard-blocks a `gh issue edit` / `gh pr edit` / `gh api` write that would set an issue body to empty or whitespace-only, so a read-modify-write whose read returned an empty/degraded body (e.g. a transient HTTP 5xx on `gh issue view --json body`) can never clobber the existing body.
495
+ - **Labels guard** (`PreToolUse` on `gh issue create`): checks the label set of a ticket at the create call. A create carrying any `trailhead:*` label must carry the whole ticket shape: `trailhead:ticket`, exactly one type (`decision`, `research`, `prototype`, `build`, `bug`, `task`) and exactly one destination (one `trailhead:map-<n>` or `trailhead:whiteboard`, never both, never two maps); the block names what is missing or conflicting and asks to retry with every label in the same create call, never a later `--add-label`. Non-ticket issues (`trailhead:map`, `dashboard`, `codebase`, `conventions`, `fog`, `unverified`) are exempt. A create with no `trailhead:*` label at all is blocked too, but only inside a trailhead repo (the working directory sits in a git work tree whose top level has `.trailhead/`) and not when aimed at another repo with `-R`/`--repo`; a deliberate non-trailhead issue opts out by prefixing the command with `TRAILHEAD_PLAIN_ISSUE=1`. A label value the guard cannot resolve statically (`$VAR`, backticks, `$(...)`) is never blocked.
384
496
  - **Secret-read guard** (`PreToolUse` on `Read|Bash`): denies reads of `.env`/`.env.*`/`.secrets`, whether via the Read tool or a Bash command (a file operand, an option value, or a `<` redirect target). This is a hook `deny` decision rather than a `Read()` permission rule, so secrets stay unreadable without arming Claude Code 2.1.259's `cd-compound-read` approval prompt.
385
497
  - **Mockup-link guard** (`PreToolUse` on `AskUserQuestion`): structural backstop for the Prototype rule that a mockup approval ask carries the mockup's link. It hard-blocks an `AskUserQuestion` that asks to approve a mockup (or pick between mockup variants) when neither the reply text right before it nor the approval question itself holds a URL or file path (when the current message is not yet in the transcript, the guard cannot judge and allows), so the agent puts the clickable link in the message before asking again. On Codex, which has no `AskUserQuestion` tool and asks as a plain-text numbered list, its twin `mockup-link-stop` runs on `Stop`: when the final reply asks for that approval without a link, Codex continues the turn so the agent rewrites the ask with it.
386
498
  - **Issue injection scanner** (`PostToolUse` on `gh` reads): trailhead reads issue/PR/comment text written by anyone with repo access, i.e. untrusted input. When that text contains prompt-injection phrases, the hook injects an advisory reminding the agent to treat it as data, never as commands. Advisory only: it never blocks.
387
499
 
388
- The commit, secret, install, search, and body guards run in **one process**: a single `trailhead-bash-guards.js` registration evaluates them in that order, so a Bash call pays one node start-up instead of five (three hook processes per Bash call in all: the merged hook, the secret-read guard, and the injection scanner). They remain five distinct guards with unchanged verdicts; the first block wins, and a guard that errors allows on its own without stopping the others. A guard whose module is missing or broken is skipped too, but never silently: the hook tells the session which guard did not check the command, so a partial install shows up. On Codex the same hook is registered with `--guards=commit,secret,install,body`, since there is no search guard there.
500
+ The commit, secret, install, search, body, and labels guards run in **one process**: a single `trailhead-bash-guards.js` registration evaluates them in that order, so a Bash call pays one node start-up instead of six (three hook processes per Bash call in all: the merged hook, the secret-read guard, and the injection scanner). They remain six distinct guards with unchanged verdicts; the first block wins, and a guard that errors allows on its own without stopping the others. A guard whose module is missing or broken is skipped too, but never silently: the hook tells the session which guard did not check the command, so a partial install shows up. On Codex the same hook is registered with `--guards=commit,secret,install,body,labels`, since there is no search guard there.
389
501
 
390
502
  The secret, body, and secret-read guards also **fail closed on shell constructs they cannot interpret with certainty** (`eval`, `source` / `.`, a bash 5.3 `${ cmd; }`, `printf -v`, a function definition, an alias, a quoted `gh api` body key, a backslash-newline inside a word): when one can hide or precede a write (or sits next to a secret path), the command is blocked with a "rewrite it in a simple form" message instead of being emulated; the same constructs with no write or secret nearby pass.
391
503
 
392
- All eight are crash-safe (any error → allow) and active whenever the plugin is installed. The commit, secret, install, search, body, and secret-read guards therefore apply in **every** repo, not only trailhead projects: intentional, since conventional commits, no `Co-Authored-By`, no leaked secrets, no unvetted installs, no `cd`-then-relative-read, no clobbered issue bodies, and no readable `.env`/`.secrets` are trailhead's standing rules. To opt out, disable the plugin's hooks in your Claude Code settings.
504
+ Alongside them trailhead installs a git **`commit-msg` hook** (a POSIX `sh` wrapper, so it also works in `"type": "module"` repos) that enforces the same commit rules on every host, plus the `Refs: #<n>` trailer while a session ticket is active. If it cannot run (script missing, no `node`) it prints `trailhead: commit-msg hook inactive (<reason>); commit rules not enforced.` to stderr and lets the commit through; an installed hook is upgraded at the next ticket start.
505
+
506
+ All nine are crash-safe (any error → allow) and active whenever the plugin is installed. The commit, secret, install, search, body, and secret-read guards therefore apply in **every** repo, not only trailhead projects: intentional, since conventional commits, no `Co-Authored-By`, no leaked secrets, no unvetted installs, no `cd`-then-relative-read, no clobbered issue bodies, and no readable `.env`/`.secrets` are trailhead's standing rules. To opt out, disable the plugin's hooks in your Claude Code settings.
393
507
 
394
508
  ---
395
509
 
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env node
2
+ // Docs test for #313: /trailhead:auto is introduced at the entry points and each `auto`
3
+ // section opens with an intro, a run example and the main-checkout note.
4
+ // Plain asserts, no framework. Run: node bin/auto-intro-docs.test.js
5
+ const assert = require('assert');
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ const root = path.resolve(__dirname, '..');
10
+ const read = (rel) => fs.readFileSync(path.join(root, rel), 'utf8');
11
+ let passed = 0;
12
+ const ok = (name, cond) => { assert.ok(cond, name); passed++; };
13
+ const EM = String.fromCharCode(0x2014);
14
+
15
+ // Sezione dal suo heading fino al prossimo heading dello stesso livello (o superiore).
16
+ const section = (text, headingRe) => {
17
+ const m = headingRe.exec(text);
18
+ if (!m) return '';
19
+ const level = m[0].match(/^#+/)[0].length;
20
+ const rest = text.slice(m.index + m[0].length);
21
+ const next = new RegExp(`^#{1,${level}} `, 'm').exec(rest);
22
+ return next ? rest.slice(0, next.index) : rest;
23
+ };
24
+ // Testa della sezione: prima del primo bullet o sotto-heading.
25
+ const head = (sec) => {
26
+ const i = sec.search(/^(- |### )/m);
27
+ return i < 0 ? sec : sec.slice(0, i);
28
+ };
29
+
30
+ const docs = [
31
+ { name: 'README.md', rel: 'README.md', re: /^### .*Autonomous run: `auto`\s*$/m, it: false },
32
+ { name: 'README.it.md', rel: 'README.it.md', re: /^### .*Run autonoma: `auto`\s*$/m, it: true },
33
+ { name: 'workflow.md', rel: 'site/src/content/docs/docs/workflow.md', re: /^## Autonomous run: `auto`\s*$/m, it: false },
34
+ ];
35
+
36
+ for (const d of docs) {
37
+ const text = read(d.rel);
38
+ const sec = section(text, d.re);
39
+ const intro = head(sec);
40
+ ok(`${d.name}: auto section found`, sec.length > 0);
41
+ ok(`${d.name}: intro says it drains a map or the whiteboard unattended`, d.it
42
+ ? /svuota/i.test(intro) && /mappa/i.test(intro) && /whiteboard/i.test(intro) && /senza supervisione/i.test(intro)
43
+ : /drains/i.test(intro) && /map/i.test(intro) && /whiteboard/i.test(intro) && /unattended/i.test(intro));
44
+ ok(`${d.name}: intro contrasts with /trailhead:work`, intro.includes('/trailhead:work'));
45
+ ok(`${d.name}: intro names the run report, needs-you tickets and local commits to push`,
46
+ /run report/i.test(intro) && /needs-you/.test(intro) && /push/i.test(intro));
47
+ const block = /```[^\n]*\n([\s\S]*?)```/.exec(intro + sec.slice(intro.length));
48
+ const ex = block ? block[1] : '';
49
+ ok(`${d.name}: run example has a progress line`, /slot/i.test(ex) && /(resolved|next)/i.test(ex));
50
+ ok(`${d.name}: run example has a report excerpt`, ['Stopped:', 'Resolved:', 'Merged:', 'Remains:'].every((k) => ex.includes(k)));
51
+ ok(`${d.name}: main-checkout note (fast-forward or merge commit, dirty tracked tree stops, untracked does not)`, d.it
52
+ ? /fast-forward/.test(sec) && /merge commit/.test(sec) && /checkout principale/i.test(sec) && /non committat/i.test(sec) && /untracked/i.test(sec)
53
+ : /fast-forward/.test(sec) && /merge commit/.test(sec) && /main checkout/i.test(sec) && /uncommitted/i.test(sec) && /untracked/i.test(sec));
54
+ ok(`${d.name}: no em-dash`, !text.includes(EM));
55
+ }
56
+
57
+ for (const [name, rel] of [['README.md', 'README.md'], ['README.it.md', 'README.it.md']]) {
58
+ const t = read(rel);
59
+ const pitch = t.slice(0, t.search(/^## .*Install/m));
60
+ ok(`${name}: the pitch names /trailhead:auto`, pitch.includes('/trailhead:auto'));
61
+ }
62
+
63
+ const landing = read('site/src/components/landing/HowItWorks.astro');
64
+ ok('landing: mentions /trailhead:auto', /<code>\/trailhead:auto<\/code>/.test(landing));
65
+ const sentence = (landing.match(/[^.>]*<code>\/trailhead:auto<\/code>[^.<]*/) || [''])[0];
66
+ ok('landing: the new sentence has no em-dash', sentence.length > 0 && !sentence.includes(EM));
67
+
68
+ const gs = read('site/src/content/docs/docs/getting-started.mdx');
69
+ ok('getting-started: links the autonomous run section', gs.includes('(/docs/workflow#autonomous-run-auto)'));
70
+ ok('getting-started: no em-dash', !gs.includes(EM));
71
+
72
+ console.log(`auto-intro-docs.test.js: ${passed} checks passed`);
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ // Docs test for #286: under `git: main` the autonomous run merges a verified slot branch
3
+ // into the local trunk and unblocks its dependents. Plain asserts, no framework.
4
+ // Run: node bin/auto-merge-docs.test.js
5
+ const assert = require('assert');
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ const plugin = path.resolve(__dirname, '..', 'plugins', 'trailhead');
10
+ const auto = fs.readFileSync(path.join(plugin, 'skills/trailhead-work/references/auto.md'), 'utf8');
11
+ let passed = 0;
12
+ const ok = (name, cond) => { assert.ok(cond, name); passed++; };
13
+
14
+ ok('auto.md: integration under git: main replaces the held rule', auto.includes('**Integration under `git: main`.**') && !auto.includes('**Integration is held.**'));
15
+ ok('auto.md: the stale-base preflight runs before the merge', /Resolve stale-base preflight[\s\S]{0,200}main checkout `--sync`, then the ticket branch `--sync`[\s\S]{0,80}re-review/.test(auto));
16
+ ok('auto.md: names the merge helper call', auto.includes('trailhead-auto-merge.js merge --repo <main-checkout> --ticket <n>'));
17
+ ok('auto.md: merges are one at a time, never a push', /one merge at a time/.test(auto) && /never a push/.test(auto));
18
+ ok('auto.md: merged / already-merged resolves, unblocks, removes, frees the slot', /`merged` or `already-merged`[^\n]*resolution comment[^\n]*close the ticket[^\n]*Decisions so far/.test(auto) && /trailhead-auto-merge\.js remove --repo <main-checkout> --ticket <n>/.test(auto) && /free the slot/.test(auto));
19
+ ok('auto.md: unblocks dependents with the helper', auto.includes('trailhead-unblock.js --issue <n> --repo <owner/name>'));
20
+ ok('auto.md: dependents open their slot at the new trunk SHA', /open their slot at the new trunk SHA/.test(auto));
21
+ ok('auto.md: a conflict stays separate, is parked and recorded, dependents stay blocked', /`conflict`[^\n]*stays \*\*separate\*\*/.test(auto) && /conflicting files/.test(auto) && /unmerged list/.test(auto) && /dependents stay blocked/.test(auto));
22
+ ok('auto.md: the other statuses park needs-you naming the status', /`dirty`, `not-on-trunk`, `slot-dirty`, `in-progress` and `merge-failed`: park the ticket needs-you/.test(auto) && !/`not-merged`: park/.test(auto));
23
+ ok('auto.md: a foreign marker never blocks the merge', !/marker-mismatch/.test(auto) && /never blocks it[^)]*--no-verify[^)]*only when the marker names another ticket/.test(auto));
24
+ ok('auto.md: error hands back at once with no further merge', /`error`: hand back at once, with no further merge/.test(auto));
25
+ ok('auto.md: other slots are never rebased mid-step', /Other in-flight slots are never rebased mid-step/.test(auto) && /strictly ahead of `origin`/.test(auto));
26
+ ok('auto.md: a remove failure is a cleanup error, not a park', /`remove` failure after the close is a cleanup error, not a park/.test(auto) && /other than `removed` \(`error`, `not-merged`, `kept`\)[^.]*never park or reopen it/.test(auto) && /free the slot, list the workspace and the branch in the run-end summary/.test(auto));
27
+ ok('auto.md: git: pr and clone slots stay held', /Integration stays held under `git: pr`, and for a clone slot/.test(auto) && /gitlink is a rail action|publishing a gitlink is a rail action/.test(auto));
28
+ ok('auto.md: a local merge is not rail-tripping, the push stays the human\'s', /local merge of a verified slot branch into the trunk is not rail-tripping/.test(auto) && /the push stays the human's/.test(auto));
29
+ ok('auto.md: resume runs recover first, aborted retried, foreign handed back', /trailhead-auto-merge\.js recover --repo <main-checkout>/.test(auto) && /`aborted`[^.]*merge is retried/.test(auto) && /`foreign`[^.]*hands the run back without touching it/.test(auto));
30
+ ok('auto.md: a resolve-step slot ticket is re-driven idempotently', /work state step is `resolve`\*\* is re-driven through Resolve idempotently/.test(auto) && /already-merged/.test(auto) && /resolution comment is posted only if none exists/.test(auto));
31
+ ok('auto.md: run-end summary names merged, unmerged with files, trunk ahead of origin', /\*\*what merged\*\*/.test(auto) && /left unmerged/.test(auto) && /ahead of `origin` by N commits/.test(auto));
32
+ ok('auto.md: no em-dash', !auto.includes(String.fromCharCode(0x2014)));
33
+ console.log(`auto-merge-docs.test.js: ${passed} checks passed`);
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+ // Docs test for #287: an autonomous run opens, or updates, one merge ticket per map (or
3
+ // the whiteboard) for the slot branches it leaves unmerged, right when each arises.
4
+ // Plain asserts, no framework. Run: node bin/auto-merge-ticket-docs.test.js
5
+ const assert = require('assert');
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ const plugin = path.resolve(__dirname, '..', 'plugins', 'trailhead');
10
+ const auto = fs.readFileSync(path.join(plugin, 'skills/trailhead-work/references/auto.md'), 'utf8');
11
+ const commands = fs.readFileSync(path.join(plugin, 'skills/_shared/substrate-commands.md'), 'utf8');
12
+ let passed = 0;
13
+ const ok = (name, cond) => { assert.ok(cond, name); passed++; };
14
+
15
+ ok('auto.md: names the add call with scope, ticket, file, workspace and repo', auto.includes('trailhead-merge-ticket.js add --scope <map-<n>|whiteboard> --ticket <n> --file <path> [--file <path> ...] --workspace <path> --repo <owner/name>'));
16
+ ok('auto.md: one --file per conflicting path of the merge result', /one `--file` per path in the merge result's `conflicts`/.test(auto));
17
+ ok('auto.md: the add runs right after the abort, before parking', /right after the abort, before parking/i.test(auto));
18
+ ok('auto.md: the park reason names the branch, workspace, files and the merge ticket', /park reason names the branch, the workspace, the conflicting files and the merge ticket/.test(auto));
19
+ ok('auto.md: one merge ticket per map or the whiteboard, opened at the first unmerged branch, updated after each later one', /one merge ticket per map \(or the whiteboard\)[^\n]*opened at the first unmerged branch[^\n]*updated after each later one/.test(auto));
20
+ ok('auto.md: never only at run end', /never only at run end/.test(auto));
21
+ ok('auto.md: a needs-you task so no run takes it, closed by the human', /needs-you `task`[^\n]*no run takes it[^\n]*closed by the human/.test(auto));
22
+ ok('auto.md: a map or the whiteboard with no unmerged branch gets no merge ticket', /no unmerged branch gets no merge ticket/.test(auto));
23
+ ok('auto.md: records as it goes names the merge ticket', /Records as it goes\n[^\n]*merge ticket/.test(auto));
24
+ ok('auto.md: run-end summary names the merge tickets', /\*\*what merged\*\*[^\n]*merge tickets/.test(auto));
25
+ ok('auto.md: a helper error is reported, named in the park reason, retried once at run end', /helper error[^\n]*reported in chat[^\n]*named in the park reason[^\n]*retried once at run end/.test(auto));
26
+ ok('auto.md: a still-failing retry is listed as no merge ticket written', /no merge ticket written/.test(auto));
27
+ ok('substrate-commands.md: names the helper and its call', commands.includes('trailhead-merge-ticket.js add --scope') && commands.includes('--workspace <path> --repo <owner/name>'));
28
+ ok('substrate-commands.md: finds through GraphQL OPEN with the marker', /trailhead:merge-ticket/.test(commands) && /states OPEN|OPEN issues/.test(commands));
29
+ ok('substrate-commands.md: an error is unknown, never none found', /merge ticket[^\n]*unknown, never as none found/.test(commands));
30
+ ok('no em-dash in auto.md', !auto.includes(String.fromCharCode(0x2014)));
31
+ ok('no em-dash in substrate-commands.md', !commands.includes(String.fromCharCode(0x2014)));
32
+ console.log(`auto-merge-ticket-docs.test.js: ${passed} checks passed`);