@marcomigozzi/trailhead 0.9.0 → 0.11.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 (37) hide show
  1. package/README.it.md +11 -11
  2. package/README.md +11 -11
  3. package/bin/lib/codex-projection.js +13 -6
  4. package/bin/lib/codex-projection.test.js +45 -3
  5. package/bin/lib/install-verify.js +257 -0
  6. package/bin/lib/install-verify.test.js +379 -0
  7. package/bin/trailhead.js +91 -5
  8. package/bin/trailhead.test.js +588 -4
  9. package/package.json +1 -1
  10. package/plugins/trailhead/.claude-plugin/plugin.json +1 -1
  11. package/plugins/trailhead/commands/quick.md +1 -1
  12. package/plugins/trailhead/hooks/lib/commit-message-check.js +58 -2
  13. package/plugins/trailhead/hooks/lib/commit-message-check.test.js +103 -13
  14. package/plugins/trailhead/hooks/lib/commit-msg-sync.test.js +375 -0
  15. package/plugins/trailhead/hooks/lib/fixtures/commit-msg-a319156 +57 -0
  16. package/plugins/trailhead/hooks/trailhead-secret-read-guard.js +32 -3
  17. package/plugins/trailhead/hooks/trailhead-secret-read-guard.test.js +41 -0
  18. package/plugins/trailhead/skills/_shared/choices.md +3 -0
  19. package/plugins/trailhead/skills/_shared/configuration-reference.md +27 -20
  20. package/plugins/trailhead/skills/_shared/load-first.md +2 -0
  21. package/plugins/trailhead/skills/_shared/principles.md +1 -1
  22. package/plugins/trailhead/skills/_shared/session-handoff.md +8 -6
  23. package/plugins/trailhead/skills/_shared/substrate-commands.md +1 -1
  24. package/plugins/trailhead/skills/_shared/teamwork.md +2 -2
  25. package/plugins/trailhead/skills/_shared/techniques/acceptance-testing.md +13 -4
  26. package/plugins/trailhead/skills/_shared/techniques/code-review.md +15 -3
  27. package/plugins/trailhead/skills/_shared/techniques/grilling.md +1 -1
  28. package/plugins/trailhead/skills/_shared/techniques/prototype.md +1 -1
  29. package/plugins/trailhead/skills/_shared/techniques/verify.md +2 -2
  30. package/plugins/trailhead/skills/_shared/techniques.md +2 -2
  31. package/plugins/trailhead/skills/trailhead-capture/references/capture.md +1 -1
  32. package/plugins/trailhead/skills/trailhead-chart/SKILL.md +1 -1
  33. package/plugins/trailhead/skills/trailhead-work/SKILL.md +11 -10
  34. package/plugins/trailhead/skills/trailhead-work/references/auto.md +3 -1
  35. package/plugins/trailhead/skills/trailhead-work/references/ticket-engines.md +12 -9
  36. package/plugins/trailhead/templates/trailhead-commit-msg +49 -2
  37. package/plugins/trailhead/templates/trailhead-commit-msg-sync.js +171 -0
package/README.it.md CHANGED
@@ -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 sette 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
 
@@ -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
 
@@ -260,7 +260,7 @@ Molte persone (e le loro sessioni di agente) condividono una mappa e la lavorano
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
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
- - **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à.
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. È 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à. Fa anche di più: finché è presente, l'hook git commit-msg richiede che ogni commit porti un trailer `Refs: #<n>` corrispondente.
264
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
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.
@@ -290,7 +290,7 @@ 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
294
 
295
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.
296
296
 
@@ -306,12 +306,12 @@ Il passo **📊 statusline** offre di installare la status bar di trailhead per
306
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` |
307
307
  | `design.approval` | **`explicit`** \| `auto` | attendi l'approvazione del mockup prima del codice UI, o procedi senza bloccare |
308
308
  | `tdd` | **`seams`** \| `on` \| `off` | come il motore `build` testa |
309
- | `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) |
310
310
  | `testing.webapp` / `testing.url` | bool / URL | è browser-drivabile, e dove |
311
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 |
312
312
  | `plan_review.rounds` | integer (**`2`**) | numero massimo di round converge-and-re-review |
313
313
  | `code_review.rounds` | integer (**`2`**) | numero massimo di round fix-and-re-review in Code review prima del checkpoint HITL di confine |
314
- | `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 |
315
315
 
316
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:
317
317
 
@@ -325,7 +325,7 @@ La **sessione principale resta l'orchestratore**. Tiene i momenti interattivi (i
325
325
  Esempio di file di config (la stessa forma funziona per il `.trailhead/config.json` di progetto e il `~/.claude/trailhead/config.json` globale):
326
326
 
327
327
  ```json
328
- { "ticket": { "language": "en" }, "models": { "plan": "claude-opus-4-8", "execute": "claude-sonnet-5" }, "tdd": "seams", "acceptance": { "browser": "auto" } }
328
+ { "ticket": { "language": "en" }, "models": { "plan": "claude-opus-5-5", "execute": "claude-sonnet-5-5" }, "tdd": "seams", "acceptance": { "browser": "auto" } }
329
329
  ```
330
330
 
331
331
  **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`:
