@marcomigozzi/trailhead 0.12.1 → 0.14.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 (66) hide show
  1. package/README.it.md +38 -13
  2. package/README.md +48 -13
  3. package/bin/lib/codex-projection.js +18 -11
  4. package/bin/lib/codex-projection.test.js +39 -11
  5. package/bin/lib/install-verify.js +10 -5
  6. package/bin/lib/install-verify.test.js +51 -0
  7. package/bin/trailhead.js +37 -25
  8. package/bin/trailhead.test.js +162 -5
  9. package/package.json +4 -1
  10. package/plugins/trailhead/.claude-plugin/plugin.json +3 -2
  11. package/plugins/trailhead/commands/panel.md +10 -0
  12. package/plugins/trailhead/hooks/hooks.json +3 -18
  13. package/plugins/trailhead/hooks/lib/body-sources.js +795 -0
  14. package/plugins/trailhead/hooks/lib/gh-subcommand.js +92 -10
  15. package/plugins/trailhead/hooks/lib/gh-subcommand.test.js +55 -0
  16. package/plugins/trailhead/hooks/lib/mockup-link.js +21 -5
  17. package/plugins/trailhead/hooks/lib/shell-scan.js +496 -64
  18. package/plugins/trailhead/hooks/lib/shell-scan.test.js +420 -0
  19. package/plugins/trailhead/hooks/lib/unmodelled.js +345 -0
  20. package/plugins/trailhead/hooks/lib/unmodelled.test.js +173 -0
  21. package/plugins/trailhead/hooks/lib/work-state.test.js +252 -0
  22. package/plugins/trailhead/hooks/mod/README.md +187 -0
  23. package/plugins/trailhead/hooks/mod/map-pane-view.ts +453 -0
  24. package/plugins/trailhead/hooks/mod/map-pane.tsx +663 -0
  25. package/plugins/trailhead/hooks/mod/register.tsx +280 -0
  26. package/plugins/trailhead/hooks/mod/work-state-prompt.ts +69 -0
  27. package/plugins/trailhead/hooks/mod/work-state-view.ts +93 -0
  28. package/plugins/trailhead/hooks/trailhead-bash-guards.js +140 -0
  29. package/plugins/trailhead/hooks/trailhead-bash-guards.test.js +233 -0
  30. package/plugins/trailhead/hooks/trailhead-body-guard.js +145 -61
  31. package/plugins/trailhead/hooks/trailhead-body-guard.test.js +224 -3
  32. package/plugins/trailhead/hooks/trailhead-commit-guard.js +41 -14
  33. package/plugins/trailhead/hooks/trailhead-commit-guard.test.js +2 -1
  34. package/plugins/trailhead/hooks/trailhead-install-guard.js +22 -12
  35. package/plugins/trailhead/hooks/trailhead-install-guard.test.js +2 -1
  36. package/plugins/trailhead/hooks/trailhead-mockup-link-guard.js +48 -11
  37. package/plugins/trailhead/hooks/trailhead-mockup-link-guard.test.js +79 -7
  38. package/plugins/trailhead/hooks/trailhead-mockup-link-stop.test.js +1 -1
  39. package/plugins/trailhead/hooks/trailhead-search-guard.js +19 -9
  40. package/plugins/trailhead/hooks/trailhead-search-guard.test.js +2 -1
  41. package/plugins/trailhead/hooks/trailhead-secret-guard.js +541 -57
  42. package/plugins/trailhead/hooks/trailhead-secret-guard.test.js +1000 -4
  43. package/plugins/trailhead/hooks/trailhead-secret-read-guard.js +171 -6
  44. package/plugins/trailhead/hooks/trailhead-secret-read-guard.test.js +170 -0
  45. package/plugins/trailhead/skills/_shared/choices.md +3 -1
  46. package/plugins/trailhead/skills/_shared/configuration-reference.md +5 -4
  47. package/plugins/trailhead/skills/_shared/configuration.md +1 -1
  48. package/plugins/trailhead/skills/_shared/multi-map.md +1 -1
  49. package/plugins/trailhead/skills/_shared/session-handoff.md +1 -1
  50. package/plugins/trailhead/skills/_shared/teamwork.md +4 -2
  51. package/plugins/trailhead/skills/_shared/techniques/acceptance-testing.md +1 -1
  52. package/plugins/trailhead/skills/_shared/techniques/prototype.md +15 -16
  53. package/plugins/trailhead/skills/_shared/techniques.md +1 -1
  54. package/plugins/trailhead/skills/trailhead/SKILL.md +2 -2
  55. package/plugins/trailhead/skills/trailhead-work/SKILL.md +3 -2
  56. package/plugins/trailhead/skills/trailhead-work/references/auto.md +2 -0
  57. package/plugins/trailhead/skills/trailhead-work/references/ticket-engines.md +3 -1
  58. package/plugins/trailhead/templates/trailhead-work-state.js +199 -0
  59. package/plugins/trailhead/tests/map-pane-ui.test.ts +1374 -0
  60. package/plugins/trailhead/tests/map-pane-view.test.ts +565 -0
  61. package/plugins/trailhead/tests/mod.test.ts +14 -0
  62. package/plugins/trailhead/tests/work-state-prompt-hook.test.ts +207 -0
  63. package/plugins/trailhead/tests/work-state-prompt.test.ts +109 -0
  64. package/plugins/trailhead/tests/work-state-ui.test.ts +365 -0
  65. package/plugins/trailhead/tests/work-state-view.test.ts +86 -0
  66. package/plugins/trailhead/types/index.d.ts +69 -0
package/README.it.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  🇬🇧 [English](README.md) · 🇮🇹 **Italiano**
8
8
 
