@marcomigozzi/trailhead 0.8.1 → 0.10.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 (45) hide show
  1. package/README.it.md +20 -17
  2. package/README.md +20 -17
  3. package/bin/lib/codex-projection.js +14 -6
  4. package/bin/lib/codex-projection.test.js +46 -2
  5. package/bin/trailhead.js +21 -10
  6. package/bin/trailhead.test.js +326 -0
  7. package/package.json +1 -1
  8. package/plugins/trailhead/.claude-plugin/plugin.json +1 -1
  9. package/plugins/trailhead/commands/prune.md +6 -0
  10. package/plugins/trailhead/commands/quick.md +1 -1
  11. package/plugins/trailhead/hooks/hooks.json +4 -0
  12. package/plugins/trailhead/hooks/lib/gh-subcommand.js +102 -0
  13. package/plugins/trailhead/hooks/lib/gh-subcommand.test.js +99 -0
  14. package/plugins/trailhead/hooks/trailhead-body-guard.js +221 -0
  15. package/plugins/trailhead/hooks/trailhead-body-guard.test.js +222 -0
  16. package/plugins/trailhead/hooks/trailhead-issue-injection-scanner.js +61 -35
  17. package/plugins/trailhead/hooks/trailhead-issue-injection-scanner.test.js +76 -0
  18. package/plugins/trailhead/hooks/trailhead-secret-guard.js +4 -15
  19. package/plugins/trailhead/hooks/trailhead-secret-guard.test.js +31 -0
  20. package/plugins/trailhead/skills/_shared/choices.md +5 -1
  21. package/plugins/trailhead/skills/_shared/configuration-reference.md +23 -16
  22. package/plugins/trailhead/skills/_shared/configuration.md +1 -1
  23. package/plugins/trailhead/skills/_shared/load-first.md +2 -0
  24. package/plugins/trailhead/skills/_shared/multi-map.md +1 -1
  25. package/plugins/trailhead/skills/_shared/principles.md +1 -1
  26. package/plugins/trailhead/skills/_shared/session-handoff.md +9 -7
  27. package/plugins/trailhead/skills/_shared/substrate-commands.md +41 -5
  28. package/plugins/trailhead/skills/_shared/substrate.md +10 -6
  29. package/plugins/trailhead/skills/_shared/teamwork.md +19 -12
  30. package/plugins/trailhead/skills/_shared/techniques/acceptance-testing.md +13 -4
  31. package/plugins/trailhead/skills/_shared/techniques/grilling.md +1 -1
  32. package/plugins/trailhead/skills/_shared/techniques/prototype.md +1 -1
  33. package/plugins/trailhead/skills/_shared/techniques/verify.md +1 -1
  34. package/plugins/trailhead/skills/_shared/techniques.md +1 -1
  35. package/plugins/trailhead/skills/trailhead/SKILL.md +3 -3
  36. package/plugins/trailhead/skills/trailhead-capture/references/capture.md +1 -1
  37. package/plugins/trailhead/skills/trailhead-chart/SKILL.md +3 -1
  38. package/plugins/trailhead/skills/trailhead-chart/references/charting.md +12 -2
  39. package/plugins/trailhead/skills/trailhead-chart/references/inbox.md +2 -2
  40. package/plugins/trailhead/skills/trailhead-manage/SKILL.md +5 -4
  41. package/plugins/trailhead/skills/trailhead-manage/references/pruning.md +48 -0
  42. package/plugins/trailhead/skills/trailhead-view/SKILL.md +4 -2
  43. package/plugins/trailhead/skills/trailhead-work/SKILL.md +18 -16
  44. package/plugins/trailhead/skills/trailhead-work/references/auto.md +4 -2
  45. package/plugins/trailhead/skills/trailhead-work/references/ticket-engines.md +12 -9
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-21%20verbi-0e8a16?style=flat-square) ![guardrail](https://img.shields.io/badge/guardrail-6%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-22%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
 
@@ -37,7 +37,7 @@ Le scelte di design che ne derivano:
37
37
  - **Costruito per i team, perché il piano è condiviso.** Il tracker è l'unica fonte di verità, quindi la mappa è un **workspace condiviso**, non il file locale di una singola persona. Molte persone (e i loro agenti) ci lavorano contemporaneamente, e i meccanismi impediscono che si scontrino:
38
38
  - **Claiming.** Prendi un ticket assegnandolo a te stesso; l'assegnatario *è* il lock. Una collisione ti ferma e chiede, invece di sovrascrivere il lavoro di qualcun altro. Chi rivendica un ticket lo possiede dall'inizio alla fine e lo chiude, senza un approvatore separato.
39
39
  - **Una frontier condivisa, un ticket per sessione.** La **frontier** (ticket aperti, non assegnati, non bloccati) è la coda comune di ciò che è prendibile ora; ognuno ne sceglie uno diverso. Risolvere un ticket termina la sessione, così il lavoro resta in porzioni revisionabili, un ticket alla volta.
40
- - **Isolation per sessioni concorrenti su una macchina.** Due agenti che modificano un unico checkout si corrompono a vicenda, quindi la convenzione `isolation:` dà a ogni ticket in corso il proprio **`git worktree`** (o un intero **`clone`** per le app legate a un path che un worktree non riesce a buildare); righe `Scope:` disgiunte lasciano girare in parallelo lavori non sovrapposti e serializzano solo dove c'è vera sovrapposizione.
40
+ - **Isolation per sessioni concorrenti su una macchina.** Due agenti che modificano un unico checkout si corrompono a vicenda, quindi la convenzione `isolation:` (default **`auto`**, che chiede al work-start di ogni ticket se lavorerai più di un ticket alla volta e poi sceglie la modalità giusta in base alla forma del repo) dà a ogni ticket in corso il proprio **`git worktree`** (o un intero **`clone`** per le app legate a un path, o i repo con submodule, che un worktree non riesce a isolare in sicurezza) quando ne lavori più di uno alla volta; righe `Scope:` disgiunte lasciano girare in parallelo lavori non sovrapposti e serializzano solo dove c'è vera sovrapposizione.
41
41
  - **Pause, resume, split.** Qualsiasi ticket può essere messo in **pause** con un commento di checkpoint e **ripreso** più tardi da chiunque (una volta rilasciato), o **splittato** in figli quando supera la dimensione di una sessione, con i blocker ripuntati così la frontier resta onesta.
42
42
  - **Più mappe insieme.** Un repo può portare **più di una mappa attiva** (milestone o feature parallele), ciascuna con la propria frontier con scope, così due persone possono condurre due sforzi sullo stesso repo senza pestarsi i piedi, e puoi parcheggiare una mappa per lavorarne un'altra.
43
43
  - **Esterni e fiducia.** Le issue aperte da altre persone passano attraverso una **inbox** che riformula quelle valide in ticket *sul posto* (mantenendo l'autorialità di chi ha segnalato), mentre una guardia di **trust e provenance** mette in quarantena le issue etichettate `trailhead:*` da fonti non fidate finché un maintainer non le adotta, supportata da un workflow di label-guard lato repo.
@@ -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`), 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`; 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.
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
 
@@ -94,7 +94,7 @@ Man mano che i ticket si risolvono, la fog si dirada: domande che erano troppo v
94
94
  Una mappa ha come scope **un** solo sforzo, quindi la conoscenza che appartiene al *repo* (non a una singola mappa) vive in **anchor issue con scope di repo**, create una volta e condivise da ogni mappa (le Notes di ciascuna mappa le collegano soltanto, così nulla resta orfano quando una mappa finisce):
95
95
 
96
96
  - **`trailhead:codebase`**: la codebase map distillata (architettura, stack, convenzioni, rischi, test/build), scritta una volta all'adopt e aggiornata solo su drift importanti.
97
- - **`trailhead:conventions`**: il **modo di lavorare** del progetto, leggibile da tutti: un piccolo header machine-read che il motore obbedisce (`git: main|pr`, `release: command|auto`, `isolation: none|worktree|clone`) sopra la prosa umana. `/trailhead:adopt` e `:new` lo chiedono all'inizio. `isolation: worktree` dà a ogni ticket in esecuzione il proprio `git worktree` + branch (le sessioni concorrenti su un clone non condividono mai un working tree); `isolation: clone` gli dà invece un clone dedicato, per le app legate a un path che un worktree non riesce a buildare.
97
+ - **`trailhead:conventions`**: il **modo di lavorare** del progetto, leggibile da tutti: un piccolo header machine-read che il motore obbedisce (`git: main|pr`, `release: command|auto`, `isolation: auto|none|worktree|clone`) sopra la prosa umana. `/trailhead:adopt` e `:new` lo chiedono all'inizio. `isolation: auto` (il default) decide per ogni ticket al work-start: chiede se lavorerai più di un ticket alla volta e, se sì, usa un `git worktree` (repo semplice) o un `clone` (superrepo con submodule); altrimenti lavora nel checkout corrente. `isolation: worktree` dà a ogni ticket in esecuzione il proprio `git worktree` + branch (le sessioni concorrenti su un clone non condividono mai un working tree); `isolation: clone` gli dà invece un clone dedicato, per le app legate a un path che un worktree non riesce a buildare e per i superrepo con submodule (dove `worktree` viene scalato a `clone`).
98
98
  - **`trailhead:dashboard`**: l'indice pinnato dell'intera superficie, un link a ogni mappa aperta (con le progress bar native di GitHub), la whiteboard, e conteggi live (inbox, frontier della whiteboard). Aggiornato quando una mappa viene tracciata o esaurita, quando un ticket della whiteboard nasce o si risolve (non ha una progress bar nativa, quindi la dashboard è l'unico posto in cui appare; un ticket nato da `quick`, lavorato e risolto nella stessa sessione, si aggiorna al suo handoff o alla pausa, non alla creazione), e su richiesta via `/trailhead:dashboard`.
99
99
 
100
100
  Questi tre riempiono i **3 slot di issue pinnate** di GitHub, così gli anchor di trailhead di un repo restano a un click di distanza; **le mappe stesse non vengono mai pinnate** (sono indicizzate dalla dashboard, dato che la mappa "attiva" è stato locale per-checkout, non un fatto globale del repo). La *config* del progetto (models, TDD, design…) è ancora una cosa separata, un semplice file `.trailhead/config.json` alla radice del repo, mai in una issue.
@@ -131,7 +131,7 @@ Frontier iniziale = ①③④, tre persone possono partire in parallelo. Poi:
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
133
  - **④ prototype** → una schermata di login grezza (sul progetto claude.ai/design configurato), approvata prima del codice UI.
134
- - Con ②③④ chiusi, **⑤ build** si diploma → discuss → plan → execute (commit atomici, TDD ai seam dell'auth) → verify (test + code review + acceptance: l'agente guida il browser attraverso il vero flusso di login).
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
 
137
137
  ```
@@ -174,7 +174,7 @@ Ogni verbo è anche un comando con namespace (`/trailhead:new`, `/trailhead:work
174
174
  | `/trailhead:new [idea]` | traccia una nuova mappa da un'idea informe |
175
175
  | `/trailhead:adopt` | adotta un progetto esistente (mappa la codebase una volta, poi vai lean) |
176
176
  | `/trailhead:work [ticket]` | lavora il prossimo ticket della frontier, o quello che nomini |
177
- | `/trailhead:quick [ticket \| "text"]` | lavora un ticket per intero, fuori dalla mappa: apre un ticket della whiteboard da `"text"` (o prende `<n>`), esegue il motore completo, fa grilling solo se serve, non splitta mai |
177
+ | `/trailhead:quick [ticket \| "text"]` | lavora un ticket per intero, senza mai splittare: `"text"` apre un ticket della whiteboard fuori dalla mappa; `<n>` prende un ticket esistente (un ticket di mappa resta sulla sua mappa), esegue il motore completo, fa grilling solo se serve |
178
178
  | `/trailhead:whiteboard` | mostra la whiteboard: i ticket sciolti (senza mappa) e la loro frontier |
179
179
  | `/trailhead:inbox [issue]` | fa il triage delle issue aperte da altri e integra quelle buone nella mappa |
180
180
  | `/trailhead:resume [ticket]` | riprende un ticket in pausa dal suo checkpoint `PAUSED` |
@@ -205,7 +205,7 @@ Quando una mappa è aperta, una capture che produce un ticket (`todo`/`bug`/`see
205
205
  In profondità dentro una mappa, emerge qualcosa di non correlato: un bug in un'altra area, una faccenda, un'idea veloce su cui vuoi agire adesso. Forzarla sulla frontier della mappa inquina la mappa; tracciare un'intera nuova mappa per essa è eccessivo. È a questo che serve la **whiteboard**, il lavoro sciolto senza mappa, e due mosse ti impediscono di restare bloccato:
206
206
 
207
207
  - **Mettila da parte.** Un `todo`/`bug`/`seed`/`idea` nitido lanciato mentre una mappa è aperta chiede *mappa o whiteboard?*. Mandala alla whiteboard e resta fuori dalla mappa: tracciata, ma fuori dai piedi, così la frontier della mappa continua a significare "la via verso questa destination".
208
- - **Fallo al volo.** `/trailhead:quick "<text>"` apre un ticket della whiteboard e lo lavora dall'inizio alla fine nella stessa seduta, il motore completo discuss → plan → execute → verify (commit atomici, code review, tutto quanto), tranne che **fa grilling solo se serve e non splitta mai**, e salta ogni passo di book-keeping della mappa. `/trailhead:quick <n>` fa lo stesso per un ticket che esiste già.
208
+ - **Fallo al volo.** `/trailhead:quick "<text>"` apre un ticket della whiteboard e lo lavora dall'inizio alla fine nella stessa seduta, il motore completo discuss → plan → execute → verify (commit atomici, code review, tutto quanto), tranne che **fa grilling solo se serve e non splitta mai**, e salta ogni passo di book-keeping della mappa. Su un pezzo piccolo fa la triage di dimensione e offre di saltare gli step pesanti (solo su un sì esplicito; la colonna correttezza resta sempre), così `quick` resta veloce senza dover impostare `effort: lean`. `/trailhead:quick <n>` fa lo stesso per un ticket che esiste già.
209
209
 
210
210
  Vedi l'intera whiteboard con `/trailhead:whiteboard`. Niente cambia riguardo alla mappa: ne sei semplicemente sceso, hai fatto la cosa, e ci risali quando sei pronto.
211
211
 
@@ -230,7 +230,7 @@ Ogni tipo ha il proprio motore inline: nessuna skill esterna viene invocata.
230
230
  | 🧭 `decision` | una scelta | HITL | diverge le opzioni se non chiare, poi grill per convergere su una |
231
231
  | 🔬 `research` | un fatto | AFK | un subagent su un branch usa-e-getta (l'unico tipo eseguito in parallelo) |
232
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 |
233
- | 🔨 `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 (browser-drive o UAT conversazionale passo-passo) |
233
+ | 🔨 `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
234
  | 🐛 `bug` | codice corretto | HITL/AFK | `repro → diagnose → fix → verify`; un difetto in lavoro chiuso è un ticket *nuovo* (`Regression of:`), non una riapertura |
235
235
  | 🔧 `task` | un cambiamento di stato esterno | HITL/AFK | lavoro manuale che sblocca una decision (provisioning accessi, spostamento dati, iscrizioni) |
236
236
 
@@ -259,11 +259,11 @@ Molte persone (e le loro sessioni di agente) condividono una mappa e la lavorano
259
259
  - **Claim = assegnare a te stesso.** Chi rivendica possiede il ticket dall'inizio alla fine e lo chiude: non c'è approvatore e nessuno da attendere. Su una collisione di claim la sessione si ferma e chiede a te, invece di risolverla silenziosamente.
260
260
  - **Split** un ticket sovradimensionato in figli e supersede l'originale (`/trailhead:split`).
261
261
  - **Pause/resume** tramite un commento di checkpoint `PAUSED`, così qualsiasi sessione può riprendere un ticket.
262
- - **Isola le sessioni concorrenti con i worktree.** Il claiming tiene due sessioni fuori dallo *stesso* ticket, ma due sessioni su ticket *diversi* nello *stesso* clone condividono comunque un working tree. Imposta `isolation: worktree` nell'header delle conventions e ogni ticket in esecuzione gira nel proprio `git worktree` su un branch `trailhead/t<n>`, integrato di nuovo nel trunk al Resolve. Implica un branch per ticket anche sotto `git: main` (git non farà il checkout del trunk due volte), ed è la postura giusta per un **monorepo** (un worktree condivide l'object store, molto più economico di un secondo clone). Un'avvertenza: un worktree isola il *source* ma è un path nuovo senza dipendenze installate, quindi un **package legato a un path** (React Native/Expo, toolchain native, qualsiasi cosa legata a un `node_modules` locale) non buildera da lì. Per questi c'è **`isolation: clone`**: un clone dedicato per-ticket che installa le proprie dipendenze e builda/UATa come l'app vera (più pesante di un worktree, e lo strumento giusto per un submodule Expo/RN). Oppure tieni `isolation: none` e serializza. Altrimenti esegui le sessioni concorrenti in clone separati.
262
+ - **Isola le sessioni concorrenti con i worktree.** Di default (`isolation: auto`) trailhead lo fa per te: al work-start di ogni ticket chiede se ne lavorerai più di uno alla volta e sceglie la modalità in base alla forma del repo. Per forzarla in anticipo, imposta `isolation: worktree` nell'header delle conventions, e ogni ticket in esecuzione gira nel proprio `git worktree` su un branch `trailhead/t<n>`, integrato di nuovo nel trunk al Resolve. Implica un branch per ticket anche sotto `git: main` (git non farà il checkout del trunk due volte), ed è la postura giusta per un **monorepo** (un worktree condivide l'object store, molto più economico di un secondo clone). Un'avvertenza: un worktree isola il *source* ma è un path nuovo senza dipendenze installate, quindi un **package legato a un path** (React Native/Expo, toolchain native, qualsiasi cosa legata a un `node_modules` locale) non buildera da lì. Per questi c'è **`isolation: clone`**: un clone dedicato per-ticket che installa le proprie dipendenze e builda/UATa come l'app vera (più pesante di un worktree, e lo strumento giusto per un submodule Expo/RN). Oppure tieni `isolation: none` e serializza. Altrimenti esegui le sessioni concorrenti in clone separati.
263
263
  - **Un marker session-ticket per la tua statusline.** Quando inizia a lavorare un ticket, trailhead deposita una riga singola, gitignorata, `.trailhead/session-ticket` (`#<n> <title>`) alla radice di lavoro e la pulisce all'handoff. È puramente un hint economico e offline così una statusline (o qualsiasi tool) può mostrare *su quale ticket è questa sessione* senza colpire il tracker; trailhead stesso continua a trattare le Issue come fonte di verità.
264
- - **Ti dà una spinta quando l'isolation non è mai stata scelta.** Se un repo è stato tracciato o adottato prima che impostassi una modalità `isolation:` (quindi assume silenziosamente `none`), la prima volta che vai a lavorare un ticket con un vero rischio di collisione (un altro ticket in corso, o tooling legato a un path nel suo `Scope:`) trailhead fa emergere un suggerimento di una riga della modalità adatta (`worktree` o `clone`) invece di modificare silenziosamente il checkout condiviso. Qualunque cosa decidi, incluso tenere `none`, scrive la chiave `isolation:` nelle conventions così la scelta è registrata e non chiede mai più. Un header che ha già impostato `isolation:` viene lasciato in pace.
264
+ - **Sceglie l'isolation per te di default (`auto`).** Senza un `isolation:` esplicito impostato (il default è `auto`), la prima volta che lavori un ticket trailhead chiede se ne lavorerai più di uno alla volta su questa macchina in questa sessione; se sì usa un `git worktree` (repo semplice) o un `clone` (superrepo con submodule), altrimenti lavora semplicemente nel checkout corrente. Non viene scritto nulla nelle conventions, e puoi comunque forzare `none`/`worktree`/`clone` esplicitamente.
265
265
  - **Dai scope ai ticket per path su un monorepo.** Un ticket può portare una riga `Scope:` che nomina il/i package/dir che tocca: scope disgiunti parallelizzano in sicurezza, e dà scope a build/test e ai commit del worktree limitandoli al package interessato. `/trailhead:adopt` rileva un monorepo e propone sia `isolation: worktree` sia la convenzione `Scope:`. Sotto `isolation: none`, `Scope:` **serializza** anche un'area condivisa: al momento del claim una sessione si trattiene se lo scope di un altro ticket in corso si sovrappone, così due ticket con scope sullo stesso submodule/package non possono girare insieme (è così che tieni due sessioni fuori da un'app legata a un path).
266
- - **I git submodule sono gestiti come repo separati.** Il lavoro che atterra dentro un submodule viene committato *dentro il submodule* più un bump del gitlink nel parent, e (sotto `worktree`) isolato a livello di submodule, non del superproject. Un ticket che copre due submodule resta atomico tramite un singolo commit del parent che bumpa entrambi i gitlink insieme; il suo `Scope:` li nomina entrambi. `/trailhead:adopt` rileva `.gitmodules` e scrive la regola nelle conventions.
266
+ - **I git submodule sono gestiti come repo separati.** Il lavoro che atterra dentro un submodule viene committato *dentro il submodule* (su un branch) e pubblicato prima che un bump del gitlink nel parent lo referenzi. Poiché i worktree del superproject condividono un unico git dir del submodule, un **repo con submodule risolve a `clone`** (sotto il default `auto`, o scalando un `worktree` esplicito), così ogni ticket ottiene uno stato del submodule indipendente; il clone provvede i suoi submodule al setup (`--recurse-submodules`) e il Verify controlla `git status` dentro ogni submodule toccato. Un ticket che copre due submodule resta atomico tramite un singolo commit del parent che bumpa entrambi i gitlink insieme; il suo `Scope:` li nomina entrambi, e lo stesso submodule conta come scope sovrapposto, quindi due ticket su di esso serializzano. `/trailhead:adopt` rileva `.gitmodules` e scrive la regola nelle conventions.
267
267
  - **Lo stato vive sui ticket** (claim, label, commenti; tutti conflict-free); il corpo della mappa è un indice ri-leggibile, non la fonte di verità. Il record durevole di una risoluzione è il commento del ticket stesso.
268
268
  - **Le label sono protette dagli esterni.** Le label `trailhead:*` guidano la mappa, quindi trailhead si fida di una issue etichettata solo quando un collaboratore con accesso in scrittura ha applicato la label e (per un ticket) porta un `Parent:` valido. Qualsiasi altra cosa è messa in quarantena come `trailhead:unverified`, fuori dalla frontier. GitHub già blocca gli utenti senza accesso in scrittura dall'applicare label; contro gli utenti di triage e le automazioni, trailhead può **installare per te la GitHub Action di label guard** (da `templates/trailhead-label-guard.yml`) nella `.github/workflows/` del tuo repo, dove rimuove alla fonte le label `trailhead:*` non autorizzate. Installare un workflow richiede un token con lo scope `workflow`, quindi trailhead esegue `gh auth refresh -s workflow` se il push viene rifiutato.
269
269
  - **Le issue aperte da altri vengono triage-ate, non fidate alla cieca.** `/trailhead:inbox` elenca le issue in ingresso (bug report, richieste, domande che non sono ancora trailhead ticket) e integra quelle valide **sul posto**: riformulandole come ticket, mantenendo l'autorialità di chi ha segnalato, e applicando le label all'adozione. Il resto viene instradato a fog, out-of-scope, duplicato, o needs-info.
@@ -290,7 +290,9 @@ Tre layer: **il più vicino vince**, chiave per chiave (una chiave non impostata
290
290
  - **Global**: `~/.claude/trailhead/config.json`: i tuoi default permanenti su ogni progetto.
291
291
  - **Defaults**: i valori integrati.
292
292
 
293
- `/trailhead:config` esegue un **setup guidato, a menu**: scegli lo scope, poi percorri ogni impostazione (🌐 ticket language · 🧠 models · 🎨 design + approval · 🧪 TDD · 🖥️ acceptance testing · 🧑‍⚖️ plan review · 📊 statusline) come un menu con icone; niente editing manuale del JSON. Ogni passo è chiesto (nessuno saltato), e i **modelli plan ed execute sono sempre due scelte separate e version-pinned**. `config get` stampa la config effettiva unita; `config set <key> <value>` scrive una chiave.
293
+ `/trailhead:config` esegue un **setup guidato, a menu**: scegli lo scope, poi percorri ogni impostazione (⚙️ modo di lavorare (git · release · isolation) · 🌐 ticket language · 🧠 models · 🎨 design + approval · 🧪 TDD · 🖥️ acceptance testing · 🧑‍⚖️ plan review · 📊 statusline) come un menu con icone; niente editing manuale del JSON. Ogni passo è chiesto (nessuno saltato), e i **modelli plan ed execute sono sempre due scelte separate e version-pinned**. `config get` stampa la config effettiva unita; `config set <key> <value>` scrive una chiave.
294
+
295
+ `/trailhead:prune [map]` recupera gli slot di sub-issue di una mappa sotto il cap di 100 di GitHub (elimina gli edge chiusi/orfani, mantiene ogni riferimento). Offerto automaticamente quando una mappa è vicina o al cap, ed eseguibile anche su richiesta.
294
296
 
295
297
  Il passo **📊 statusline** offre di installare la status bar di trailhead per Claude Code: una riga con **model · project · branch · plan usage (`5h %` · reset · `7d %`) · una barra della context-window**, più una **seconda riga con il ticket attivo** (`▸ #N Title`) ogni volta che ne stai lavorando uno, e un flag `⬆ trailhead <version>` quando è disponibile un trailhead più recente (esegui `/trailhead:update`). Il project è sempre il nome del repo principale anche da un checkout isolato, e il branch porta un tag `(WT)` in un worktree o `(C)` in un clone per-ticket (niente sul checkout originale). È un'impostazione globale di Claude Code; se già usi una statusline (es. `ccstatusline`) il setup chiede prima di rimpiazzarla, e lo script espone anche i segmenti `--ticket-only` / `--context-only` / `--usage-only` da inserire in un tool esistente.
296
298
 
@@ -304,12 +306,12 @@ Il passo **📊 statusline** offre di installare la status bar di trailhead per
304
306
  | `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` |
305
307
  | `design.approval` | **`explicit`** \| `auto` | attendi l'approvazione del mockup prima del codice UI, o procedi senza bloccare |
306
308
  | `tdd` | **`seams`** \| `on` \| `off` | come il motore `build` testa |
307
- | `acceptance.browser` | **`auto`** \| `on` \| `off` | guida il browser in Verify, o ti accompagna in una UAT conversazionale (passo-passo in chat, non una checklist da fare da solo) |
309
+ | `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) |
308
310
  | `testing.webapp` / `testing.url` | bool / URL | è browser-drivabile, e dove |
309
311
  | `plan_review` | **`off`** \| `on` \| CLI list | invia i PLAN di `build` ad AI CLI esterne (Gemini, Codex, …) per un secondo parere e converge sulle loro obiezioni |
310
312
  | `plan_review.rounds` | integer (**`2`**) | numero massimo di round converge-and-re-review |
311
313
  | `code_review.rounds` | integer (**`2`**) | numero massimo di round fix-and-re-review in Code review prima del checkpoint HITL di confine |
312
- | `effort` | **`standard`** \| `lean` | quanto lavora il motore `build`/`bug`/`quick` su una modifica: `standard` esegue ogni step, `lean` fa una triage di dimensione sulle modifiche piccole e offre di saltare quelli pesanti (plan, TDD, cross-AI review, code review completa, verify goal-backward) |
314
+ | `effort` | **`standard`** \| `lean` | quanto lavora il motore `build`/`bug` su una modifica: `standard` esegue ogni step, `lean` fa una triage di dimensione sulle modifiche piccole e offre di saltare quelli pesanti (plan, TDD, cross-AI review, code review completa, verify goal-backward). Governa solo `build`/`bug`: `quick` fa sempre la triage di dimensione, a prescindere da questa chiave |
313
315
 
314
316
  **Models.** Ogni chiave esegue la sua attività come **subagent** sul modello che nomini, così l'intera suddivisione per-attività si applica dentro una singola sessione di lavoro, qualunque modello quella sessione esegua:
315
317
 
@@ -339,16 +341,17 @@ Se l'MCP della modalità hosted non è connesso, trailhead **offre di installarl
339
341
 
340
342
  ## 🪝 Hooks
341
343
 
342
- Oltre alle istruzioni della skill, trailhead include sei hook propri (in `hooks/`, self-contained via `${CLAUDE_PLUGIN_ROOT}`) che *impongono* le parti della disciplina che non ci si dovrebbe fidare che il modello ricordi:
344
+ Oltre alle istruzioni della skill, trailhead include sette hook propri (in `hooks/`, self-contained via `${CLAUDE_PLUGIN_ROOT}`) che *impongono* le parti della disciplina che non ci si dovrebbe fidare che il modello ricordi:
343
345
 
344
346
  - **Commit guard** (`PreToolUse` su `git commit`): blocca in modo netto un commit il cui subject non è [Conventional Commits](https://www.conventionalcommits.org), e blocca in modo netto qualsiasi trailer `Co-Authored-By`. Questo rispecchia la validazione dei commit di GSD.
345
347
  - **Secret guard** (`PreToolUse` sulle scritture `gh` di issue/PR): trailhead pubblica molto sul tracker (corpi dei ticket, commenti del motore, issue codebase/conventions, risoluzioni). Questo scansiona ciò che una scrittura `gh issue`/`gh pr`/`gh api` sta per pubblicare (l'inline `--body`, un heredoc, o un `--body-file`) e lo **blocca in modo netto** se corrisponde a un pattern di credenziale (chiavi private, token GitHub/AWS/OpenAI/Slack/Google/Stripe, JWT, o un `password`/`secret`/`token=…` hardcoded). Il blocco è un **segnale di pulisci-e-riprova, non un vicolo cieco**: trailhead redige il valore segnalato sul posto (`<REDACTED>` o un riferimento a env-var) e ripubblica automaticamente, così il contenuto pulito atterra comunque: il segreto semplicemente non raggiunge mai il tracker. Resta un *blocco* fail-safe anziché una silenziosa auto-riscrittura di proposito: un blocco non fa mai leak, mentre una redazione che silenziosamente non è stata applicata sì. Non scansiona mai le letture, solo le scritture in uscita.
346
348
  - **Install guard** (`PreToolUse` sulle installazioni di pacchetti): una barriera anti-slopsquatting. Blocca in modo netto un'installazione da registry pubblico (npm / PyPI / crates.io / Go / RubyGems) finché il pacchetto non è verificato (similarità typosquat a nomi popolari, età / prima apparizione, numero di download, metadati del repository presenti e coerenti); una volta verificato, ri-esegui il comando esatto con il prefisso `TRAILHEAD_VERIFIED_INSTALL=1` per procedere. Il motore Execute / Fix porta lo stesso block-and-ask.
347
- - **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.
349
+ - **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()`.
350
+ - **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.
348
351
  - **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.
349
352
  - **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.
350
353
 
351
- Tutti e sei sono crash-safe (qualsiasi errore → allow) e attivi ogni volta che il plugin è installato. I guard di commit, secret, install, search 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, e niente `.env`/`.secrets` leggibili sono regole permanenti di trailhead. Per fare opt-out, disabilita gli hook del plugin nelle impostazioni di Claude Code.
354
+ Tutti e sette 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.
352
355
 
353
356
  ---
354
357
 
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-21%20verbs-0e8a16?style=flat-square) ![guardrails](https://img.shields.io/badge/guardrails-6%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-22%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
 
@@ -37,7 +37,7 @@ The design choices that fall out of it:
37
37
  - **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
38
  - **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
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
- - **Isolation for concurrent sessions on one machine.** Two agents editing one checkout corrupt each other, so the `isolation:` convention gives each in-flight ticket its own **`git worktree`** (or a full **`clone`** for path-bound apps a worktree can't build); disjoint `Scope:` lines let non-overlapping work run in parallel and serialise only where it truly overlaps.
40
+ - **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
41
  - **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
42
  - **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.
43
43
  - **Outsiders and trust.** Issues opened by other people flow through an **inbox** that reframes the worthwhile ones into tickets *in place* (keeping the reporter's authorship), while a **trust and provenance** guard quarantines `trailhead:*`-labelled issues from untrusted sources until a maintainer adopts them, backed by a repo-side label-guard workflow.
@@ -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`), 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 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.
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
 
@@ -94,7 +94,7 @@ As tickets resolve, the fog clears: questions that were too vague to phrase beco
94
94
  A map is scoped to **one** effort, so knowledge that belongs to the *repo* (not to any single map) lives in **repo-scoped anchor issues**, created once and shared by every map (each map's Notes just links them, so nothing is stranded when a map finishes):
95
95
 
96
96
  - **`trailhead:codebase`**: the distilled codebase map (architecture, stack, conventions, risks, test/build), written once at adopt and refreshed only on major drift.
97
- - **`trailhead:conventions`**: the project's **way of working**, readable by everyone: a small machine-read header the engine obeys (`git: main|pr`, `release: command|auto`, `isolation: none|worktree|clone`) over human prose. `/trailhead:adopt` and `:new` ask for it up front. `isolation: worktree` gives each executing ticket its own `git worktree` + branch (concurrent sessions on one clone never share a working tree); `isolation: clone` gives it a dedicated clone instead, for path-bound apps a worktree can't build.
97
+ - **`trailhead:conventions`**: the project's **way of working**, readable by everyone: a small machine-read header the engine obeys (`git: main|pr`, `release: command|auto`, `isolation: auto|none|worktree|clone`) over human prose. `/trailhead:adopt` and `:new` ask for it up front. `isolation: auto` (the default) decides per ticket at work-start: it asks whether you'll work more than one ticket at once and, if so, uses a `git worktree` (plain repo) or a `clone` (submodule superrepo); otherwise it works in the current checkout. `isolation: worktree` gives each executing ticket its own `git worktree` + branch (concurrent sessions on one clone never share a working tree); `isolation: clone` gives it a dedicated clone instead, for path-bound apps a worktree can't build and for submodule-bearing superrepos (where `worktree` is escalated to `clone`).
98
98
  - **`trailhead:dashboard`**: the pinned index of the whole surface, a link to every open map (with GitHub's native progress bars), the whiteboard, and live counts (inbox, whiteboard frontier). Refreshed when a map is charted or exhausted, when a whiteboard ticket is born or resolved (it has no native progress bar, so the dashboard is the only place it shows; a `quick`-born ticket, worked and resolved in one sitting, refreshes at its handoff or pause, not at creation), and on demand via `/trailhead:dashboard`.
99
99
 
100
100
  These three fill GitHub's **3 pinned-issue slots**, so a repo's trailhead anchors stay one click away; **maps themselves are never pinned** (they're indexed by the dashboard instead, since the "active" map is per-checkout local state, not a repo-global fact). Project *config* (models, TDD, design…) is separate again, a plain `.trailhead/config.json` file at the repo root, never in an issue.
@@ -131,7 +131,7 @@ Initial frontier = ①③④, three people can start in parallel. Then:
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
133
  - **④ prototype** → a rough login screen (on the configured claude.ai/design project), approved before UI code.
134
- - With ②③④ closed, **⑤ build** graduates → discuss → plan → execute (atomic commits, TDD at the auth seams) → verify (tests + code review + acceptance: the agent drives the browser through the real login flow).
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
 
137
137
  ```
@@ -174,7 +174,7 @@ Every verb is also a namespaced command (`/trailhead:new`, `/trailhead:work`, `/
174
174
  | `/trailhead:new [idea]` | chart a new map from a loose idea |
175
175
  | `/trailhead:adopt` | adopt an existing project (map the codebase once, then go lean) |
176
176
  | `/trailhead:work [ticket]` | work the next frontier ticket, or the one you name |
177
- | `/trailhead:quick [ticket \| "text"]` | work one ticket whole, off the map: opens a whiteboard ticket from `"text"` (or takes `<n>`), runs the full engine, grills only if needed, never splits |
177
+ | `/trailhead:quick [ticket \| "text"]` | work one ticket whole, never splitting: `"text"` opens a whiteboard ticket off the map; `<n>` takes an existing ticket (a map ticket stays on its map), runs the full engine, grills only if needed |
178
178
  | `/trailhead:whiteboard` | show the whiteboard: the loose (map-less) tickets and their frontier |
179
179
  | `/trailhead:inbox [issue]` | triage issues opened by others and integrate the good ones into the map |
180
180
  | `/trailhead:resume [ticket]` | resume a paused ticket from its `PAUSED` checkpoint |
@@ -205,7 +205,7 @@ When a map is open, a capture that produces a ticket (`todo`/`bug`/`seed`/a shar
205
205
  Deep in a map, something unrelated surfaces: a bug in another area, a chore, a quick idea you want to act on now. Forcing it onto the map's frontier pollutes the map; charting a whole new map for it is overkill. That is what the **whiteboard** is for, loose map-less work, and two moves keep you from getting stuck:
206
206
 
207
207
  - **Capture it aside.** A `todo`/`bug`/`seed`/sharp `idea` fired while a map is open asks *map or whiteboard?*. Send it to the whiteboard and it stays off the map: tracked, but out of the way, so the map's frontier keeps meaning "the way to this destination".
208
- - **Do it on the fly.** `/trailhead:quick "<text>"` opens a whiteboard ticket and works it end to end in the same sitting, the full discuss → plan → execute → verify engine (atomic commits, code review, the lot), except it **grills only if needed and never splits**, and skips every map book-keeping step. `/trailhead:quick <n>` does the same for a ticket that already exists.
208
+ - **Do it on the fly.** `/trailhead:quick "<text>"` opens a whiteboard ticket and works it end to end in the same sitting, the full discuss → plan → execute → verify engine (atomic commits, code review, the lot), except it **grills only if needed and never splits**, and skips every map book-keeping step. On a small piece it size-triages and offers to skip the heavy steps (on an explicit yes only; the correctness spine always stays), so `quick` stays fast without setting `effort: lean`. `/trailhead:quick <n>` does the same for a ticket that already exists.
209
209
 
210
210
  See the whole whiteboard with `/trailhead:whiteboard`. Nothing about the map changes: you just stepped off it, did the thing, and step back on when you're ready.
211
211
 
@@ -230,7 +230,7 @@ Each type has its own inline engine: no external skill is invoked.
230
230
  | 🧭 `decision` | a choice | HITL | diverge the options if unclear, then grill to converge on one |
231
231
  | 🔬 `research` | a fact | AFK | a subagent on a throwaway branch (the only type run in parallel) |
232
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 |
233
- | 🔨 `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 (browser-drive or conversational step-by-step UAT) |
233
+ | 🔨 `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
234
  | 🐛 `bug` | corrected code | HITL/AFK | `repro → diagnose → fix → verify`; a defect in closed work is a *new* ticket (`Regression of:`), not a reopen |
235
235
  | 🔧 `task` | an external state change | HITL/AFK | manual work that unblocks a decision (provision access, move data, sign up) |
236
236
 
@@ -259,11 +259,11 @@ Many people (and their agent sessions) share one map and work it concurrently:
259
259
  - **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.
260
260
  - **Split** an oversized ticket into children and supersede the original (`/trailhead:split`).
261
261
  - **Pause/resume** via a `PAUSED` checkpoint comment, so any session can pick a ticket back up.
262
- - **Isolate concurrent sessions with worktrees.** Claiming keeps two sessions off the *same* ticket, but two sessions on *different* tickets in the *same* clone still share one working tree. 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.
262
+ - **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.
263
263
  - **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 purely 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.
264
- - **It nudges you when isolation was never chosen.** If a repo was charted or adopted before you set an `isolation:` mode (so it silently defaults to `none`), the first time you go to work a ticket with a real collision risk (another ticket in progress, or path-bound tooling in its `Scope:`) trailhead surfaces a one-line suggestion of the fitting mode (`worktree` or `clone`) instead of quietly editing the shared checkout. Whatever you decide, including keeping `none`, it writes the `isolation:` key into the conventions so the choice is recorded and it never asks again. A header that already set `isolation:` is left alone.
264
+ - **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.
265
265
  - **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).
266
- - **Git submodules are handled as separate repos.** Work that lands inside a submodule is committed *inside the submodule* plus a gitlink bump in the parent, and (under `worktree`) isolated at the submodule, not the superproject. A ticket spanning two submodules stays atomic via a single parent commit that bumps both gitlinks together; its `Scope:` names both. `/trailhead:adopt` detects `.gitmodules` and writes the rule into the conventions.
266
+ - **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.
267
267
  - **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.
268
268
  - **Labels are protected from outsiders.** `trailhead:*` labels drive the map, so trailhead trusts a labelled issue only when a write-access collaborator applied the label and (for a ticket) it carries a valid `Parent:`. Anything else is quarantined as `trailhead:unverified`, off the frontier. GitHub already blocks non-write users from labelling; against triage users and automations, trailhead can **install the label guard GitHub Action for you** (from `templates/trailhead-label-guard.yml`) into your repo's `.github/workflows/`, where it strips unauthorized `trailhead:*` labels at the source. Installing a workflow needs a token with the `workflow` scope, so trailhead runs `gh auth refresh -s workflow` if the push is rejected.
269
269
  - **Issues opened by others are triaged, not trusted blindly.** `/trailhead:inbox` lists inbound issues (bug reports, requests, questions that aren't yet trailhead tickets) and integrates the worthwhile ones **in place**: reframing them as tickets, keeping the reporter's authorship, and applying the labels on adoption. The rest routes to fog, out-of-scope, duplicate, or needs-info.
@@ -290,7 +290,9 @@ Three layers: **nearest wins**, key by key (a key unset at one layer inherits th
290
290
  - **Global**: `~/.claude/trailhead/config.json`: your standing defaults across every project.
291
291
  - **Defaults**: the built-in values.
292
292
 
293
- `/trailhead:config` runs a **guided, menu-driven setup**: pick the scope, then walk each setting (🌐 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.
293
+ `/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.
294
+
295
+ `/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.
294
296
 
295
297
  The **📊 statusline** step offers to install trailhead's Claude Code status bar: one line with **model · project · branch · plan usage (`5h %` · reset · `7d %`) · a context-window bar**, plus a **second line with the active ticket** (`▸ #N Title`) whenever you're working one, and a `⬆ trailhead <version>` flag when a newer trailhead is available (run `/trailhead:update`). The project is always the main repo's name even from an isolated checkout, and the branch carries a `(WT)` tag in a worktree or `(C)` in a per-ticket clone (nothing on the original checkout). It's a global Claude Code setting; if you already run a statusline (e.g. `ccstatusline`) the setup asks before replacing it, and the script also exposes `--ticket-only` / `--context-only` / `--usage-only` segments to slot into an existing tool.
296
298
 
@@ -304,12 +306,12 @@ The **📊 statusline** step offers to install trailhead's Claude Code status ba
304
306
  | `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` |
305
307
  | `design.approval` | **`explicit`** \| `auto` | wait for mockup approval before UI code, or proceed without blocking |
306
308
  | `tdd` | **`seams`** \| `on` \| `off` | how the `build` engine tests |
307
- | `acceptance.browser` | **`auto`** \| `on` \| `off` | drive the browser in Verify, or walk you through a conversational UAT (step by step in chat, not a checklist to self-serve) |
309
+ | `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) |
308
310
  | `testing.webapp` / `testing.url` | bool / URL | is it browser-drivable, and where |
309
311
  | `plan_review` | **`off`** \| `on` \| CLI list | send `build` PLANs to external AI CLIs (Gemini, Codex, …) for a second opinion and converge on their concerns |
310
312
  | `plan_review.rounds` | integer (**`2`**) | max converge-and-re-review rounds |
311
313
  | `code_review.rounds` | integer (**`2`**) | max fix-and-re-review rounds in Code review before its boundary HITL checkpoint |
312
- | `effort` | **`standard`** \| `lean` | how hard the `build`/`bug`/`quick` 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) |
314
+ | `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 |
313
315
 
314
316
  **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:
315
317
 
@@ -339,16 +341,17 @@ If a hosted mode's MCP isn't connected, trailhead **offers to install it once**
339
341
 
340
342
  ## 🪝 Hooks
341
343
 
342
- Beyond the skill's instructions, trailhead ships six 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:
344
+ Beyond the skill's instructions, trailhead ships seven 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:
343
345
 
344
346
  - **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.
345
347
  - **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.
346
348
  - **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.
347
- - **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.
349
+ - **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.
350
+ - **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.
348
351
  - **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.
349
352
  - **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.
350
353
 
351
- All six are crash-safe (any error → allow) and active whenever the plugin is installed. The commit, secret, install, search, 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, and no readable `.env`/`.secrets` are trailhead's standing rules. To opt out, disable the plugin's hooks in your Claude Code settings.
354
+ All seven 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.
352
355
 
353
356
  ---
354
357
 
@@ -93,8 +93,12 @@ trailhead is a native Codex skill. Invoke it as \`$trailhead <verb>\` (e.g. \`$t
93
93
  ## B. This IS the skill; the engine is split across sibling skill dirs
94
94
  Codex loaded this SKILL.md because you invoked \`$trailhead\` (or a \`$trailhead-<verb>\` / \`$trailhead-<cluster>\` entry); there is no separate Skill tool to call. The engine is split across sibling skill dirs under \`~/.codex/skills/\`: the dispatcher (\`trailhead/\`), five cluster skills (\`trailhead-chart/\`, \`trailhead-work/\`, \`trailhead-view/\`, \`trailhead-capture/\`, \`trailhead-manage/\`), and the shared core (\`_shared/\`, no SKILL.md). Read every referenced file **relative to the SKILL.md you are currently in**: a \`references/*.md\` from this skill's own dir, and the shared core (\`../_shared/*.md\`, or \`../../_shared/*.md\` from a \`references/\` file) from the sibling \`_shared/\` dir. Where the dispatcher's routing says "call the Skill tool with skill name \`trailhead-<cluster>\` and arguments \`<verb> <rest>\`", there is no Skill tool on Codex: **Read \`../trailhead-<cluster>/SKILL.md\` (the sibling dir) and follow it**, treating \`<verb> <rest>\` as its arguments.
95
95
 
96
- ## C. AskUserQuestion -> request_user_input
97
- Where the engine uses \`AskUserQuestion\`, use Codex's \`request_user_input\`: map \`header\`->\`header\`, \`question\`->\`question\`, each option \`{label, description}\`; generate an \`id\` from the header (lowercase, spaces->underscores). Codex has no \`multiSelect\`: present sequential single-selects, or a numbered freeform list and ask for comma-separated numbers. If \`request_user_input\` is unavailable, present the choices as a plain-text numbered list and STOP for the user's reply; do not silently pick a default and proceed.
96
+ ## C. AskUserQuestion -> plain-text numbered list (the guaranteed path on Codex)
97
+ Codex's \`request_user_input\` tool only works in **Plan Mode** (Codex's collaboration mode with a plan-review step). trailhead skills run in Codex's **Default mode**: the installer never enables \`collaboration_modes\` / Plan Mode, so \`request_user_input\` is unavailable here. This is a **permanent, known constraint** of running trailhead on Codex, not a conditional "if unavailable" case.
98
+
99
+ Where the engine uses \`AskUserQuestion\`, pose the choice as a **plain-text numbered list**: on Codex this is the **guaranteed default path**, not a rare exception. Render the \`header\` and \`question\` as plain text, then one numbered line per option carrying its \`label\` (and its \`description\`): that is the \`{label, description}\` shape the engine's \`AskUserQuestion\` call-sites use, so nothing is lost in the projection. Codex has no \`multiSelect\`: for a multi-select question ask for comma-separated numbers instead of sequential single-selects. Follow \`_shared/choices.md\`'s own rules for what belongs in the list: when the call-site offers the delegate ("let the agent pick") and/or defer affordances, add them as their own numbered options under the same 4-option cap (fill real options plus applicable affordances up to 4, then push whatever does not fit into one prose line right after the list), so the numbering never diverges from what the engine intends. (You may still attempt \`request_user_input\` once first, since the call is cheap and would work if Plan Mode is ever active; treat any error or non-response from it as expected and fall through to the numbered list without waiting on it.)
100
+
101
+ After presenting the numbered list, STOP and wait: the user's **next message** is the answer to this pending question, not a fresh unrelated turn. Resolve it by matching either the **number typed** (e.g. "2") or the option's **label text** back onto that option, then continue the engine exactly as it would from a native \`AskUserQuestion\` response (e.g. write the chosen config value, or proceed down the chosen branch). If the reply matches no option, ask again rather than guessing. Never silently pick a default and proceed.
98
102
 
99
103
  ## D. Subagents (native multi_agent)
100
104
  Codex has a subagent toolkit: the multi_agent tools (\`spawn_agent\`, \`send_input\`, \`wait_agent\`, \`resume_agent\`, \`close_agent\`), stable and on by default on supported Codex (the installer gates on the floor that guarantees them). Where the engine delegates to a subagent (research, codebase-map, review, plan, execute, debug), spawn a real Codex subagent with these tools and the technique's own protocol (each technique carries its full protocol in-prompt, so no specialised agent is needed; pass the ticket's \`Scope:\` when relevant). The Claude-only Explore-vs-general-purpose split does not apply: Codex has one subagent kind. A subagent runs as a FRESH session that receives a task prompt, not this conversation; run independent ones in parallel (e.g. the codebase-map readers) and \`wait_agent\` on them.
@@ -109,7 +113,7 @@ Codex has a subagent toolkit: the multi_agent tools (\`spawn_agent\`, \`send_inp
109
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>\`).
110
114
 
111
115
  ## F. Lifecycle hooks (native Codex hooks)
112
- 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 search-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, 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. 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, 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, 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.
113
117
 
114
118
  ## G. Design / UI mockups -> local disk
115
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.
@@ -357,17 +361,21 @@ function codexVerbSkillPlan(codexHome, verbs) {
357
361
  // The six guardrail hooks trailhead registers on Codex, as {event, matcher,
358
362
  // command} records. Commands run the copied guard scripts under hooksScriptsDir
359
363
  // via node, matching Claude's `node "<path>"` form. PreToolUse guards (commit,
360
- // secret, install, search, secret-read) can veto; the injection-scanner is a
361
- // PostToolUse advisory; check-update runs at SessionStart. Reused by the
364
+ // secret, install, body, secret-read) can veto; the injection-scanner
365
+ // is a PostToolUse advisory; check-update runs at SessionStart. Reused by the
362
366
  // installer, which merges them into ~/.codex/hooks.json (same shape as
363
367
  // Claude's settings.json hooks block).
368
+ // search-guard is deliberately NOT projected here: it is Claude-Code-specific
369
+ // (its only job is to stop a `cd <dir> && grep <relative>` shell shape from
370
+ // arming Claude's `Read()` deny rule under bypass permissions), a permission
371
+ // mechanism Codex does not have.
364
372
  function codexHookEntries(hooksScriptsDir) {
365
373
  const cmd = (name) => `node "${path.join(hooksScriptsDir, name)}"`;
366
374
  return [
367
375
  { event: 'PreToolUse', matcher: 'Bash', command: cmd('trailhead-commit-guard.js') },
368
376
  { event: 'PreToolUse', matcher: 'Bash', command: cmd('trailhead-secret-guard.js') },
369
377
  { event: 'PreToolUse', matcher: 'Bash', command: cmd('trailhead-install-guard.js') },
370
- { event: 'PreToolUse', matcher: 'Bash', command: cmd('trailhead-search-guard.js') },
378
+ { event: 'PreToolUse', matcher: 'Bash', command: cmd('trailhead-body-guard.js') },
371
379
  { event: 'PreToolUse', matcher: 'Read|Bash', command: cmd('trailhead-secret-read-guard.js') },
372
380
  { event: 'PostToolUse', matcher: 'Bash', command: cmd('trailhead-issue-injection-scanner.js') },
373
381
  { event: 'SessionStart', matcher: '', command: cmd('trailhead-check-update.js') },
@@ -2,6 +2,8 @@
2
2
  // Tests for codex-projection.js. Run: node codex-projection.test.js
3
3
  // No framework: plain asserts, mirrors the style of host-descriptor.test.js.
4
4
  const assert = require('assert');
5
+ const fs = require('fs');
6
+ const path = require('path');
5
7
  const {
6
8
  codexLayout,
7
9
  convertToCodex,
@@ -65,6 +67,18 @@ ok('codexLayout codexAgentsDir', layout.codexAgentsDir === '/c/agents');
65
67
  const header = codexSkillAdapterHeader();
66
68
  ok('codexSkillAdapterHeader has adapter tag', header.includes('<codex_skill_adapter>'));
67
69
  ok('codexSkillAdapterHeader mentions request_user_input', header.includes('request_user_input'));
70
+ ok('codexSkillAdapterHeader §C states request_user_input only works in Plan Mode, a permanent constraint since trailhead runs in Default mode',
71
+ header.includes('only works in **Plan Mode**') && header.includes('Default mode') && header.includes('permanent, known constraint'));
72
+ ok('codexSkillAdapterHeader §C makes the plain-text numbered list the guaranteed default path, not a rare exception',
73
+ header.includes('guaranteed default path') && header.includes('not a rare exception'));
74
+ ok('codexSkillAdapterHeader §C closes the loop: the user\'s next message resolves the pending question by number typed or label text',
75
+ header.includes("user's **next message**") && header.includes('label text') && header.includes('number typed'));
76
+ ok('codexSkillAdapterHeader §C uses the real AskUserQuestion {label, description} option shape, not a fictional per-option id',
77
+ header.includes('{label, description}') && !header.includes('{id, label}'));
78
+ ok('codexSkillAdapterHeader §C never silently picks a default and proceeds',
79
+ header.includes('Never silently pick a default and proceed'));
80
+ ok('codexSkillAdapterHeader §C honours choices.md delegate/defer affordances as numbered options under the 4-option cap',
81
+ header.includes('_shared/choices.md') && header.includes('4-option cap'));
68
82
  ok('codexSkillAdapterHeader mentions $trailhead <verb> form', header.includes('$trailhead <verb>'));
69
83
  ok('codexSkillAdapterHeader does not mention the legacy hyphen form', !header.includes('/trailhead-<verb>'));
70
84
  ok('codexSkillAdapterHeader §D describes native multi_agent spawning',
@@ -89,6 +103,10 @@ ok('codexSkillAdapterHeader §D makes background+poll the hard cross-host defaul
89
103
  header.includes('always run this way') && header.includes('hard cross-host default'));
90
104
  ok('codexSkillAdapterHeader §F mentions real Codex hooks', header.includes('real Codex hooks'));
91
105
  ok('codexSkillAdapterHeader §F no longer says Codex has no hook bus', !header.includes('Codex has no hook bus'));
106
+ ok('codexSkillAdapterHeader §F does not list search-guard among the projected PreToolUse guards',
107
+ !header.includes('search-guard and body-guard') && !header.includes('search-guard, body-guard'));
108
+ ok('codexSkillAdapterHeader §F explains search-guard is Claude-Code-specific and not projected to Codex',
109
+ header.includes('search-guard') && header.includes('not projected to Codex'));
92
110
  ok('codexSkillAdapterHeader §H maps plan-review external-CLI Bash timeout/background onto Codex shell',
93
111
  header.includes('## H.') && header.includes('Cross-AI plan review') && header.includes('run_in_background') && header.includes('shell/exec'));
94
112
  ok('codexSkillAdapterHeader §H distinguishes the external-CLI shell path from the §D subagent path',
@@ -150,8 +168,10 @@ ok('codexHookEntries: secret-guard is PreToolUse/Bash', entries.some((e) =>
150
168
  e.event === 'PreToolUse' && e.matcher === 'Bash' && e.command.includes('trailhead-secret-guard.js')));
151
169
  ok('codexHookEntries: install-guard is PreToolUse/Bash', entries.some((e) =>
152
170
  e.event === 'PreToolUse' && e.matcher === 'Bash' && e.command.includes('trailhead-install-guard.js')));
153
- ok('codexHookEntries: search-guard is PreToolUse/Bash', entries.some((e) =>
154
- e.event === 'PreToolUse' && e.matcher === 'Bash' && e.command.includes('trailhead-search-guard.js')));
171
+ ok('codexHookEntries: search-guard is absent (Claude-Code-only, not projected to Codex)',
172
+ entries.every((e) => !e.command.includes('trailhead-search-guard.js')));
173
+ ok('codexHookEntries: body-guard is PreToolUse/Bash', entries.some((e) =>
174
+ e.event === 'PreToolUse' && e.matcher === 'Bash' && e.command.includes('trailhead-body-guard.js')));
155
175
  ok('codexHookEntries: secret-read-guard is PreToolUse/Read|Bash', entries.some((e) =>
156
176
  e.event === 'PreToolUse' && e.matcher === 'Read|Bash' && e.command.includes('trailhead-secret-read-guard.js')));
157
177
  ok('codexHookEntries: injection-scanner is PostToolUse/Bash', entries.some((e) =>
@@ -355,4 +375,28 @@ ok('codexAgentsYaml disallows implicit invocation', agentsYaml.includes('allow_i
355
375
  ok('codexAgentsYaml mentions $trailhead', agentsYaml.includes('$trailhead'));
356
376
  ok('codexAgentsYaml has the display name', agentsYaml.includes('display_name: "Trailhead"'));
357
377
 
378
+ // --- host command forms anchor (#170) ---
379
+ // The source load-first.md must carry a standing rule that anchors every
380
+ // runtime-generated command (handoff, capture confirmation, etc.) to THIS
381
+ // host's command forms, symmetric to the Codex adapter header's own anchor
382
+ // (§A above). Regression for #170: without it, Codex vocabulary salient in
383
+ // this repo's own source (the projection code and its tests) can bleed into
384
+ // a Claude Code session's runtime prose.
385
+ const loadFirstPath = path.join(__dirname, '../../plugins/trailhead/skills/_shared/load-first.md');
386
+ const loadFirstSrc = fs.readFileSync(loadFirstPath, 'utf8');
387
+
388
+ ok('load-first.md carries the host-command-forms anchor',
389
+ loadFirstSrc.includes('Host command forms') &&
390
+ loadFirstSrc.includes('/trailhead:work') &&
391
+ loadFirstSrc.includes('/clear'));
392
+
393
+ ok('host-command-forms anchor is projection-safe (no literal Codex tokens in source)',
394
+ !loadFirstSrc.includes('$trailhead') && !/\/new\b/.test(loadFirstSrc));
395
+
396
+ ok('convertToCodex projects the anchor positives to Codex forms', (() => {
397
+ const out = convertToCodex(loadFirstSrc);
398
+ return out.includes('$trailhead work') && out.includes('/new') &&
399
+ !out.includes('/trailhead:work') && !out.includes('/clear');
400
+ })());
401
+
358
402
  console.log(`✓ codex-projection: ${passed} assertions passed`);