@@ -346,7 +346,7 @@ Oltre alle istruzioni della skill, trailhead include sette hook propri (in `hook
346
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.
347
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.
348
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.
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.
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
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.
351
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.
352
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.
package/README.md CHANGED
@@ -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 seven 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
 
@@ -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
 
@@ -260,7 +260,7 @@ Many people (and their agent sessions) share one map and work it concurrently:
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
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
- - **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.
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 a cheap, offline hint so a statusline (or any tool) can show *which ticket this session is on* without hitting the tracker; trailhead itself still treats the Issues as the source of truth. It also does more than that: while it's present, the git commit-msg hook requires every commit to carry a `Refs: #<n>` trailer matching it.
264
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
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.
@@ -290,7 +290,7 @@ 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
294
 
295
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.
296
296
 
@@ -306,12 +306,12 @@ The **📊 statusline** step offers to install trailhead's Claude Code status ba
306
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` |
307
307
  | `design.approval` | **`explicit`** \| `auto` | wait for mockup approval before UI code, or proceed without blocking |
308
308
  | `tdd` | **`seams`** \| `on` \| `off` | how the `build` engine tests |
309
- | `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) |
310
310
  | `testing.webapp` / `testing.url` | bool / URL | is it browser-drivable, and where |
311
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 |
312
312
  | `plan_review.rounds` | integer (**`2`**) | max converge-and-re-review rounds |
313
313
  | `code_review.rounds` | integer (**`2`**) | max fix-and-re-review rounds in Code review before its boundary HITL checkpoint |
314
- | `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 |
315
315
 
316
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:
317
317
 
@@ -325,7 +325,7 @@ The **main session stays the orchestrator**. It holds the interactive moments (a
325
325
  Example config file (the same shape works for the project `.trailhead/config.json` and the global `~/.claude/trailhead/config.json`):
326
326
 
327
327
  ```json
328
- { "ticket": { "language": "en" }, "models": { "plan": "claude-opus-4-8", "execute": "claude-sonnet-5" }, "tdd": "seams", "acceptance": { "browser": "auto" } }
328
+ { "ticket": { "language": "en" }, "models": { "plan": "claude-opus-5-5", "execute": "claude-sonnet-5-5" }, "tdd": "seams", "acceptance": { "browser": "auto" } }
329
329
  ```
330
330
 
331
331
  **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`:
@@ -346,7 +346,7 @@ Beyond the skill's instructions, trailhead ships seven of its own hooks (in `hoo
346
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.
347
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.
348
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.
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.
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
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.
351
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.
352
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.
@@ -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, search-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. 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.
@@ -354,20 +358,23 @@ function codexVerbSkillPlan(codexHome, verbs) {
354
358
  }
355
359
 
356
360
  // --- codexHookEntries -----------------------------------------------------
357
- // The seven guardrail hooks trailhead registers on Codex, as {event, matcher,
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, body, secret-read) can veto; the injection-scanner
364
+ // secret, install, body, secret-read) can veto; the injection-scanner
361
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') },
371
378
  { event: 'PreToolUse', matcher: 'Bash', command: cmd('trailhead-body-guard.js') },
372
379
  { event: 'PreToolUse', matcher: 'Read|Bash', command: cmd('trailhead-secret-read-guard.js') },
373
380
  { event: 'PostToolUse', matcher: 'Bash', command: cmd('trailhead-issue-injection-scanner.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',
@@ -143,15 +161,15 @@ ok('codexVerbSkillPlan: null verbs yields nothing', codexVerbSkillPlan('/c', nul
143
161
 
144
162
  // --- codexHookEntries ---
145
163
  const entries = codexHookEntries('/h');
146
- ok('codexHookEntries returns 8 entries', Array.isArray(entries) && entries.length === 8);
164
+ ok('codexHookEntries returns 7 entries', Array.isArray(entries) && entries.length === 7);
147
165
  ok('codexHookEntries: commit-guard is PreToolUse/Bash', entries.some((e) =>
148
166
  e.event === 'PreToolUse' && e.matcher === 'Bash' && e.command.includes('trailhead-commit-guard.js') && e.command.includes('/h')));
149
167
  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')));
155
173
  ok('codexHookEntries: body-guard is PreToolUse/Bash', entries.some((e) =>
156
174
  e.event === 'PreToolUse' && e.matcher === 'Bash' && e.command.includes('trailhead-body-guard.js')));
157
175
  ok('codexHookEntries: secret-read-guard is PreToolUse/Read|Bash', entries.some((e) =>
@@ -357,4 +375,28 @@ ok('codexAgentsYaml disallows implicit invocation', agentsYaml.includes('allow_i
357
375
  ok('codexAgentsYaml mentions $trailhead', agentsYaml.includes('$trailhead'));
358
376
  ok('codexAgentsYaml has the display name', agentsYaml.includes('display_name: "Trailhead"'));
359
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
+
360
402
  console.log(`✓ codex-projection: ${passed} assertions passed`);