9
- [![sito](https://img.shields.io/badge/sito-trailhead.marcomigozzi.it-3C7A5A?style=flat-square)](https://trailhead.marcomigozzi.it) [![CI sito](https://img.shields.io/github/actions/workflow/status/ToRvaLDz/trailhead/site-ci.yml?branch=main&style=flat-square&label=CI%20sito)](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) ![host](https://img.shields.io/badge/host-Claude%20Code%20%C2%B7%20Codex-1E2A54?style=flat-square) ![comandi](https://img.shields.io/badge/comandi-23%20verbi-0e8a16?style=flat-square) ![guardrail](https://img.shields.io/badge/guardrail-7%20hook-e8710a?style=flat-square)
9
+ [![sito](https://img.shields.io/badge/sito-trailhead.marcomigozzi.it-3C7A5A?style=flat-square)](https://trailhead.marcomigozzi.it) [![CI sito](https://img.shields.io/github/actions/workflow/status/ToRvaLDz/trailhead/site-ci.yml?branch=main&style=flat-square&label=CI%20sito)](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) ![host](https://img.shields.io/badge/host-Claude%20Code%20%C2%B7%20Codex-1E2A54?style=flat-square) ![comandi](https://img.shields.io/badge/comandi-24%20verbi-0e8a16?style=flat-square) ![guardrail](https://img.shields.io/badge/guardrail-7%20hook-e8710a?style=flat-square)
10
10
 
11
11
  </div>
12
12
 
@@ -26,7 +26,7 @@ Due approcci al lavoro di progetto guidato dagli agenti risolvono ciascuno una m
26
26
 
27
27
  Lavorando su entrambi, uno schema continuava a ripresentarsi: **usa una mappa e un onboarding in stile Wayfinder per decidere la forma del lavoro, poi un motore in stile GSD per costruire ogni pezzo, senza mai lasciare l'issue tracker.** `trailhead` è quello schema trasformato in un'unica skill, una sua interpretazione autonoma di entrambe le idee anziché un wrapper attorno all'una o all'altra.
28
28
 
29
- > **trailhead è self-contained** nel senso che ha **zero dipendenze da plugin esterni**: gli agent di trailhead e i built-in dell'host vanno bene, e nessun'altra skill o plugin è richiesta. Wayfinder e GSD sono *ispirazione*, non dipendenze. Ogni tecnica che quei sistemi impacchettano come skill separata (grilling, TDD, debugging sistematico, codebase mapping, code review) è **integrata qui come protocollo inline**. Il core di trailhead non invoca nessun'altra skill; ogni tecnica del motore dispatcha al **proprio agent `trailhead-*`** (`trailhead-executor`, `trailhead-plan`, `trailhead-research`, `trailhead-code-review`, `trailhead-debug`, più `trailhead-fix` e `trailhead-codebase-map`), scritto una volta come file `agents/trailhead-*.md` versionato e **proiettato su ogni host** (su Codex diventa `~/.codex/agents/trailhead-*.toml` generato dalla stessa sorgente). Questi agent sono **thin** (identità + tool policy + un modello guidato dalla config, con il protocollo della tecnica iniettato allo spawn) e girano sul toolkit di subagent nativo dell'host (il tool `Agent` integrato di Claude Code, oppure il toolkit `multi_agent` di Codex). L'unica cosa da installare è trailhead stesso. C'è un'unica eccezione opt-in: la modalità mockup hosted. Quando abilitata, `claude.ai/design` costruisce il mockup su claude.ai/design (solo Claude): per default una canvas (la skill integrata `/design` pubblicata come Artifact, o un progetto creato a mano nella web app, non guidato da MCP) o, per una libreria di componenti condivisa, un progetto design-system tramite DesignSync (l'MCP `claude-design` più la skill `/design-sync`, l'unica superficie guidata da MCP). Una terza modalità, `stitch`, usa l'MCP Google Stitch e funziona sia su **Claude Code sia su Codex**, quindi è l'opzione hosted cross-host. Se l'MCP della modalità configurata non è connesso, offre un'installazione una tantum e ripiega sui mockup su disco locale se rifiuti; su Codex, dove `claude.ai/design` non è disponibile, offre di passare a `stitch` prima di ripiegare sul disco locale.
29
+ > **trailhead è self-contained** nel senso che ha **zero dipendenze da plugin esterni**: gli agent di trailhead e i built-in dell'host vanno bene, e nessun'altra skill o plugin è richiesta. Wayfinder e GSD sono *ispirazione*, non dipendenze. Ogni tecnica che quei sistemi impacchettano come skill separata (grilling, TDD, debugging sistematico, codebase mapping, code review) è **integrata qui come protocollo inline**. Il core di trailhead non invoca nessun'altra skill; ogni tecnica del motore dispatcha al **proprio agent `trailhead-*`** (`trailhead-executor`, `trailhead-plan`, `trailhead-research`, `trailhead-code-review`, `trailhead-debug`, più `trailhead-fix` e `trailhead-codebase-map`), scritto una volta come file `agents/trailhead-*.md` versionato e **proiettato su ogni host** (su Codex diventa `~/.codex/agents/trailhead-*.toml` generato dalla stessa sorgente). Questi agent sono **thin** (identità + tool policy + un modello guidato dalla config, con il protocollo della tecnica iniettato allo spawn) e girano sul toolkit di subagent nativo dell'host (il tool `Agent` integrato di Claude Code, oppure il toolkit `multi_agent` di Codex). L'unica cosa da installare è trailhead stesso. C'è un'unica eccezione opt-in: la modalità mockup hosted. Quando abilitata, `artifacts` costruisce il mockup come canvas Design **Artifact** su claude.ai tramite il tool `Artifact` integrato in Claude Code (solo Claude Code, niente da installare), con il tuo design system installato sul canvas. Una seconda modalità hosted, `stitch`, usa l'MCP Google Stitch e funziona sia su **Claude Code sia su Codex**, quindi è l'opzione cross-host; se il suo MCP non è connesso, offre un'installazione una tantum e ripiega sui mockup su disco locale se rifiuti. Dove il tool `Artifact` non c'è (Codex, sessioni con API key), offre di passare a `stitch` prima di ripiegare sul disco locale.
30
30
 
31
31
  Le scelte di design che ne derivano:
32
32
 
@@ -70,7 +70,7 @@ npx @marcomigozzi/trailhead --dir=<path> # punta a una config dir specifica
70
70
  ```
71
71
  Su **Claude Code** copia la skill (+ le sue `references/`), i comandi `/trailhead:*`, gli hook (in `hooks/`, registrati in `settings.json`) incluso un **update check** al SessionStart, e i template di label-guard + statusline, in modo idempotente.
72
72
 
73
- Su **Codex CLI** (`--codex`) proietta una skill nativa Codex invocata come **`$trailhead`** (`$trailhead work`, `$trailhead new "idea"`, …), una skill sottile **`$trailhead-<verb>`** per ogni verbo per la discoverability, le sei guardrail come **hook** nativi di Codex (`~/.codex/hooks.json`, abilitando `features.hooks`; la search guard è solo per Claude Code e non è proiettata su Codex), e i pin di modello per-tecnica da **`models.codex.*`** in `~/.codex/agents/`. Serve Codex **0.145.0+** (l'installer fa un gate su `codex --version`). La modalità mockup `claude.ai/design` non è disponibile su Codex; lì l'opzione hosted è la modalità cross-host `design: stitch` (MCP Google Stitch), con il disco locale come ripiego.
73
+ Su **Codex CLI** (`--codex`) proietta una skill nativa Codex invocata come **`$trailhead`** (`$trailhead work`, `$trailhead new "idea"`, …), una skill sottile **`$trailhead-<verb>`** per ogni verbo per la discoverability, le sei guardrail come **hook** nativi di Codex (`~/.codex/hooks.json`, abilitando `features.hooks`; i quattro guard Bash condividono un solo processo dietro una singola registrazione; la search guard è solo per Claude Code e non è proiettata su Codex), e i pin di modello per-tecnica da **`models.codex.*`** in `~/.codex/agents/`. Serve Codex **0.145.0+** (l'installer fa un gate su `codex --version`). La modalità mockup `artifacts` (il tool `Artifact` di Claude Code) non è disponibile su Codex; lì l'opzione hosted è la modalità cross-host `design: stitch` (MCP Google Stitch), con il disco locale come ripiego.
74
74
 
75
75
  Ri-esegui l'install per aggiornare, oppure esegui **`/trailhead:update`** (Claude) / **`$trailhead update`** (Codex) da dentro l'agente: rileva come trailhead è stato installato e installa la versione più recente dove è sicuro farlo (un `git pull` per un dev-symlink, una ri-esecuzione `npx` per npm, o `/plugin update` per il plugin). Quando esiste una versione più recente, la statusline mostra un flag `⬆ trailhead <version>` (Claude).
76
76
 
@@ -130,7 +130,7 @@ Frontier iniziale = ①③④, tre persone possono partire in parallelo. Poi:
130
130
  - **① research** → un subagent legge la documentazione, restituisce un finding pronto per la decisione, chiude → sblocca ②.
131
131
  - **② decision** → dirama le opzioni, grill per scegliere native vs custom, registra il perché.
132
132
  - **③ task** → l'agente ti consegna una checklist (servono i *tuoi* account Google/GitHub); tu registri le app, lui registra dove vivono le credenziali.
133
- - **④ prototype** → una schermata di login grezza (sul progetto claude.ai/design configurato), approvata prima del codice UI.
133
+ - **④ prototype** → una schermata di login grezza (sul canvas Artifacts configurato), approvata prima del codice UI.
134
134
  - Con ②③④ chiusi, **⑤ build** si diploma → discuss → plan → execute (commit atomici, TDD ai seam dell'auth) → verify (test + code review + acceptance: chiesto chi guida, l'agente va AI-driven e guida il browser attraverso il vero flusso di login).
135
135
  - Dopo il rilascio noti un redirect sbagliato in prod → `/trailhead:bug --of ⑤ "GitHub redirect goes to localhost"` → un nuovo ticket che porta `Regression of: ⑤`, lavorato repro → diagnose → fix → verify. ⑤ resta chiuso.
136
136
 
@@ -185,6 +185,27 @@ Ogni verbo è anche un comando con namespace (`/trailhead:new`, `/trailhead:work
185
185
  | `/trailhead:grill [topic]` | esegue una sessione di grilling autonoma su una decision/topic |
186
186
  | `/trailhead:map` | mostra la mappa a bassa risoluzione (destination, decisions, frontier, fog) |
187
187
  | `/trailhead:dashboard` | mostra la dashboard del repo: l'indice pinnato di ogni mappa aperta, la whiteboard, e i conteggi live |
188
+ | `/trailhead:panel` | apre o chiude il pannello live della mappa: i ticket aperti della mappa attiva per stato, con un pulsante `work` per ogni ticket della frontier (**solo Claude Code**: richiede la mod di trailhead, 2.1.287+) |
189
+
190
+ ### 🖥️ A schermo in Claude Code
191
+
192
+ La mod di trailhead (Claude Code 2.1.287+) è inclusa nel plugin e disegna il lavoro accanto alla conversazione. Codex mantiene gli hook dei comandi e non ha un pannello.
193
+
194
+ ![Il pannello della mappa affiancato al transcript: mappe aperte, frontier con un pulsante work per ticket, in corso, bloccati e un footer con refresh e close](assets/screenshots/map-panel.png)
195
+
196
+ Il pannello della mappa elenca le mappe aperte da consultare, la frontier con un pulsante `work` per ogni ticket che pulisce il contesto e avvia il ticket, chi tiene cosa e cosa è bloccato. Il footer ha `refresh` e `close`. `/trailhead:panel` lo apre e lo chiude, e si apre da solo una volta in fullscreen.
197
+
198
+ ![Il pannello che mostra un'altra mappa, con l'intestazione viewing e active e il pulsante back to active](assets/screenshots/map-panel-viewing.png)
199
+
200
+ Un click mostra soltanto una mappa, e `back to active` torna a quella attiva. La mappa attiva cambia quando lavori uno dei suoi ticket.
201
+
202
+ ![La sezione Burn-down del pannello: riga dei conteggi e grafico dei ticket aperti nel tempo](assets/screenshots/burndown.png)
203
+
204
+ Il burn-down mostra i ticket aperti nel tempo, dal primo ticket della mappa a oggi, con chiusi e rimossi contati a parte.
205
+
206
+ ![La pipeline band sopra il prompt e la voce di stato trailhead sotto](assets/screenshots/work-state-band.png)
207
+
208
+ La pipeline band sopra il prompt e la voce di stato sotto mostrano il ticket in gioco e il suo step.
188
209
 
189
210
  ### Capture: zero attrito, una riga di conferma, non risolve nulla
190
211
 
@@ -229,7 +250,7 @@ Ogni tipo ha il proprio motore inline: nessuna skill esterna viene invocata.
229
250
  |---|---|---|---|
230
251
  | 🧭 `decision` | una scelta | HITL | diverge le opzioni se non chiare, poi grill per convergere su una |
231
252
  | 🔬 `research` | un fatto | AFK | un subagent su un branch usa-e-getta (l'unico tipo eseguito in parallelo) |
232
- | 🎨 `prototype` | una direzione approvata | HITL | un artefatto grezzo usa-e-getta a cui reagire; le schermate UI passano da qui (disco, o un progetto claude.ai/design configurato) prima del codice UI |
253
+ | 🎨 `prototype` | una direzione approvata | HITL | un artefatto grezzo usa-e-getta a cui reagire; le schermate UI passano da qui (disco, un canvas Artifacts, o Stitch) prima del codice UI |
233
254
  | 🔨 `build` | codice funzionante | HITL/AFK | `discuss → plan → execute → verify`: commit atomici, TDD ai seam, **prima un mockup per la UI rivolta all'utente** (tecnica Prototype, gated da `design.approval`), code review + acceptance testing (chiede chi guida, AI o umano, poi browser-drive o UAT conversazionale passo-passo) |
234
255
  | 🐛 `bug` | codice corretto | HITL/AFK | `repro → diagnose → fix → verify`; un difetto in lavoro chiuso è un ticket *nuovo* (`Regression of:`), non una riapertura |
235
256
  | 🔧 `task` | un cambiamento di stato esterno | HITL/AFK | lavoro manuale che sblocca una decision (provisioning accessi, spostamento dati, iscrizioni) |
@@ -304,8 +325,9 @@ Il passo **📊 statusline** offre di installare la status bar di trailhead per
304
325
  |---|---|---|
305
326
  | `ticket.language` | un codice ISO 639-1 (**`en`**) | la lingua in cui trailhead **scrive** la sua prosa GitHub e le descrizioni dei commit, disaccoppiata dalla lingua in cui conversa |
306
327
  | `models.{plan,execute,research,review,debug,verify,codebase-map}` | un id modello completo **versionato** (**inherit session**) | quale modello esegue ogni attività; `plan` ed `execute` sono sempre impostati separatamente |
307
- | `design` | **`disk`** \| `claude.ai/design` \| `stitch` | dove vanno i mockup UI: HTML usa-e-getta locale, claude.ai/design via l'MCP ufficiale Claude Design di Anthropic, o Google Stitch (cross-host, via l'MCP Stitch) |
308
- | `design.surface` | **`canvas`** \| `design-system` | sotto `claude.ai/design` o `stitch`, quale superficie: un progetto canvas (default) o un progetto design-system via `/design-sync` |
328
+ | `design` | **`disk`** \| `artifacts` \| `stitch` | dove vanno i mockup UI: HTML usa-e-getta locale, un canvas Design **Artifact** su claude.ai (solo Claude Code), o Google Stitch (cross-host, via l'MCP Stitch). Il vecchio valore `claude.ai/design` viene letto come `artifacts` |
329
+ | `design.surface` | **`canvas`** \| `design-system` | solo `stitch`: un progetto Stitch di schermate (default) o un design system Stitch condiviso |
330
+ | `design.system` | URL di un Design System Artifact | solo `artifacts`: il design system installato sul canvas dei mockup (chiesto alla prima schermata se non impostato) |
309
331
  | `design.approval` | **`explicit`** \| `auto` | attendi l'approvazione del mockup prima del codice UI, o procedi senza bloccare |
310
332
  | `tdd` | **`seams`** \| `on` \| `off` | come il motore `build` testa |
311
333
  | `acceptance.browser` | **`auto`** \| `on` \| `off` | guida il browser in Verify (dopo aver chiesto chi guida, AI o umano); `off` esegue comunque controlli AI-driven via comandi, e ti accompagna in una UAT conversazionale per ciò che solo tu puoi fare (passo-passo in chat, non una checklist da fare da solo) |
@@ -330,14 +352,13 @@ Esempio di file di config (la stessa forma funziona per il `.trailhead/config.js
330
352
  { "ticket": { "language": "en" }, "models": { "plan": "claude-opus-5-5", "execute": "claude-sonnet-5-5" }, "tdd": "seams", "acceptance": { "browser": "auto" } }
331
353
  ```
332
354
 
333
- **Design mockups.** `design: disk` (il default) deposita un mockup HTML statico usa-e-getta accanto al codice e lo collega dal ticket. `design: claude.ai/design` invece costruisce il mockup su claude.ai/design (canvas tramite la skill integrata `/design` o un progetto creato a mano nella web app; design-system tramite l'**MCP ufficiale Claude Design** di Anthropic, `claude mcp add --scope user --transport http claude-design https://api.anthropic.com/v1/design/mcp`, accedi con `/design-login`), dove lo affini visivamente, e `design: stitch` usa l'**MCP Google Stitch** (`claude mcp add --scope user --transport http stitch https://stitch.googleapis.com/mcp`), il generatore UI di Google basato su Gemini che gira sia su **Claude Code sia su Codex** (l'opzione hosted cross-host). Entrambe onorano `design.surface`:
355
+ **Design mockups.** `design: disk` (il default) deposita un mockup HTML statico usa-e-getta accanto al codice e lo collega dal ticket. `design: artifacts` costruisce il mockup come **canvas Design Artifact** su claude.ai, tramite il tool `Artifact` integrato in Claude Code (niente da installare), dove lo affini visivamente, lo commenti e lo esporti; `design: stitch` usa l'**MCP Google Stitch** (`claude mcp add --scope user --transport http stitch https://stitch.googleapis.com/mcp`), il generatore UI di Google basato su Gemini che gira sia su **Claude Code sia su Codex** (l'opzione hosted cross-host).
334
356
 
335
- - **`canvas`** (default): una schermata singola a cui reagire, modificabile ed esportabile come prototipo live. Sotto `claude.ai/design` è la skill integrata `/design` (pubblicata come Artifact) o un progetto claude.ai/design creato a mano nella web app; sotto `stitch` è un progetto Stitch.
336
- - **`design-system`**: un progetto design-system guidato da **DesignSync** (la skill `/design-sync` più l'MCP `claude-design`), per una libreria di componenti condivisa tenuta in sync.
357
+ Con **`artifacts`**, alla prima schermata UI trailhead aggancia un canvas: **sceglierne uno esistente**, **incollare un URL**, oppure **crearne uno nuovo** (ti chiede il nome), e ne mette in cache l'URL in `design.project`. Ogni schermata (e ogni variante) è una propria tavola, raggruppata in una pagina del canvas per ticket; il design system in `design.system` (quello predefinito del tuo account, o quello che scegli) viene installato sul canvas, così colori, tipografia e componenti sono a portata di mano. I messaggi di approvazione riportano l'URL del canvas più il nome di ogni tavola. Una volta che approvi, trailhead **rilegge** le tavole (nel caso tu le abbia modificate live) prima di scrivere qualsiasi codice UI.
337
358
 
338
- Alla prima schermata UI, trailhead si aggancia a una superficie: per un progetto **Stitch** o un **design-system claude.ai/design** può **sceglierne uno esistente**, **incollare un URL**, oppure **crearne uno nuovo** (ti chiede il nome); per una **canvas claude.ai/design** non esiste la creazione via MCP, quindi usa la skill `/design` (un **Artifact**) o un progetto web-app di cui incolli l'URL `/p/<id>`. Fa il caching dell'id/URL scelto in `design.project`; un progetto già in cache da prima mantiene la superficie `design-system` a meno che tu non imposti `design.surface`, così i setup esistenti non cambiano. Ogni schermata viene aggiunta lì, e la sua URL è collegata dal ticket. Una volta che approvi, trailhead **ri-fetcha** il design corrente (nel caso tu l'abbia modificato live) prima di scrivere qualsiasi codice UI.
359
+ Con **`stitch`**, `design.surface` sceglie un progetto Stitch di schermate (`canvas`, default) o un design system Stitch condiviso (`design-system`); trailhead lo sceglie, incolla o crea allo stesso modo, e offre una volta di installare l'MCP Stitch se non è connesso (se rifiuti, ripiega sul disco locale).
339
360
 
340
- Se l'MCP della modalità hosted non è connesso, trailhead **offre di installarlo una volta** (`claude mcp add ...`); se rifiuti, ripiega sul disco locale. (La superficie **canvas** di `claude.ai/design` non richiede MCP, quindi per lei non viene offerto nulla; solo la superficie **design-system** ha l'offerta di installazione.) Su Codex `claude.ai/design` non è disponibile, quindi trailhead offre di passare a `stitch` (cross-host) prima di ripiegare sul disco locale. `design.approval` decide se il build attende il tuo esplicito via libera (`explicit`), o procede subito dopo aver mostrato il mockup (`auto`); non tocca l'offerta di installazione una tantum, che compare in entrambi i casi.
361
+ **Passare da claude.ai/design.** Il sito standalone di Claude Design chiude il 14/12/2026 e i suoi design ora vivono in Claude come Artifacts. Una config che dice ancora `design: claude.ai/design` continua a funzionare come `artifacts` (trailhead te lo dice una volta e offre di riscrivere la chiave), ma un progetto claude.ai/design in cache non è riutilizzabile: migra i design system dal banner della pagina Artifacts e lascia che trailhead agganci un nuovo canvas. Dove il tool `Artifact` non c'è (Codex, o una sessione con API key), trailhead offre di passare a `stitch` prima di ripiegare sul disco locale. `design.approval` decide se il build attende il tuo esplicito via libera (`explicit`), o procede subito dopo aver mostrato il mockup (`auto`).
341
362
 
342
363
  ---
343
364
 
@@ -351,9 +372,13 @@ Oltre alle istruzioni della skill, trailhead include otto hook propri (in `hooks
351
372
  - **Search guard** (`PreToolUse` su `Bash`): un backstop strutturale per la regola di igiene dei comandi di ricerca (mai `cd` seguito da una lettura/ricerca su percorso relativo). Blocca in modo netto un singolo comando Bash che sia insieme un cambio di directory (`cd`/`pushd`/`env -C`) e, in un'istruzione unita o successiva, una lettura/ricerca su percorso relativo (`grep`/`rg`/`ag`/`cat`/`head`/`tail`/`wc`/`nl`/`sed`/`awk`/`less`/`more`, o `git grep`/`log`/`show`/`diff` senza `-C`), così la forma che fa scattare una regola di deny su `Read()` sotto bypass permissions (ripiegando su un prompt di approvazione manuale invece di girare headless) non viene mai eseguita, anziché essere solo scoraggiata nella prosa dell'agente. Solo Claude Code; non è proiettata su Codex, che non ha una regola di deny su `Read()`.
352
373
  - **Body guard** (`PreToolUse` sulle scritture `gh` di body issue/PR): blocca in modo netto una scrittura `gh issue edit` / `gh pr edit` / `gh api` che imposterebbe il body di un issue a vuoto o solo spazi, così un read-modify-write la cui lettura è tornata con un body vuoto/degradato (es. un HTTP 5xx transitorio su `gh issue view --json body`) non può mai cancellare il body esistente.
353
374
  - **Secret-read guard** (`PreToolUse` su `Read|Bash`): blocca in modo netto la lettura di `.env`/`.env.*`/`.secrets`, sia tramite il tool Read sia tramite un comando Bash (un operando file, il valore di un'opzione, o un target di redirect `<`). È una decisione `deny` a livello di hook anziché una regola di permesso su `Read()`, quindi i segreti restano illeggibili senza attivare il prompt di approvazione `cd-compound-read` di Claude Code 2.1.259.
354
- - **Mockup-link guard** (`PreToolUse` su `AskUserQuestion`): rete di sicurezza strutturale per la regola della tecnica Prototype secondo cui la richiesta di approvazione di un mockup porta con sé il link al mockup. Blocca un `AskUserQuestion` che chiede di approvare un mockup (o di scegliere tra varianti) quando il testo della risposta subito prima non contiene un URL o un path, così l'agente mette il link cliccabile nel messaggio prima di richiedere. Su Codex, che non ha il tool `AskUserQuestion` e pone le domande come elenco numerato in testo, il gemello `mockup-link-stop` gira su `Stop`: se la risposta finale chiede quell'approvazione senza link, Codex fa proseguire il turno e l'agente riscrive la domanda col link.
375
+ - **Mockup-link guard** (`PreToolUse` su `AskUserQuestion`): rete di sicurezza strutturale per la regola della tecnica Prototype secondo cui la richiesta di approvazione di un mockup porta con sé il link al mockup. Blocca un `AskUserQuestion` che chiede di approvare un mockup (o di scegliere tra varianti) quando né il testo della risposta subito prima né la domanda di approvazione stessa contengono un URL o un path (se il messaggio corrente non è ancora nel transcript, la guardia non può giudicare e lascia passare), così l'agente mette il link cliccabile nel messaggio prima di richiedere. Su Codex, che non ha il tool `AskUserQuestion` e pone le domande come elenco numerato in testo, il gemello `mockup-link-stop` gira su `Stop`: se la risposta finale chiede quell'approvazione senza link, Codex fa proseguire il turno e l'agente riscrive la domanda col link.
355
376
  - **Issue injection scanner** (`PostToolUse` sulle letture `gh`): trailhead legge testo di issue/PR/commenti scritto da chiunque abbia accesso al repo, cioè input non fidato. Quando quel testo contiene frasi di prompt-injection, l'hook inietta un avviso che ricorda all'agente di trattarlo come dati, mai come comandi. Solo advisory: non blocca mai.
356
377
 
378
+ I guard di commit, secret, install, search e body girano in **un solo processo**: una singola registrazione `trailhead-bash-guards.js` li valuta in quell'ordine, così una chiamata Bash paga un solo avvio di node invece di cinque (tre processi hook per chiamata Bash in tutto: l'hook unificato, il secret-read guard e l'injection scanner). Restano cinque guard distinti con verdetti invariati; vince il primo blocco, e un guard che va in errore consente da solo senza fermare gli altri. Anche un guard il cui modulo manca o è rotto viene saltato, ma mai in silenzio: l'hook segnala alla sessione quale guard non ha controllato il comando, così un'installazione parziale non passa inosservata. Su Codex lo stesso hook è registrato con `--guards=commit,secret,install,body`, perché lì non c'è la search guard.
379
+
380
+ I guard secret, body e secret-read **falliscono in modo chiuso sui costrutti shell che non sanno interpretare con certezza** (`eval`, `source` / `.`, un `${ cmd; }` di bash 5.3, `printf -v`, la definizione di una funzione, un alias, una chiave body di `gh api` quotata, un backslash-a-capo dentro una parola): quando uno di questi può nascondere o precedere una scrittura (o sta accanto a un path segreto) il comando viene bloccato con un messaggio "riscrivilo in forma semplice" invece di essere emulato; gli stessi costrutti senza scritture o segreti vicini passano.
381
+
357
382
  Tutti e otto sono crash-safe (qualsiasi errore → allow) e attivi ogni volta che il plugin è installato. I guard di commit, secret, install, search, body e secret-read quindi si applicano in **ogni** repo, non solo nei progetti trailhead: intenzionale, dato che i conventional commit, niente `Co-Authored-By`, nessun segreto leakato, nessuna installazione non verificata, niente `cd`-poi-lettura-relativa, nessun body issue cancellato, e niente `.env`/`.secrets` leggibili sono regole permanenti di trailhead. Per fare opt-out, disabilita gli hook del plugin nelle impostazioni di Claude Code.
358
383
 
359
384
  ---
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-23%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-7%20hooks-e8710a?style=flat-square)
10
10
 
11
11
  </div>
12
12
 
@@ -26,7 +26,7 @@ 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, `claude.ai/design` builds the mockup on claude.ai/design (Claude only): by default a canvas (the built-in `/design` skill published as an Artifact, or a hand-made web-app project, not MCP-driven) or, for a shared component library, a design-system project via DesignSync (the `claude-design` MCP plus the `/design-sync` skill, the only MCP-driven surface). A third mode, `stitch`, uses the Google Stitch MCP and works on **both Claude Code and Codex**, so it's the cross-host hosted option. If a hosted mode's MCP isn't connected, it offers a one-time install and falls back to local-disk mockups if you decline; on Codex, where `claude.ai/design` isn't available, 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 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.
30
30
 
31
31
  The design choices that fall out of it:
32
32
 
@@ -70,7 +70,7 @@ npx @marcomigozzi/trailhead --dir=<path> # target a specific config dir
70
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
72
 
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 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 `claude.ai/design` mockup mode 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.
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
74
 
75
75
  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
76
 
@@ -130,7 +130,7 @@ Initial frontier = ①③④, three people can start in parallel. Then:
130
130
  - **① research** → a subagent reads the docs, returns a decision-ready finding, closes → unblocks ②.
131
131
  - **② decision** → diverge the options, grill to choose native vs custom, record the why.
132
132
  - **③ task** → the agent hands you a checklist (it needs *your* Google/GitHub accounts); you register the apps, it records where the credentials live.
133
- - **④ prototype** → a rough login screen (on the configured claude.ai/design project), approved before UI code.
133
+ - **④ prototype** → a rough login screen (on the configured Artifacts canvas), approved before UI code.
134
134
  - With ②③④ closed, **⑤ build** graduates → discuss → plan → execute (atomic commits, TDD at the auth seams) → verify (tests + code review + acceptance: asked who drives, the agent goes AI-driven and drives the browser through the real login flow).
135
135
  - After it ships you spot a wrong redirect in prod → `/trailhead:bug --of ⑤ "GitHub redirect goes to localhost"` → a new ticket carrying `Regression of: ⑤`, worked repro → diagnose → fix → verify. ⑤ stays closed.
136
136
 
@@ -185,6 +185,27 @@ Every verb is also a namespaced command (`/trailhead:new`, `/trailhead:work`, `/
185
185
  | `/trailhead:grill [topic]` | run a standalone grilling session on a decision/topic |
186
186
  | `/trailhead:map` | show the low-res map (destination, decisions, frontier, fog) |
187
187
  | `/trailhead:dashboard` | show the repo dashboard: the pinned index of every open map, the whiteboard, and live counts |
188
+ | `/trailhead:panel` | open or close the live map panel: the active map's open tickets by state, with a `work` button per frontier ticket (**Claude Code only**: needs the trailhead mod, 2.1.287+) |
189
+
190
+ ### 🖥️ On screen in Claude Code
191
+
192
+ The trailhead mod (Claude Code 2.1.287+) ships inside the plugin and draws the work beside the conversation. Codex keeps the command hooks and has no panel.
193
+
194
+ ![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
+
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.
197
+
198
+ ![The panel viewing another map, with a viewing and active header and a back to active button](assets/screenshots/map-panel-viewing.png)
199
+
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.
201
+
202
+ ![The Burn-down section of the panel: counts line and a chart of open tickets over time](assets/screenshots/burndown.png)
203
+
204
+ The burn-down shows the open tickets over time, from the map's first ticket to now, with done and removed counted apart.
205
+
206
+ ![The pipeline band above the prompt and the trailhead status entry under it](assets/screenshots/work-state-band.png)
207
+
208
+ The pipeline band above the prompt and the status entry under it show the ticket in play and its step.
188
209
 
189
210
  ### Capture: zero friction, one confirmation line, resolves nothing
190
211
 
@@ -217,6 +238,15 @@ The three that trip people up are **idea, seed, todo**, so here they are spelled
217
238
 
218
239
  The two cuts that matter: **idea vs seed** is waiting on *clarity* vs waiting on a *condition* (both land "later", for different reasons); **seed vs todo** is committed *later* vs committed *now*. Below all three sits **`note`** (raw text to remember, maybe never work), and off to the side is **`bug`** (a defect, not a commitment tier). The test for idea vs ticket is always: **can you phrase the question precisely now?** Yes → a ticket (`todo` if takeable, `seed` if gated); no → the fog (`idea`).
219
240
 
241
+ ### Manage: keep the install and the maps healthy
242
+
243
+ | Command | What it does |
244
+ |---|---|
245
+ | `/trailhead:config [get\|set …]` | guided menu setup; `get` prints the effective config, `set <key> <value>` writes one key (see Configuration below) |
246
+ | `/trailhead:update` | check for a newer trailhead and install it where that is safe |
247
+ | `/trailhead:prune [map]` | reclaim a map's sub-issue slots under GitHub's 100-cap (drops closed/orphan edges, keeps references) |
248
+ | `/trailhead:audit [map] [--all]` | audit closed tickets for regressions against today's code, plus duplicate and groupable open ones; read-only until you confirm |
249
+
220
250
  ---
221
251
 
222
252
  ## 🎫 Ticket types and their engines
@@ -229,7 +259,7 @@ Each type has its own inline engine: no external skill is invoked.
229
259
  |---|---|---|---|
230
260
  | 🧭 `decision` | a choice | HITL | diverge the options if unclear, then grill to converge on one |
231
261
  | 🔬 `research` | a fact | AFK | a subagent on a throwaway branch (the only type run in parallel) |
232
- | 🎨 `prototype` | an approved direction | HITL | a rough throwaway artifact to react to; UI screens go through this (disk, or a configured claude.ai/design project) before UI code |
262
+ | 🎨 `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 |
233
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) |
234
264
  | 🐛 `bug` | corrected code | HITL/AFK | `repro → diagnose → fix → verify`; a defect in closed work is a *new* ticket (`Regression of:`), not a reopen |
235
265
  | 🔧 `task` | an external state change | HITL/AFK | manual work that unblocks a decision (provision access, move data, sign up) |
@@ -304,8 +334,10 @@ The **📊 statusline** step offers to install trailhead's Claude Code status ba
304
334
  |---|---|---|
305
335
  | `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 |
306
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 |
307
- | `design` | **`disk`** \| `claude.ai/design` \| `stitch` | where UI mockups go: local throwaway HTML, claude.ai/design via Anthropic's official Claude Design MCP, or Google Stitch (cross-host, via the Stitch MCP) |
308
- | `design.surface` | **`canvas`** \| `design-system` | under `claude.ai/design` or `stitch`, which surface: a canvas project (default) or a design-system project via `/design-sync` |
337
+ | `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
+ | `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
+ | `design.surface` | **`canvas`** \| `design-system` | `stitch` only: a Stitch project of screens (default) or a shared Stitch design system |
340
+ | `design.system` | Design System Artifact URL | `artifacts` only: the design system installed on the mockup canvas (asked at the first screen if unset) |
309
341
  | `design.approval` | **`explicit`** \| `auto` | wait for mockup approval before UI code, or proceed without blocking |
310
342
  | `tdd` | **`seams`** \| `on` \| `off` | how the `build` engine tests |
311
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) |
@@ -330,14 +362,13 @@ Example config file (the same shape works for the project `.trailhead/config.jso
330
362
  { "ticket": { "language": "en" }, "models": { "plan": "claude-opus-5-5", "execute": "claude-sonnet-5-5" }, "tdd": "seams", "acceptance": { "browser": "auto" } }
331
363
  ```
332
364
 
333
- **Design mockups.** `design: disk` (the default) drops a throwaway static HTML mockup next to the code and links it from the ticket. `design: claude.ai/design` instead builds the mockup on claude.ai/design (canvas via the built-in `/design` skill or a hand-made web-app project; design-system via Anthropic's **official Claude Design MCP**, `claude mcp add --scope user --transport http claude-design https://api.anthropic.com/v1/design/mcp`, sign in with `/design-login`), where you refine it visually, and `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). Both honour `design.surface`:
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).
334
366
 
335
- - **`canvas`** (default): a one-off screen to react to, editable and exportable as a live prototype. Under `claude.ai/design` it's the built-in `/design` skill (published as an Artifact) or a claude.ai/design project made by hand in the web app; under `stitch` it's a Stitch project.
336
- - **`design-system`**: a design-system project driven by **DesignSync** (the `/design-sync` skill plus the `claude-design` MCP), for a shared component library kept in sync.
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.
337
368
 
338
- On the first UI screen, trailhead attaches to a surface: for a **Stitch** project or a **claude.ai/design design-system**, it can **pick an existing one**, **paste a URL**, or **create a new one** (it asks you for the name); for a **claude.ai/design canvas** there is no create-via-MCP, so it uses the `/design` skill (an **Artifact**) or a web-app project whose `/p/<id>` URL you **paste**. It caches the chosen id/URL in `design.project`; a project already cached from before keeps the `design-system` surface unless you set `design.surface`, so existing setups don't change. Each screen is added there, and its URL is linked from the ticket. Once you approve, trailhead **re-fetches** the current design (in case you edited it live) before writing any UI code.
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).
339
370
 
340
- If a hosted mode's MCP isn't connected, trailhead **offers to install it once** (`claude mcp add ...`); decline and it falls back to local disk. (The `claude.ai/design` **canvas** surface needs no MCP, so nothing is offered for it; only the **design-system** surface has an install offer.) On Codex, `claude.ai/design` isn't available, so trailhead offers to switch to `stitch` (cross-host) 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`); it doesn't affect the one-time install offer, which prompts either way.
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`).
341
372
 
342
373
  ---
343
374
 
@@ -351,9 +382,13 @@ Beyond the skill's instructions, trailhead ships eight of its own hooks (in `hoo
351
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.
352
383
  - **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.
353
384
  - **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.
354
- - **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 the reply text right before it holds no URL or file path, 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.
385
+ - **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.
355
386
  - **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.
356
387
 
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.
389
+
390
+ 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
+
357
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.
358
393
 
359
394
  ---
@@ -113,10 +113,10 @@ Codex has a subagent toolkit: the multi_agent tools (\`spawn_agent\`, \`send_inp
113
113
  The engine's \`/clear\` is Codex's \`/new\` (start a fresh session). Command references in a handoff use the skill form (\`$trailhead work <n>\`).
114
114
 
115
115
  ## F. Lifecycle hooks (native Codex hooks)
116
- Codex has a hook bus, so trailhead's guardrails run as **real Codex hooks**, not degraded prose. The installer registers them in \`~/.codex/hooks.json\`: commit-guard, secret-guard, install-guard and body-guard as \`PreToolUse\` (matcher \`Bash\`) hooks that can veto the command before it runs, secret-read-guard as a \`PreToolUse\` (matcher \`Read|Bash\`) hook that denies reads of \`.env\`/\`.env.*\`/\`.secrets\`, the injection-scanner as a \`PostToolUse\` (matcher \`Bash\`) advisory, mockup-link-stop as a \`Stop\` hook (when your final reply asks to approve a mockup without carrying its link, it continues the turn so you rewrite the ask with the URL or file path in the prose), and check-update as a \`SessionStart\` hook. On Codex, file reads happen through shell commands rather than a native Read-tool event, so the secret-read guard's enforcement here runs via its **Bash** arm (protection is not lost); the \`Read\` half of its matcher is a Claude-Code-native path, registered on Codex too only so it fires without changes if a future Codex version adds an equivalent event. search-guard is Claude-Code-specific (it only prevents arming Claude's \`Read()\` deny rule under bypass permissions, a mechanism Codex lacks), so it is not projected to Codex at all. They use the same wire format they use on Claude Code (JSON on stdin, a \`decision\`/\`permissionDecision\` verdict on stdout), which Codex accepts. The host-independent git \`commit-msg\` hook is still installed at repo first-use as well (defence in depth), exactly as on Claude. Codex gates hooks behind \`features.hooks = true\` and reviews them for trust on first start: the installer enables the flag, and Codex will ask you to trust trailhead's hooks the next time it starts.
116
+ Codex has a hook bus, so trailhead's guardrails run as **real Codex hooks**, not degraded prose. The installer registers them in \`~/.codex/hooks.json\`: commit-guard, secret-guard, install-guard and body-guard as \`PreToolUse\` (matcher \`Bash\`) hooks that can veto the command before it runs (the four run in one process, a single \`trailhead-bash-guards.js --guards=commit,secret,install,body\` registration, so a Bash call pays one start-up instead of four), secret-read-guard as a \`PreToolUse\` (matcher \`Read|Bash\`) hook that denies reads of \`.env\`/\`.env.*\`/\`.secrets\`, the injection-scanner as a \`PostToolUse\` (matcher \`Bash\`) advisory, mockup-link-stop as a \`Stop\` hook (when your final reply asks to approve a mockup without carrying its link, it continues the turn so you rewrite the ask with the URL or file path in the prose), and check-update as a \`SessionStart\` hook. On Codex, file reads happen through shell commands rather than a native Read-tool event, so the secret-read guard's enforcement here runs via its **Bash** arm (protection is not lost); the \`Read\` half of its matcher is a Claude-Code-native path, registered on Codex too only so it fires without changes if a future Codex version adds an equivalent event. search-guard is Claude-Code-specific (it only prevents arming Claude's \`Read()\` deny rule under bypass permissions, a mechanism Codex lacks), so it is not projected to Codex at all. They use the same wire format they use on Claude Code (JSON on stdin, a \`decision\`/\`permissionDecision\` verdict on stdout), which Codex accepts. The host-independent git \`commit-msg\` hook is still installed at repo first-use as well (defence in depth), exactly as on Claude. Codex gates hooks behind \`features.hooks = true\` and reviews them for trust on first start: the installer enables the flag, and Codex will ask you to trust trailhead's hooks the next time it starts.
117
117
 
118
- ## G. Design / UI mockups -> local disk
119
- The \`claude.ai/design\` mockup mode (DesignSync: the \`claude_design\` MCP + the \`/design-sync\` skill) does not exist on Codex, so it **collapses to local-disk mockups** here: whatever \`config.design\` says, the Prototype technique's \`disk\` path is the only one available. Produce a throwaway static HTML mockup next to the code, link it from the ticket, and never attempt DesignSync / claude.ai/design on Codex (there is no \`claude_design\` MCP or \`/design-sync\` skill to call). \`config.design.approval\` still applies unchanged: \`explicit\` waits for the user's go-ahead before any UI code, \`auto\` surfaces the mockup and proceeds.
118
+ ## G. Design / UI mockups: artifacts -> stitch offer, else local disk
119
+ The \`artifacts\` mockup mode (a Design canvas Artifact built with Claude Code's \`Artifact\` tool; the legacy \`claude.ai/design\` value means the same) does not exist on Codex: there is no \`Artifact\` tool here. When \`config.design\` names it, follow the Prototype technique's fallback: **offer once to switch this project to \`design: stitch\`** (the Google Stitch MCP runs on Codex too), and only on decline produce a throwaway static HTML mockup next to the code (\`disk\`), link it from the ticket, and say why. Never try to create or edit an Artifact on Codex. \`config.design.approval\` still applies unchanged: \`explicit\` waits for the user's go-ahead before any UI code, \`auto\` surfaces the mockup and proceeds.
120
120
 
121
121
  ## H. Shell timeouts & background jobs (plan-review's external CLIs)
122
122
  The **Cross-AI plan review** technique invokes each external reviewer CLI (\`codex exec\`, \`gemini -p\`, \`qwen -p\`, …) through Claude Code's **Bash tool**, and tells the engine to beat that tool's ~2-minute default cut by setting its \`timeout\` parameter to the 10-minute max, or launching the call with \`run_in_background: true\` and collecting it later. Those are Claude Bash-tool knobs with no Codex equivalent, so map the *intent* onto Codex's own shell/exec tool. Reviews never block the session (the engine's hard cross-host default), so **the default is to background the reviewers** and collect them by polling, never block on them: launch each with a trailing \`&\` (or your shell tool's background form), **redirect its stdin from \`/dev/null\` and its output to a file** (\`codex exec … "<prompt>" < /dev/null > out.txt 2>&1 &\`), and poll/collect when it finishes, so several reviewers run in parallel instead of serially eating the wall-clock; or wrap each reviewer invocation in a §D subagent so the long-running call sits behind the multi_agent poll loop. Where a reviewer must run synchronously instead, give that call its **own long timeout** (the \`timeout_ms\` / equivalent on Codex's shell/exec tool, set to several minutes, never the short default), so a reviewer that legitimately runs for minutes is not cut mid-review. Either way plan-review's own rule is unchanged: never rely on a short default timeout, retry once at the wide margin, then drop a still-slow reviewer rather than let it wedge the build. **Close the reviewer's stdin when backgrounding it** (\`< /dev/null\`): \`codex exec\` reads additional prompt input from stdin and blocks on EOF, so a backgrounded reviewer that inherits an open or attached stdin sits idle at "Reading additional input from stdin..." (0% CPU) forever instead of running (passing the prompt as an argument does not exempt it). Guard the poll loop too: a reviewer pinned at 0% CPU with no output past a threshold is stuck, so surface it and drop it after the one retry rather than reporting "still running" indefinitely. This is the external-CLI **shell** path; §D covers the separate **subagent** (\`wait_agent\`) path, and the two are distinct (an external reviewer CLI is a plain shell call, not a spawned Codex subagent).
@@ -323,6 +323,11 @@ policy:
323
323
  `;
324
324
  }
325
325
 
326
+ // --- CODEX_MOD_ONLY_VERBS --------------------------------------------------
327
+ // Verbs only the Claude Code mod answers (commands/<verb>.md exists for the
328
+ // namespaced command, but nothing on Codex can draw it): never projected.
329
+ const CODEX_MOD_ONLY_VERBS = ['panel'];
330
+
326
331
  // --- codexVerbSkillPlan ----------------------------------------------------
327
332
  // Given the resolved verb list (each { verb, description? }, or a bare string
328
333
  // verb; normally read from plugins/trailhead/commands/*.md by the installer),
@@ -340,6 +345,7 @@ function codexVerbSkillPlan(codexHome, verbs) {
340
345
  const v = entry && typeof entry === 'object' ? entry.verb : entry;
341
346
  if (v == null || String(v).trim() === '') continue;
342
347
  const verb = String(v).trim();
348
+ if (CODEX_MOD_ONLY_VERBS.includes(verb)) continue;
343
349
  const description = entry && typeof entry === 'object' ? entry.description : undefined;
344
350
  const skillDir = path.join(skillsRoot, `trailhead-${verb}`);
345
351
  dirs.push(skillDir);
@@ -358,10 +364,13 @@ function codexVerbSkillPlan(codexHome, verbs) {
358
364
  }
359
365
 
360
366
  // --- codexHookEntries -----------------------------------------------------
361
- // The six guardrail hooks trailhead registers on Codex, as {event, matcher,
362
- // command} records. Commands run the copied guard scripts under hooksScriptsDir
363
- // via node, matching Claude's `node "<path>"` form. PreToolUse guards (commit,
364
- // secret, install, body, secret-read) can veto; the injection-scanner
367
+ // The guardrail hooks trailhead registers on Codex, as {event, matcher,
368
+ // command} records. Commands run the copied hook scripts under hooksScriptsDir
369
+ // via node, matching Claude's `node "<path>"` form. The four Bash guards
370
+ // (commit, secret, install, body) are ONE registration, the merged
371
+ // trailhead-bash-guards.js with `--guards=commit,secret,install,body` (a single
372
+ // node process per Bash call); secret-read is a separate PreToolUse hook. All
373
+ // PreToolUse guards can veto; the injection-scanner
365
374
  // is a PostToolUse advisory; check-update runs at SessionStart. Reused by the
366
375
  // installer, which merges them into ~/.codex/hooks.json (same shape as
367
376
  // Claude's settings.json hooks block).
@@ -372,10 +381,7 @@ function codexVerbSkillPlan(codexHome, verbs) {
372
381
  function codexHookEntries(hooksScriptsDir) {
373
382
  const cmd = (name) => `node "${path.join(hooksScriptsDir, name)}"`;
374
383
  return [
375
- { event: 'PreToolUse', matcher: 'Bash', command: cmd('trailhead-commit-guard.js') },
376
- { event: 'PreToolUse', matcher: 'Bash', command: cmd('trailhead-secret-guard.js') },
377
- { event: 'PreToolUse', matcher: 'Bash', command: cmd('trailhead-install-guard.js') },
378
- { event: 'PreToolUse', matcher: 'Bash', command: cmd('trailhead-body-guard.js') },
384
+ { event: 'PreToolUse', matcher: 'Bash', command: `${cmd('trailhead-bash-guards.js')} --guards=commit,secret,install,body` },
379
385
  { event: 'PreToolUse', matcher: 'Read|Bash', command: cmd('trailhead-secret-read-guard.js') },
380
386
  { event: 'PostToolUse', matcher: 'Bash', command: cmd('trailhead-issue-injection-scanner.js') },
381
387
  { event: 'Stop', matcher: '', command: cmd('trailhead-mockup-link-stop.js') },
@@ -447,6 +453,7 @@ module.exports = {
447
453
  codexVerbSkillContent,
448
454
  codexVerbSkillAgentsYaml,
449
455
  codexVerbSkillPlan,
456
+ CODEX_MOD_ONLY_VERBS,
450
457
  codexHookEntries,
451
458
  enableCodexFeatureFlag,
452
459
  enableCodexHooksFeature,
@@ -15,6 +15,7 @@ const {
15
15
  codexVerbSkillContent,
16
16
  codexVerbSkillAgentsYaml,
17
17
  codexVerbSkillPlan,
18
+ CODEX_MOD_ONLY_VERBS,
18
19
  codexHookEntries,
19
20
  enableCodexHooksFeature,
20
21
  enableCodexMultiAgentV2Feature,
@@ -160,19 +161,22 @@ ok('codexVerbSkillPlan: accepts a bare-string verb and skips empty/invalid', (()
160
161
  })());
161
162
  ok('codexVerbSkillPlan: null verbs yields nothing', codexVerbSkillPlan('/c', null).dirs.length === 0 && codexVerbSkillPlan('/c').dirs.length === 0);
162
163
 
164
+ // #208: a verb only the Claude Code mod answers (the map panel) has no Codex projection.
165
+ ok('CODEX_MOD_ONLY_VERBS names exactly the panel verb', Array.isArray(CODEX_MOD_ONLY_VERBS) && CODEX_MOD_ONLY_VERBS.length === 1 && CODEX_MOD_ONLY_VERBS[0] === 'panel');
166
+ ok('codexVerbSkillPlan: never projects a mod-only verb, object or bare string', (() => {
167
+ const plan = codexVerbSkillPlan('/c', [{ verb: 'panel', description: 'x' }, 'panel', { verb: 'map' }]);
168
+ return plan.dirs.length === 1 && plan.dirs[0] === '/c/skills/trailhead-map' && !plan.writes.some((w) => /panel/.test(w.path));
169
+ })());
170
+
163
171
  // --- codexHookEntries ---
164
172
  const entries = codexHookEntries('/h');
165
- ok('codexHookEntries returns 8 entries', Array.isArray(entries) && entries.length === 8);
166
- ok('codexHookEntries: commit-guard is PreToolUse/Bash', entries.some((e) =>
167
- e.event === 'PreToolUse' && e.matcher === 'Bash' && e.command.includes('trailhead-commit-guard.js') && e.command.includes('/h')));
168
- ok('codexHookEntries: secret-guard is PreToolUse/Bash', entries.some((e) =>
169
- e.event === 'PreToolUse' && e.matcher === 'Bash' && e.command.includes('trailhead-secret-guard.js')));
170
- ok('codexHookEntries: install-guard is PreToolUse/Bash', entries.some((e) =>
171
- e.event === 'PreToolUse' && e.matcher === 'Bash' && e.command.includes('trailhead-install-guard.js')));
172
- ok('codexHookEntries: search-guard is absent (Claude-Code-only, not projected to Codex)',
173
- entries.every((e) => !e.command.includes('trailhead-search-guard.js')));
174
- ok('codexHookEntries: body-guard is PreToolUse/Bash', entries.some((e) =>
175
- e.event === 'PreToolUse' && e.matcher === 'Bash' && e.command.includes('trailhead-body-guard.js')));
173
+ ok('codexHookEntries returns 5 entries', Array.isArray(entries) && entries.length === 5);
174
+ const bashEntries = entries.filter((e) => e.event === 'PreToolUse' && e.matcher === 'Bash');
175
+ ok('codexHookEntries: exactly one PreToolUse/Bash registration', bashEntries.length === 1);
176
+ ok('codexHookEntries: the Bash entry is the merged hook with exactly --guards=commit,secret,install,body',
177
+ bashEntries[0].command === 'node "/h/trailhead-bash-guards.js" --guards=commit,secret,install,body');
178
+ ok('codexHookEntries: no per-guard script is registered on its own', entries.every((e) =>
179
+ !/trailhead-(commit|secret|install|search|body)-guard\.js/.test(e.command)));
176
180
  ok('codexHookEntries: secret-read-guard is PreToolUse/Read|Bash', entries.some((e) =>
177
181
  e.event === 'PreToolUse' && e.matcher === 'Read|Bash' && e.command.includes('trailhead-secret-read-guard.js')));
178
182
  ok('codexHookEntries: injection-scanner is PostToolUse/Bash', entries.some((e) =>
@@ -402,4 +406,28 @@ ok('convertToCodex projects the anchor positives to Codex forms', (() => {
402
406
  !out.includes('/trailhead:work') && !out.includes('/clear');
403
407
  })());
404
408
 
409
+ // #205: the work state helper and its session env var survive the projection;
410
+ // the projected work skill and handoff carry the set/clear calls.
411
+ ok('convertToCodex keeps the trailhead-work-state.js filename',
412
+ convertToCodex('node <templates>/trailhead-work-state.js set').includes('<templates>/trailhead-work-state.js set'));
413
+ ok('convertToCodex keeps CLAUDE_CODE_SESSION_ID intact',
414
+ convertToCodex('session from CLAUDE_CODE_SESSION_ID when set').includes('CLAUDE_CODE_SESSION_ID'));
415
+ {
416
+ const skillsDir = path.join(__dirname, '../../plugins/trailhead/skills');
417
+ const workSkill = convertToCodex(fs.readFileSync(path.join(skillsDir, 'trailhead-work', 'SKILL.md'), 'utf8'));
418
+ ok('projected work skill carries the work-start set call',
419
+ workSkill.includes('trailhead-work-state.js set --repo <working-root> --map <n> --ticket <n> --title "<title>" --step discuss'));
420
+ const handoff = convertToCodex(fs.readFileSync(path.join(skillsDir, '_shared', 'session-handoff.md'), 'utf8'));
421
+ ok('projected session handoff carries the work state clear call',
422
+ handoff.includes('trailhead-work-state.js clear --repo <working-root>'));
423
+ const teamwork = convertToCodex(fs.readFileSync(path.join(skillsDir, '_shared', 'teamwork.md'), 'utf8'));
424
+ ok('projected teamwork carries the step change call',
425
+ teamwork.includes('trailhead-work-state.js set --repo <working-root> --step <step>'));
426
+ ok('projected teamwork carries the autoDone counter flag',
427
+ teamwork.includes('--auto true --auto-done <n>'));
428
+ const autoRef = convertToCodex(fs.readFileSync(path.join(skillsDir, 'trailhead-work', 'references', 'auto.md'), 'utf8'));
429
+ ok('projected auto reference carries the autoDone counter flag',
430
+ autoRef.includes('--auto true --auto-done <n>'));
431
+ }
432
+
405
433
  console.log(`✓ codex-projection: ${passed} assertions passed`);
@@ -135,6 +135,9 @@ function readRegistry(registryFile) {
135
135
  // token expands to exactly hooksDir/<f>, OR an unresolved token's basename is
136
136
  // <f> (stays warning-only, never also missing).
137
137
  function checkRegisteredTargets(spec, missing, unverified) {
138
+ // Shipped files (hookFiles) may include modules that are only require()d;
139
+ // the files that must be registered are the entrypoints (default: all shipped).
140
+ const entrypoints = spec.entrypointFiles || spec.hookFiles;
138
141
  const registry = readRegistry(spec.registryFile);
139
142
  const registryName = path.basename(spec.registryFile);
140
143
  const events = (registry && typeof registry.hooks === 'object' && registry.hooks) ? registry.hooks : {};
@@ -168,7 +171,7 @@ function checkRegisteredTargets(spec, missing, unverified) {
168
171
  }
169
172
  }
170
173
 
171
- for (const f of spec.hookFiles) {
174
+ for (const f of entrypoints) {
172
175
  if (!registeredExact.has(f) && !registeredViaUnresolved.has(f)) {
173
176
  missing.push(`hook registration ${f} (${registryName})`);
174
177
  }
@@ -185,8 +188,8 @@ function checkAgents(spec, missing) {
185
188
  }
186
189
 
187
190
  // --- verifyInstall --------------------------------------------------------------
188
- // spec = { skillsRoot, skillDirs, sharedFiles, hooksDir, hookFiles,
189
- // hookLibFiles, registryFile, agentsDir, agentPattern, env, home }
191
+ // spec = { skillsRoot, skillDirs, sharedFiles, hooksDir, hookFiles (shipped),
192
+ // entrypointFiles (registered; defaults to hookFiles), hookLibFiles, registryFile, agentsDir, agentPattern, env, home }
190
193
  function verifyInstall(spec) {
191
194
  const missing = [];
192
195
  const unverified = [];
@@ -199,13 +202,14 @@ function verifyInstall(spec) {
199
202
  }
200
203
 
201
204
  // --- spec builders ---------------------------------------------------------
202
- function claudeVerifySpec(configDir, { skillDirs, sharedFiles, hookFiles, hookLibFiles, env, home }) {
205
+ function claudeVerifySpec(configDir, { skillDirs, sharedFiles, hookFiles, entrypointFiles, hookLibFiles, env, home }) {
203
206
  return {
204
207
  skillsRoot: path.join(configDir, 'skills'),
205
208
  skillDirs,
206
209
  sharedFiles,
207
210
  hooksDir: path.join(configDir, 'hooks'),
208
211
  hookFiles,
212
+ entrypointFiles,
209
213
  hookLibFiles,
210
214
  registryFile: path.join(configDir, 'settings.json'),
211
215
  agentsDir: path.join(configDir, 'agents'),
@@ -215,7 +219,7 @@ function claudeVerifySpec(configDir, { skillDirs, sharedFiles, hookFiles, hookLi
215
219
  };
216
220
  }
217
221
 
218
- function codexVerifySpec(configDir, { skillDirs, sharedFiles, hookFiles, hookLibFiles, env, home }) {
222
+ function codexVerifySpec(configDir, { skillDirs, sharedFiles, hookFiles, entrypointFiles, hookLibFiles, env, home }) {
219
223
  const L = codexLayout(configDir);
220
224
  return {
221
225
  skillsRoot: L.skillsRoot,
@@ -223,6 +227,7 @@ function codexVerifySpec(configDir, { skillDirs, sharedFiles, hookFiles, hookLib
223
227
  sharedFiles,
224
228
  hooksDir: L.hooksScriptsDir,
225
229
  hookFiles,
230
+ entrypointFiles,
226
231
  hookLibFiles,
227
232
  registryFile: L.hooksJson,
228
233
  agentsDir: L.codexAgentsDir,