@marcomigozzi/trailhead 0.12.1 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.it.md +38 -13
- package/README.md +48 -13
- package/bin/lib/codex-projection.js +18 -11
- package/bin/lib/codex-projection.test.js +39 -11
- package/bin/lib/install-verify.js +10 -5
- package/bin/lib/install-verify.test.js +51 -0
- package/bin/trailhead.js +37 -25
- package/bin/trailhead.test.js +162 -5
- package/package.json +4 -1
- package/plugins/trailhead/.claude-plugin/plugin.json +3 -2
- package/plugins/trailhead/commands/panel.md +10 -0
- package/plugins/trailhead/hooks/hooks.json +3 -18
- package/plugins/trailhead/hooks/lib/body-sources.js +795 -0
- package/plugins/trailhead/hooks/lib/gh-subcommand.js +92 -10
- package/plugins/trailhead/hooks/lib/gh-subcommand.test.js +55 -0
- package/plugins/trailhead/hooks/lib/mockup-link.js +21 -5
- package/plugins/trailhead/hooks/lib/shell-scan.js +496 -64
- package/plugins/trailhead/hooks/lib/shell-scan.test.js +420 -0
- package/plugins/trailhead/hooks/lib/unmodelled.js +345 -0
- package/plugins/trailhead/hooks/lib/unmodelled.test.js +173 -0
- package/plugins/trailhead/hooks/lib/work-state.test.js +252 -0
- package/plugins/trailhead/hooks/mod/README.md +187 -0
- package/plugins/trailhead/hooks/mod/map-pane-view.ts +453 -0
- package/plugins/trailhead/hooks/mod/map-pane.tsx +663 -0
- package/plugins/trailhead/hooks/mod/register.tsx +280 -0
- package/plugins/trailhead/hooks/mod/work-state-prompt.ts +69 -0
- package/plugins/trailhead/hooks/mod/work-state-view.ts +93 -0
- package/plugins/trailhead/hooks/trailhead-bash-guards.js +140 -0
- package/plugins/trailhead/hooks/trailhead-bash-guards.test.js +233 -0
- package/plugins/trailhead/hooks/trailhead-body-guard.js +145 -61
- package/plugins/trailhead/hooks/trailhead-body-guard.test.js +224 -3
- package/plugins/trailhead/hooks/trailhead-commit-guard.js +41 -14
- package/plugins/trailhead/hooks/trailhead-commit-guard.test.js +2 -1
- package/plugins/trailhead/hooks/trailhead-install-guard.js +22 -12
- package/plugins/trailhead/hooks/trailhead-install-guard.test.js +2 -1
- package/plugins/trailhead/hooks/trailhead-mockup-link-guard.js +48 -11
- package/plugins/trailhead/hooks/trailhead-mockup-link-guard.test.js +79 -7
- package/plugins/trailhead/hooks/trailhead-mockup-link-stop.test.js +1 -1
- package/plugins/trailhead/hooks/trailhead-search-guard.js +19 -9
- package/plugins/trailhead/hooks/trailhead-search-guard.test.js +2 -1
- package/plugins/trailhead/hooks/trailhead-secret-guard.js +541 -57
- package/plugins/trailhead/hooks/trailhead-secret-guard.test.js +1000 -4
- package/plugins/trailhead/hooks/trailhead-secret-read-guard.js +171 -6
- package/plugins/trailhead/hooks/trailhead-secret-read-guard.test.js +170 -0
- package/plugins/trailhead/skills/_shared/choices.md +3 -1
- package/plugins/trailhead/skills/_shared/configuration-reference.md +5 -4
- package/plugins/trailhead/skills/_shared/configuration.md +1 -1
- package/plugins/trailhead/skills/_shared/multi-map.md +1 -1
- package/plugins/trailhead/skills/_shared/session-handoff.md +1 -1
- package/plugins/trailhead/skills/_shared/teamwork.md +4 -2
- package/plugins/trailhead/skills/_shared/techniques/acceptance-testing.md +1 -1
- package/plugins/trailhead/skills/_shared/techniques/prototype.md +15 -16
- package/plugins/trailhead/skills/_shared/techniques.md +1 -1
- package/plugins/trailhead/skills/trailhead/SKILL.md +2 -2
- package/plugins/trailhead/skills/trailhead-work/SKILL.md +3 -2
- package/plugins/trailhead/skills/trailhead-work/references/auto.md +2 -0
- package/plugins/trailhead/skills/trailhead-work/references/ticket-engines.md +3 -1
- package/plugins/trailhead/templates/trailhead-work-state.js +199 -0
- package/plugins/trailhead/tests/map-pane-ui.test.ts +1374 -0
- package/plugins/trailhead/tests/map-pane-view.test.ts +565 -0
- package/plugins/trailhead/tests/mod.test.ts +14 -0
- package/plugins/trailhead/tests/work-state-prompt-hook.test.ts +207 -0
- package/plugins/trailhead/tests/work-state-prompt.test.ts +109 -0
- package/plugins/trailhead/tests/work-state-ui.test.ts +365 -0
- package/plugins/trailhead/tests/work-state-view.test.ts +86 -0
- package/plugins/trailhead/types/index.d.ts +69 -0
package/README.it.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
🇬🇧 [English](README.md) · 🇮🇹 **Italiano**
|
|
8
8
|
|
|
9
|
-
[](https://trailhead.marcomigozzi.it) [](https://github.com/ToRvaLDz/trailhead/actions/workflows/site-ci.yml) [](https://www.npmjs.com/package/@marcomigozzi/trailhead) [](LICENSE)  ](https://trailhead.marcomigozzi.it) [](https://github.com/ToRvaLDz/trailhead/actions/workflows/site-ci.yml) [](https://www.npmjs.com/package/@marcomigozzi/trailhead) [](LICENSE)   
|
|
10
10
|
|
|
11
11
|
</div>
|
|
12
12
|
|
|
@@ -26,7 +26,7 @@ Due approcci al lavoro di progetto guidato dagli agenti risolvono ciascuno una m
|
|
|
26
26
|
|
|
27
27
|
Lavorando su entrambi, uno schema continuava a ripresentarsi: **usa una mappa e un onboarding in stile Wayfinder per decidere la forma del lavoro, poi un motore in stile GSD per costruire ogni pezzo, senza mai lasciare l'issue tracker.** `trailhead` è quello schema trasformato in un'unica skill, una sua interpretazione autonoma di entrambe le idee anziché un wrapper attorno all'una o all'altra.
|
|
28
28
|
|
|
29
|
-
> **trailhead è self-contained** nel senso che ha **zero dipendenze da plugin esterni**: gli agent di trailhead e i built-in dell'host vanno bene, e nessun'altra skill o plugin è richiesta. Wayfinder e GSD sono *ispirazione*, non dipendenze. Ogni tecnica che quei sistemi impacchettano come skill separata (grilling, TDD, debugging sistematico, codebase mapping, code review) è **integrata qui come protocollo inline**. Il core di trailhead non invoca nessun'altra skill; ogni tecnica del motore dispatcha al **proprio agent `trailhead-*`** (`trailhead-executor`, `trailhead-plan`, `trailhead-research`, `trailhead-code-review`, `trailhead-debug`, più `trailhead-fix` e `trailhead-codebase-map`), scritto una volta come file `agents/trailhead-*.md` versionato e **proiettato su ogni host** (su Codex diventa `~/.codex/agents/trailhead-*.toml` generato dalla stessa sorgente). Questi agent sono **thin** (identità + tool policy + un modello guidato dalla config, con il protocollo della tecnica iniettato allo spawn) e girano sul toolkit di subagent nativo dell'host (il tool `Agent` integrato di Claude Code, oppure il toolkit `multi_agent` di Codex). L'unica cosa da installare è trailhead stesso. C'è un'unica eccezione opt-in: la modalità mockup hosted. Quando abilitata, `
|
|
29
|
+
> **trailhead è self-contained** nel senso che ha **zero dipendenze da plugin esterni**: gli agent di trailhead e i built-in dell'host vanno bene, e nessun'altra skill o plugin è richiesta. Wayfinder e GSD sono *ispirazione*, non dipendenze. Ogni tecnica che quei sistemi impacchettano come skill separata (grilling, TDD, debugging sistematico, codebase mapping, code review) è **integrata qui come protocollo inline**. Il core di trailhead non invoca nessun'altra skill; ogni tecnica del motore dispatcha al **proprio agent `trailhead-*`** (`trailhead-executor`, `trailhead-plan`, `trailhead-research`, `trailhead-code-review`, `trailhead-debug`, più `trailhead-fix` e `trailhead-codebase-map`), scritto una volta come file `agents/trailhead-*.md` versionato e **proiettato su ogni host** (su Codex diventa `~/.codex/agents/trailhead-*.toml` generato dalla stessa sorgente). Questi agent sono **thin** (identità + tool policy + un modello guidato dalla config, con il protocollo della tecnica iniettato allo spawn) e girano sul toolkit di subagent nativo dell'host (il tool `Agent` integrato di Claude Code, oppure il toolkit `multi_agent` di Codex). L'unica cosa da installare è trailhead stesso. C'è un'unica eccezione opt-in: la modalità mockup hosted. Quando abilitata, `artifacts` costruisce il mockup come canvas Design **Artifact** su claude.ai tramite il tool `Artifact` integrato in Claude Code (solo Claude Code, niente da installare), con il tuo design system installato sul canvas. Una seconda modalità hosted, `stitch`, usa l'MCP Google Stitch e funziona sia su **Claude Code sia su Codex**, quindi è l'opzione cross-host; se il suo MCP non è connesso, offre un'installazione una tantum e ripiega sui mockup su disco locale se rifiuti. Dove il tool `Artifact` non c'è (Codex, sessioni con API key), offre di passare a `stitch` prima di ripiegare sul disco locale.
|
|
30
30
|
|
|
31
31
|
Le scelte di design che ne derivano:
|
|
32
32
|
|
|
@@ -70,7 +70,7 @@ npx @marcomigozzi/trailhead --dir=<path> # punta a una config dir specifica
|
|
|
70
70
|
```
|
|
71
71
|
Su **Claude Code** copia la skill (+ le sue `references/`), i comandi `/trailhead:*`, gli hook (in `hooks/`, registrati in `settings.json`) incluso un **update check** al SessionStart, e i template di label-guard + statusline, in modo idempotente.
|
|
72
72
|
|
|
73
|
-
Su **Codex CLI** (`--codex`) proietta una skill nativa Codex invocata come **`$trailhead`** (`$trailhead work`, `$trailhead new "idea"`, …), una skill sottile **`$trailhead-<verb>`** per ogni verbo per la discoverability, le sei guardrail come **hook** nativi di Codex (`~/.codex/hooks.json`, abilitando `features.hooks`; la search guard è solo per Claude Code e non è proiettata su Codex), e i pin di modello per-tecnica da **`models.codex.*`** in `~/.codex/agents/`. Serve Codex **0.145.0+** (l'installer fa un gate su `codex --version`). La modalità mockup `
|
|
73
|
+
Su **Codex CLI** (`--codex`) proietta una skill nativa Codex invocata come **`$trailhead`** (`$trailhead work`, `$trailhead new "idea"`, …), una skill sottile **`$trailhead-<verb>`** per ogni verbo per la discoverability, le sei guardrail come **hook** nativi di Codex (`~/.codex/hooks.json`, abilitando `features.hooks`; i quattro guard Bash condividono un solo processo dietro una singola registrazione; la search guard è solo per Claude Code e non è proiettata su Codex), e i pin di modello per-tecnica da **`models.codex.*`** in `~/.codex/agents/`. Serve Codex **0.145.0+** (l'installer fa un gate su `codex --version`). La modalità mockup `artifacts` (il tool `Artifact` di Claude Code) non è disponibile su Codex; lì l'opzione hosted è la modalità cross-host `design: stitch` (MCP Google Stitch), con il disco locale come ripiego.
|
|
74
74
|
|
|
75
75
|
Ri-esegui l'install per aggiornare, oppure esegui **`/trailhead:update`** (Claude) / **`$trailhead update`** (Codex) da dentro l'agente: rileva come trailhead è stato installato e installa la versione più recente dove è sicuro farlo (un `git pull` per un dev-symlink, una ri-esecuzione `npx` per npm, o `/plugin update` per il plugin). Quando esiste una versione più recente, la statusline mostra un flag `⬆ trailhead <version>` (Claude).
|
|
76
76
|
|
|
@@ -130,7 +130,7 @@ Frontier iniziale = ①③④, tre persone possono partire in parallelo. Poi:
|
|
|
130
130
|
- **① research** → un subagent legge la documentazione, restituisce un finding pronto per la decisione, chiude → sblocca ②.
|
|
131
131
|
- **② decision** → dirama le opzioni, grill per scegliere native vs custom, registra il perché.
|
|
132
132
|
- **③ task** → l'agente ti consegna una checklist (servono i *tuoi* account Google/GitHub); tu registri le app, lui registra dove vivono le credenziali.
|
|
133
|
-
- **④ prototype** → una schermata di login grezza (sul
|
|
133
|
+
- **④ prototype** → una schermata di login grezza (sul canvas Artifacts configurato), approvata prima del codice UI.
|
|
134
134
|
- Con ②③④ chiusi, **⑤ build** si diploma → discuss → plan → execute (commit atomici, TDD ai seam dell'auth) → verify (test + code review + acceptance: chiesto chi guida, l'agente va AI-driven e guida il browser attraverso il vero flusso di login).
|
|
135
135
|
- Dopo il rilascio noti un redirect sbagliato in prod → `/trailhead:bug --of ⑤ "GitHub redirect goes to localhost"` → un nuovo ticket che porta `Regression of: ⑤`, lavorato repro → diagnose → fix → verify. ⑤ resta chiuso.
|
|
136
136
|
|
|
@@ -185,6 +185,27 @@ Ogni verbo è anche un comando con namespace (`/trailhead:new`, `/trailhead:work
|
|
|
185
185
|
| `/trailhead:grill [topic]` | esegue una sessione di grilling autonoma su una decision/topic |
|
|
186
186
|
| `/trailhead:map` | mostra la mappa a bassa risoluzione (destination, decisions, frontier, fog) |
|
|
187
187
|
| `/trailhead:dashboard` | mostra la dashboard del repo: l'indice pinnato di ogni mappa aperta, la whiteboard, e i conteggi live |
|
|
188
|
+
| `/trailhead:panel` | apre o chiude il pannello live della mappa: i ticket aperti della mappa attiva per stato, con un pulsante `work` per ogni ticket della frontier (**solo Claude Code**: richiede la mod di trailhead, 2.1.287+) |
|
|
189
|
+
|
|
190
|
+
### 🖥️ A schermo in Claude Code
|
|
191
|
+
|
|
192
|
+
La mod di trailhead (Claude Code 2.1.287+) è inclusa nel plugin e disegna il lavoro accanto alla conversazione. Codex mantiene gli hook dei comandi e non ha un pannello.
|
|
193
|
+
|
|
194
|
+

|
|
195
|
+
|
|
196
|
+
Il pannello della mappa elenca le mappe aperte da consultare, la frontier con un pulsante `work` per ogni ticket che pulisce il contesto e avvia il ticket, chi tiene cosa e cosa è bloccato. Il footer ha `refresh` e `close`. `/trailhead:panel` lo apre e lo chiude, e si apre da solo una volta in fullscreen.
|
|
197
|
+
|
|
198
|
+

|
|
199
|
+
|
|
200
|
+
Un click mostra soltanto una mappa, e `back to active` torna a quella attiva. La mappa attiva cambia quando lavori uno dei suoi ticket.
|
|
201
|
+
|
|
202
|
+

|
|
203
|
+
|
|
204
|
+
Il burn-down mostra i ticket aperti nel tempo, dal primo ticket della mappa a oggi, con chiusi e rimossi contati a parte.
|
|
205
|
+
|
|
206
|
+

|
|
207
|
+
|
|
208
|
+
La pipeline band sopra il prompt e la voce di stato sotto mostrano il ticket in gioco e il suo step.
|
|
188
209
|
|
|
189
210
|
### Capture: zero attrito, una riga di conferma, non risolve nulla
|
|
190
211
|
|
|
@@ -229,7 +250,7 @@ Ogni tipo ha il proprio motore inline: nessuna skill esterna viene invocata.
|
|
|
229
250
|
|---|---|---|---|
|
|
230
251
|
| 🧭 `decision` | una scelta | HITL | diverge le opzioni se non chiare, poi grill per convergere su una |
|
|
231
252
|
| 🔬 `research` | un fatto | AFK | un subagent su un branch usa-e-getta (l'unico tipo eseguito in parallelo) |
|
|
232
|
-
| 🎨 `prototype` | una direzione approvata | HITL | un artefatto grezzo usa-e-getta a cui reagire; le schermate UI passano da qui (disco,
|
|
253
|
+
| 🎨 `prototype` | una direzione approvata | HITL | un artefatto grezzo usa-e-getta a cui reagire; le schermate UI passano da qui (disco, un canvas Artifacts, o Stitch) prima del codice UI |
|
|
233
254
|
| 🔨 `build` | codice funzionante | HITL/AFK | `discuss → plan → execute → verify`: commit atomici, TDD ai seam, **prima un mockup per la UI rivolta all'utente** (tecnica Prototype, gated da `design.approval`), code review + acceptance testing (chiede chi guida, AI o umano, poi browser-drive o UAT conversazionale passo-passo) |
|
|
234
255
|
| 🐛 `bug` | codice corretto | HITL/AFK | `repro → diagnose → fix → verify`; un difetto in lavoro chiuso è un ticket *nuovo* (`Regression of:`), non una riapertura |
|
|
235
256
|
| 🔧 `task` | un cambiamento di stato esterno | HITL/AFK | lavoro manuale che sblocca una decision (provisioning accessi, spostamento dati, iscrizioni) |
|
|
@@ -304,8 +325,9 @@ Il passo **📊 statusline** offre di installare la status bar di trailhead per
|
|
|
304
325
|
|---|---|---|
|
|
305
326
|
| `ticket.language` | un codice ISO 639-1 (**`en`**) | la lingua in cui trailhead **scrive** la sua prosa GitHub e le descrizioni dei commit, disaccoppiata dalla lingua in cui conversa |
|
|
306
327
|
| `models.{plan,execute,research,review,debug,verify,codebase-map}` | un id modello completo **versionato** (**inherit session**) | quale modello esegue ogni attività; `plan` ed `execute` sono sempre impostati separatamente |
|
|
307
|
-
| `design` | **`disk`** \| `
|
|
308
|
-
| `design.surface` | **`canvas`** \| `design-system` |
|
|
328
|
+
| `design` | **`disk`** \| `artifacts` \| `stitch` | dove vanno i mockup UI: HTML usa-e-getta locale, un canvas Design **Artifact** su claude.ai (solo Claude Code), o Google Stitch (cross-host, via l'MCP Stitch). Il vecchio valore `claude.ai/design` viene letto come `artifacts` |
|
|
329
|
+
| `design.surface` | **`canvas`** \| `design-system` | solo `stitch`: un progetto Stitch di schermate (default) o un design system Stitch condiviso |
|
|
330
|
+
| `design.system` | URL di un Design System Artifact | solo `artifacts`: il design system installato sul canvas dei mockup (chiesto alla prima schermata se non impostato) |
|
|
309
331
|
| `design.approval` | **`explicit`** \| `auto` | attendi l'approvazione del mockup prima del codice UI, o procedi senza bloccare |
|
|
310
332
|
| `tdd` | **`seams`** \| `on` \| `off` | come il motore `build` testa |
|
|
311
333
|
| `acceptance.browser` | **`auto`** \| `on` \| `off` | guida il browser in Verify (dopo aver chiesto chi guida, AI o umano); `off` esegue comunque controlli AI-driven via comandi, e ti accompagna in una UAT conversazionale per ciò che solo tu puoi fare (passo-passo in chat, non una checklist da fare da solo) |
|
|
@@ -330,14 +352,13 @@ Esempio di file di config (la stessa forma funziona per il `.trailhead/config.js
|
|
|
330
352
|
{ "ticket": { "language": "en" }, "models": { "plan": "claude-opus-5-5", "execute": "claude-sonnet-5-5" }, "tdd": "seams", "acceptance": { "browser": "auto" } }
|
|
331
353
|
```
|
|
332
354
|
|
|
333
|
-
**Design mockups.** `design: disk` (il default) deposita un mockup HTML statico usa-e-getta accanto al codice e lo collega dal ticket. `design:
|
|
355
|
+
**Design mockups.** `design: disk` (il default) deposita un mockup HTML statico usa-e-getta accanto al codice e lo collega dal ticket. `design: artifacts` costruisce il mockup come **canvas Design Artifact** su claude.ai, tramite il tool `Artifact` integrato in Claude Code (niente da installare), dove lo affini visivamente, lo commenti e lo esporti; `design: stitch` usa l'**MCP Google Stitch** (`claude mcp add --scope user --transport http stitch https://stitch.googleapis.com/mcp`), il generatore UI di Google basato su Gemini che gira sia su **Claude Code sia su Codex** (l'opzione hosted cross-host).
|
|
334
356
|
|
|
335
|
-
|
|
336
|
-
- **`design-system`**: un progetto design-system guidato da **DesignSync** (la skill `/design-sync` più l'MCP `claude-design`), per una libreria di componenti condivisa tenuta in sync.
|
|
357
|
+
Con **`artifacts`**, alla prima schermata UI trailhead aggancia un canvas: **sceglierne uno esistente**, **incollare un URL**, oppure **crearne uno nuovo** (ti chiede il nome), e ne mette in cache l'URL in `design.project`. Ogni schermata (e ogni variante) è una propria tavola, raggruppata in una pagina del canvas per ticket; il design system in `design.system` (quello predefinito del tuo account, o quello che scegli) viene installato sul canvas, così colori, tipografia e componenti sono a portata di mano. I messaggi di approvazione riportano l'URL del canvas più il nome di ogni tavola. Una volta che approvi, trailhead **rilegge** le tavole (nel caso tu le abbia modificate live) prima di scrivere qualsiasi codice UI.
|
|
337
358
|
|
|
338
|
-
|
|
359
|
+
Con **`stitch`**, `design.surface` sceglie un progetto Stitch di schermate (`canvas`, default) o un design system Stitch condiviso (`design-system`); trailhead lo sceglie, incolla o crea allo stesso modo, e offre una volta di installare l'MCP Stitch se non è connesso (se rifiuti, ripiega sul disco locale).
|
|
339
360
|
|
|
340
|
-
|
|
361
|
+
**Passare da claude.ai/design.** Il sito standalone di Claude Design chiude il 14/12/2026 e i suoi design ora vivono in Claude come Artifacts. Una config che dice ancora `design: claude.ai/design` continua a funzionare come `artifacts` (trailhead te lo dice una volta e offre di riscrivere la chiave), ma un progetto claude.ai/design in cache non è riutilizzabile: migra i design system dal banner della pagina Artifacts e lascia che trailhead agganci un nuovo canvas. Dove il tool `Artifact` non c'è (Codex, o una sessione con API key), trailhead offre di passare a `stitch` prima di ripiegare sul disco locale. `design.approval` decide se il build attende il tuo esplicito via libera (`explicit`), o procede subito dopo aver mostrato il mockup (`auto`).
|
|
341
362
|
|
|
342
363
|
---
|
|
343
364
|
|
|
@@ -351,9 +372,13 @@ Oltre alle istruzioni della skill, trailhead include otto hook propri (in `hooks
|
|
|
351
372
|
- **Search guard** (`PreToolUse` su `Bash`): un backstop strutturale per la regola di igiene dei comandi di ricerca (mai `cd` seguito da una lettura/ricerca su percorso relativo). Blocca in modo netto un singolo comando Bash che sia insieme un cambio di directory (`cd`/`pushd`/`env -C`) e, in un'istruzione unita o successiva, una lettura/ricerca su percorso relativo (`grep`/`rg`/`ag`/`cat`/`head`/`tail`/`wc`/`nl`/`sed`/`awk`/`less`/`more`, o `git grep`/`log`/`show`/`diff` senza `-C`), così la forma che fa scattare una regola di deny su `Read()` sotto bypass permissions (ripiegando su un prompt di approvazione manuale invece di girare headless) non viene mai eseguita, anziché essere solo scoraggiata nella prosa dell'agente. Solo Claude Code; non è proiettata su Codex, che non ha una regola di deny su `Read()`.
|
|
352
373
|
- **Body guard** (`PreToolUse` sulle scritture `gh` di body issue/PR): blocca in modo netto una scrittura `gh issue edit` / `gh pr edit` / `gh api` che imposterebbe il body di un issue a vuoto o solo spazi, così un read-modify-write la cui lettura è tornata con un body vuoto/degradato (es. un HTTP 5xx transitorio su `gh issue view --json body`) non può mai cancellare il body esistente.
|
|
353
374
|
- **Secret-read guard** (`PreToolUse` su `Read|Bash`): blocca in modo netto la lettura di `.env`/`.env.*`/`.secrets`, sia tramite il tool Read sia tramite un comando Bash (un operando file, il valore di un'opzione, o un target di redirect `<`). È una decisione `deny` a livello di hook anziché una regola di permesso su `Read()`, quindi i segreti restano illeggibili senza attivare il prompt di approvazione `cd-compound-read` di Claude Code 2.1.259.
|
|
354
|
-
- **Mockup-link guard** (`PreToolUse` su `AskUserQuestion`): rete di sicurezza strutturale per la regola della tecnica Prototype secondo cui la richiesta di approvazione di un mockup porta con sé il link al mockup. Blocca un `AskUserQuestion` che chiede di approvare un mockup (o di scegliere tra varianti) quando il testo della risposta subito prima
|
|
375
|
+
- **Mockup-link guard** (`PreToolUse` su `AskUserQuestion`): rete di sicurezza strutturale per la regola della tecnica Prototype secondo cui la richiesta di approvazione di un mockup porta con sé il link al mockup. Blocca un `AskUserQuestion` che chiede di approvare un mockup (o di scegliere tra varianti) quando né il testo della risposta subito prima né la domanda di approvazione stessa contengono un URL o un path (se il messaggio corrente non è ancora nel transcript, la guardia non può giudicare e lascia passare), così l'agente mette il link cliccabile nel messaggio prima di richiedere. Su Codex, che non ha il tool `AskUserQuestion` e pone le domande come elenco numerato in testo, il gemello `mockup-link-stop` gira su `Stop`: se la risposta finale chiede quell'approvazione senza link, Codex fa proseguire il turno e l'agente riscrive la domanda col link.
|
|
355
376
|
- **Issue injection scanner** (`PostToolUse` sulle letture `gh`): trailhead legge testo di issue/PR/commenti scritto da chiunque abbia accesso al repo, cioè input non fidato. Quando quel testo contiene frasi di prompt-injection, l'hook inietta un avviso che ricorda all'agente di trattarlo come dati, mai come comandi. Solo advisory: non blocca mai.
|
|
356
377
|
|
|
378
|
+
I guard di commit, secret, install, search e body girano in **un solo processo**: una singola registrazione `trailhead-bash-guards.js` li valuta in quell'ordine, così una chiamata Bash paga un solo avvio di node invece di cinque (tre processi hook per chiamata Bash in tutto: l'hook unificato, il secret-read guard e l'injection scanner). Restano cinque guard distinti con verdetti invariati; vince il primo blocco, e un guard che va in errore consente da solo senza fermare gli altri. Anche un guard il cui modulo manca o è rotto viene saltato, ma mai in silenzio: l'hook segnala alla sessione quale guard non ha controllato il comando, così un'installazione parziale non passa inosservata. Su Codex lo stesso hook è registrato con `--guards=commit,secret,install,body`, perché lì non c'è la search guard.
|
|
379
|
+
|
|
380
|
+
I guard secret, body e secret-read **falliscono in modo chiuso sui costrutti shell che non sanno interpretare con certezza** (`eval`, `source` / `.`, un `${ cmd; }` di bash 5.3, `printf -v`, la definizione di una funzione, un alias, una chiave body di `gh api` quotata, un backslash-a-capo dentro una parola): quando uno di questi può nascondere o precedere una scrittura (o sta accanto a un path segreto) il comando viene bloccato con un messaggio "riscrivilo in forma semplice" invece di essere emulato; gli stessi costrutti senza scritture o segreti vicini passano.
|
|
381
|
+
|
|
357
382
|
Tutti e otto sono crash-safe (qualsiasi errore → allow) e attivi ogni volta che il plugin è installato. I guard di commit, secret, install, search, body e secret-read quindi si applicano in **ogni** repo, non solo nei progetti trailhead: intenzionale, dato che i conventional commit, niente `Co-Authored-By`, nessun segreto leakato, nessuna installazione non verificata, niente `cd`-poi-lettura-relativa, nessun body issue cancellato, e niente `.env`/`.secrets` leggibili sono regole permanenti di trailhead. Per fare opt-out, disabilita gli hook del plugin nelle impostazioni di Claude Code.
|
|
358
383
|
|
|
359
384
|
---
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
🇬🇧 **English** · 🇮🇹 [Italiano](README.it.md)
|
|
8
8
|
|
|
9
|
-
[](https://trailhead.marcomigozzi.it) [](https://github.com/ToRvaLDz/trailhead/actions/workflows/site-ci.yml) [](https://www.npmjs.com/package/@marcomigozzi/trailhead) [](LICENSE)  ](https://trailhead.marcomigozzi.it) [](https://github.com/ToRvaLDz/trailhead/actions/workflows/site-ci.yml) [](https://www.npmjs.com/package/@marcomigozzi/trailhead) [](LICENSE)   
|
|
10
10
|
|
|
11
11
|
</div>
|
|
12
12
|
|
|
@@ -26,7 +26,7 @@ Two approaches to agent-driven project work each nail one half of the problem:
|
|
|
26
26
|
|
|
27
27
|
Working across both, one pattern kept recurring: **use a Wayfinder-style map and onboarding to decide the shape of the work, then a GSD-style engine to build each piece, without ever leaving the issue tracker.** `trailhead` is that pattern made into a set of cohesion-clustered skills over a shared core, its own self-contained take on both ideas rather than a wrapper around either.
|
|
28
28
|
|
|
29
|
-
> **trailhead is self-contained** in the sense that it has **zero external-plugin dependencies**: trailhead-owned agents and the host's built-ins are fine, and no other skill or plugin is required. Wayfinder and GSD are *inspiration*, not dependencies. Every technique those systems package as a separate skill (grilling, TDD, systematic debugging, codebase mapping, code review) is **built in here as an inline protocol**. trailhead's core invokes no other skill; each engine technique dispatches to **trailhead's own `trailhead-*` agent** (`trailhead-executor`, `trailhead-plan`, `trailhead-research`, `trailhead-code-review`, `trailhead-debug`, plus `trailhead-fix` and `trailhead-codebase-map`), authored once as a committed `agents/trailhead-*.md` file and **projected to each host** (Codex gets `~/.codex/agents/trailhead-*.toml` generated from that same source). These agents are **thin** (identity + tool policy + a config-driven model, with the technique protocol injected at spawn) and run on the host's native subagent toolkit (Claude Code's built-in `Agent` tool, or Codex's `multi_agent` toolkit). The only thing to install is trailhead itself. There is one opt-in exception: hosted mockup mode. When enabled, `
|
|
29
|
+
> **trailhead is self-contained** in the sense that it has **zero external-plugin dependencies**: trailhead-owned agents and the host's built-ins are fine, and no other skill or plugin is required. Wayfinder and GSD are *inspiration*, not dependencies. Every technique those systems package as a separate skill (grilling, TDD, systematic debugging, codebase mapping, code review) is **built in here as an inline protocol**. trailhead's core invokes no other skill; each engine technique dispatches to **trailhead's own `trailhead-*` agent** (`trailhead-executor`, `trailhead-plan`, `trailhead-research`, `trailhead-code-review`, `trailhead-debug`, plus `trailhead-fix` and `trailhead-codebase-map`), authored once as a committed `agents/trailhead-*.md` file and **projected to each host** (Codex gets `~/.codex/agents/trailhead-*.toml` generated from that same source). These agents are **thin** (identity + tool policy + a config-driven model, with the technique protocol injected at spawn) and run on the host's native subagent toolkit (Claude Code's built-in `Agent` tool, or Codex's `multi_agent` toolkit). The only thing to install is trailhead itself. There is one opt-in exception: hosted mockup mode. When enabled, `artifacts` builds the mockup as a Design canvas **Artifact** on claude.ai through Claude Code's built-in `Artifact` tool (Claude Code only, nothing to install), with your design system installed on the canvas. A second hosted mode, `stitch`, uses the Google Stitch MCP and works on **both Claude Code and Codex**, so it's the cross-host option; if its MCP isn't connected, it offers a one-time install and falls back to local-disk mockups if you decline. Where the `Artifact` tool isn't available (Codex, API-key sessions), it offers to switch to `stitch` before falling to local disk.
|
|
30
30
|
|
|
31
31
|
The design choices that fall out of it:
|
|
32
32
|
|
|
@@ -70,7 +70,7 @@ npx @marcomigozzi/trailhead --dir=<path> # target a specific config dir
|
|
|
70
70
|
```
|
|
71
71
|
On **Claude Code** it copies the skill (+ its `references/`), the `/trailhead:*` commands, the hooks (into `hooks/`, registered in `settings.json`) including a SessionStart **update check**, and the label-guard + statusline templates, idempotently.
|
|
72
72
|
|
|
73
|
-
On **Codex CLI** (`--codex`) it projects a native Codex skill invoked as **`$trailhead`** (`$trailhead work`, `$trailhead new "idea"`, …), one thin **`$trailhead-<verb>`** skill per verb for discoverability, the six guardrails as native Codex **hooks** (`~/.codex/hooks.json`, enabling `features.hooks`; the search guard is Claude-Code-only and isn't projected to Codex), and per-technique subagent model pins from **`models.codex.*`** into `~/.codex/agents/`. Codex **0.145.0+** is required (the installer gates on `codex --version`). The `
|
|
73
|
+
On **Codex CLI** (`--codex`) it projects a native Codex skill invoked as **`$trailhead`** (`$trailhead work`, `$trailhead new "idea"`, …), one thin **`$trailhead-<verb>`** skill per verb for discoverability, the six guardrails as native Codex **hooks** (`~/.codex/hooks.json`, enabling `features.hooks`; the four Bash guards share one process behind a single registration; the search guard is Claude-Code-only and isn't projected to Codex), and per-technique subagent model pins from **`models.codex.*`** into `~/.codex/agents/`. Codex **0.145.0+** is required (the installer gates on `codex --version`). The `artifacts` mockup mode (Claude Code's `Artifact` tool) isn't available on Codex; the cross-host `design: stitch` mode (Google Stitch MCP) is the hosted option there, with local disk as the fallback.
|
|
74
74
|
|
|
75
75
|
Re-run the install to update, or run **`/trailhead:update`** (Claude) / **`$trailhead update`** (Codex) from inside the agent: it detects how trailhead was installed and installs the newer version where that is safe (a `git pull` for a dev-symlink, an `npx` re-run for npm, or `/plugin update` for the plugin). When a newer version exists, the statusline shows a `⬆ trailhead <version>` flag (Claude).
|
|
76
76
|
|
|
@@ -130,7 +130,7 @@ Initial frontier = ①③④, three people can start in parallel. Then:
|
|
|
130
130
|
- **① research** → a subagent reads the docs, returns a decision-ready finding, closes → unblocks ②.
|
|
131
131
|
- **② decision** → diverge the options, grill to choose native vs custom, record the why.
|
|
132
132
|
- **③ task** → the agent hands you a checklist (it needs *your* Google/GitHub accounts); you register the apps, it records where the credentials live.
|
|
133
|
-
- **④ prototype** → a rough login screen (on the configured
|
|
133
|
+
- **④ prototype** → a rough login screen (on the configured Artifacts canvas), approved before UI code.
|
|
134
134
|
- With ②③④ closed, **⑤ build** graduates → discuss → plan → execute (atomic commits, TDD at the auth seams) → verify (tests + code review + acceptance: asked who drives, the agent goes AI-driven and drives the browser through the real login flow).
|
|
135
135
|
- After it ships you spot a wrong redirect in prod → `/trailhead:bug --of ⑤ "GitHub redirect goes to localhost"` → a new ticket carrying `Regression of: ⑤`, worked repro → diagnose → fix → verify. ⑤ stays closed.
|
|
136
136
|
|
|
@@ -185,6 +185,27 @@ Every verb is also a namespaced command (`/trailhead:new`, `/trailhead:work`, `/
|
|
|
185
185
|
| `/trailhead:grill [topic]` | run a standalone grilling session on a decision/topic |
|
|
186
186
|
| `/trailhead:map` | show the low-res map (destination, decisions, frontier, fog) |
|
|
187
187
|
| `/trailhead:dashboard` | show the repo dashboard: the pinned index of every open map, the whiteboard, and live counts |
|
|
188
|
+
| `/trailhead:panel` | open or close the live map panel: the active map's open tickets by state, with a `work` button per frontier ticket (**Claude Code only**: needs the trailhead mod, 2.1.287+) |
|
|
189
|
+
|
|
190
|
+
### 🖥️ On screen in Claude Code
|
|
191
|
+
|
|
192
|
+
The trailhead mod (Claude Code 2.1.287+) ships inside the plugin and draws the work beside the conversation. Codex keeps the command hooks and has no panel.
|
|
193
|
+
|
|
194
|
+

|
|
195
|
+
|
|
196
|
+
The map panel lists the open maps to view, the frontier with a `work` button per ticket that clears the context and starts the ticket, who holds what, and what is blocked. The footer has `refresh` and `close`. `/trailhead:panel` toggles it, and it opens by itself once in fullscreen.
|
|
197
|
+
|
|
198
|
+

|
|
199
|
+
|
|
200
|
+
A click only views a map, and `back to active` returns to the active one. The active map changes when you work one of its tickets.
|
|
201
|
+
|
|
202
|
+

|
|
203
|
+
|
|
204
|
+
The burn-down shows the open tickets over time, from the map's first ticket to now, with done and removed counted apart.
|
|
205
|
+
|
|
206
|
+

|
|
207
|
+
|
|
208
|
+
The pipeline band above the prompt and the status entry under it show the ticket in play and its step.
|
|
188
209
|
|
|
189
210
|
### Capture: zero friction, one confirmation line, resolves nothing
|
|
190
211
|
|
|
@@ -217,6 +238,15 @@ The three that trip people up are **idea, seed, todo**, so here they are spelled
|
|
|
217
238
|
|
|
218
239
|
The two cuts that matter: **idea vs seed** is waiting on *clarity* vs waiting on a *condition* (both land "later", for different reasons); **seed vs todo** is committed *later* vs committed *now*. Below all three sits **`note`** (raw text to remember, maybe never work), and off to the side is **`bug`** (a defect, not a commitment tier). The test for idea vs ticket is always: **can you phrase the question precisely now?** Yes → a ticket (`todo` if takeable, `seed` if gated); no → the fog (`idea`).
|
|
219
240
|
|
|
241
|
+
### Manage: keep the install and the maps healthy
|
|
242
|
+
|
|
243
|
+
| Command | What it does |
|
|
244
|
+
|---|---|
|
|
245
|
+
| `/trailhead:config [get\|set …]` | guided menu setup; `get` prints the effective config, `set <key> <value>` writes one key (see Configuration below) |
|
|
246
|
+
| `/trailhead:update` | check for a newer trailhead and install it where that is safe |
|
|
247
|
+
| `/trailhead:prune [map]` | reclaim a map's sub-issue slots under GitHub's 100-cap (drops closed/orphan edges, keeps references) |
|
|
248
|
+
| `/trailhead:audit [map] [--all]` | audit closed tickets for regressions against today's code, plus duplicate and groupable open ones; read-only until you confirm |
|
|
249
|
+
|
|
220
250
|
---
|
|
221
251
|
|
|
222
252
|
## 🎫 Ticket types and their engines
|
|
@@ -229,7 +259,7 @@ Each type has its own inline engine: no external skill is invoked.
|
|
|
229
259
|
|---|---|---|---|
|
|
230
260
|
| 🧭 `decision` | a choice | HITL | diverge the options if unclear, then grill to converge on one |
|
|
231
261
|
| 🔬 `research` | a fact | AFK | a subagent on a throwaway branch (the only type run in parallel) |
|
|
232
|
-
| 🎨 `prototype` | an approved direction | HITL | a rough throwaway artifact to react to; UI screens go through this (disk,
|
|
262
|
+
| 🎨 `prototype` | an approved direction | HITL | a rough throwaway artifact to react to; UI screens go through this (disk, an Artifacts canvas, or Stitch) before UI code |
|
|
233
263
|
| 🔨 `build` | working code | HITL/AFK | `discuss → plan → execute → verify`: atomic commits, TDD at seams, **a mockup first for user-facing UI** (Prototype technique, gated by `design.approval`), code review + acceptance testing (asks who drives, AI or human, then browser-drive or conversational step-by-step UAT) |
|
|
234
264
|
| 🐛 `bug` | corrected code | HITL/AFK | `repro → diagnose → fix → verify`; a defect in closed work is a *new* ticket (`Regression of:`), not a reopen |
|
|
235
265
|
| 🔧 `task` | an external state change | HITL/AFK | manual work that unblocks a decision (provision access, move data, sign up) |
|
|
@@ -304,8 +334,10 @@ The **📊 statusline** step offers to install trailhead's Claude Code status ba
|
|
|
304
334
|
|---|---|---|
|
|
305
335
|
| `ticket.language` | an ISO 639-1 code (**`en`**) | the language trailhead **writes** its GitHub prose & commit descriptions in, decoupled from the language it converses in |
|
|
306
336
|
| `models.{plan,execute,research,review,debug,verify,codebase-map}` | a full **versioned** model id (**inherit session**) | which model runs each activity; `plan` and `execute` are always set separately |
|
|
307
|
-
| `design` | **`disk`** \| `
|
|
308
|
-
| `design.
|
|
337
|
+
| `design` | **`disk`** \| `artifacts` \| `stitch` | where UI mockups go: local throwaway HTML, a Design canvas **Artifact** on claude.ai (Claude Code only), or Google Stitch (cross-host, via the Stitch MCP). The legacy `claude.ai/design` value is read as `artifacts` |
|
|
338
|
+
| `design.project` | canvas Artifact URL / Stitch project id | `artifacts` and `stitch` only: the canvas or Stitch project the mockups land on; set at the first UI screen (pick, paste or create), not by hand. A leftover claude.ai/design project is not reused |
|
|
339
|
+
| `design.surface` | **`canvas`** \| `design-system` | `stitch` only: a Stitch project of screens (default) or a shared Stitch design system |
|
|
340
|
+
| `design.system` | Design System Artifact URL | `artifacts` only: the design system installed on the mockup canvas (asked at the first screen if unset) |
|
|
309
341
|
| `design.approval` | **`explicit`** \| `auto` | wait for mockup approval before UI code, or proceed without blocking |
|
|
310
342
|
| `tdd` | **`seams`** \| `on` \| `off` | how the `build` engine tests |
|
|
311
343
|
| `acceptance.browser` | **`auto`** \| `on` \| `off` | drive the browser in Verify (after asking who drives, AI or human); `off` still runs AI-driven checks via commands, and walks you through a conversational UAT for anything only you can do (step by step in chat, not a checklist to self-serve) |
|
|
@@ -330,14 +362,13 @@ Example config file (the same shape works for the project `.trailhead/config.jso
|
|
|
330
362
|
{ "ticket": { "language": "en" }, "models": { "plan": "claude-opus-5-5", "execute": "claude-sonnet-5-5" }, "tdd": "seams", "acceptance": { "browser": "auto" } }
|
|
331
363
|
```
|
|
332
364
|
|
|
333
|
-
**Design mockups.** `design: disk` (the default) drops a throwaway static HTML mockup next to the code and links it from the ticket. `design:
|
|
365
|
+
**Design mockups.** `design: disk` (the default) drops a throwaway static HTML mockup next to the code and links it from the ticket. `design: artifacts` builds the mockup as a **Design canvas Artifact** on claude.ai, through Claude Code's built-in `Artifact` tool (nothing to install), where you refine it visually, comment on it and export it; `design: stitch` uses the **Google Stitch MCP** (`claude mcp add --scope user --transport http stitch https://stitch.googleapis.com/mcp`), Google's Gemini-powered UI generator that runs on **both Claude Code and Codex** (the cross-host hosted option).
|
|
334
366
|
|
|
335
|
-
|
|
336
|
-
- **`design-system`**: a design-system project driven by **DesignSync** (the `/design-sync` skill plus the `claude-design` MCP), for a shared component library kept in sync.
|
|
367
|
+
With **`artifacts`**, on the first UI screen trailhead attaches a canvas: **pick an existing one**, **paste a URL**, or **create a new one** (it asks you for the name), and caches its URL in `design.project`. Each screen (and each variant) is its own artboard, grouped in a canvas page per ticket; the design system in `design.system` (your account's default one, or the one you pick) is installed on the canvas so its colours, type and components are at hand. Approval messages carry the canvas URL plus the name of each artboard. Once you approve, trailhead **re-fetches** the artboards (in case you edited them live) before writing any UI code.
|
|
337
368
|
|
|
338
|
-
|
|
369
|
+
With **`stitch`**, `design.surface` picks a Stitch project of screens (`canvas`, default) or a shared Stitch design system (`design-system`); trailhead picks, pastes or creates it the same way, and offers once to install the Stitch MCP if it isn't connected (decline and it falls back to local disk).
|
|
339
370
|
|
|
340
|
-
|
|
371
|
+
**Moving from claude.ai/design.** The standalone Claude Design site closes on 2026-12-14 and its designs now live in Claude as Artifacts. A config that still says `design: claude.ai/design` keeps working as `artifacts` (trailhead tells you once and offers to rewrite the key), but a cached claude.ai/design project can't be reused: migrate your design systems from the Artifacts page banner and let trailhead attach a new canvas. Where the `Artifact` tool isn't available (Codex, or an API-key session), trailhead offers to switch to `stitch` before falling to local disk. `design.approval` decides whether the build waits for your explicit go-ahead (`explicit`), or proceeds right after surfacing the mockup (`auto`).
|
|
341
372
|
|
|
342
373
|
---
|
|
343
374
|
|
|
@@ -351,9 +382,13 @@ Beyond the skill's instructions, trailhead ships eight of its own hooks (in `hoo
|
|
|
351
382
|
- **Search guard** (`PreToolUse` on `Bash`): structural backstop for the search-command-hygiene rule (never `cd` then read/search a relative path). It hard-blocks a single Bash command that both changes directory (`cd`/`pushd`/`env -C`) and, in a joined or later statement, reads/searches a relative path (`grep`/`rg`/`ag`/`cat`/`head`/`tail`/`wc`/`nl`/`sed`/`awk`/`less`/`more`, or `git grep`/`log`/`show`/`diff` without a `-C`), so the shape that trips a `Read()` deny rule under bypass permissions (falling back to a manual approval prompt instead of running headless) never runs at all, rather than merely being discouraged in the agent's own prose. Claude Code only; not projected to Codex, which has no such `Read()` deny rule.
|
|
352
383
|
- **Body guard** (`PreToolUse` on `gh` issue/PR body writes): hard-blocks a `gh issue edit` / `gh pr edit` / `gh api` write that would set an issue body to empty or whitespace-only, so a read-modify-write whose read returned an empty/degraded body (e.g. a transient HTTP 5xx on `gh issue view --json body`) can never clobber the existing body.
|
|
353
384
|
- **Secret-read guard** (`PreToolUse` on `Read|Bash`): denies reads of `.env`/`.env.*`/`.secrets`, whether via the Read tool or a Bash command (a file operand, an option value, or a `<` redirect target). This is a hook `deny` decision rather than a `Read()` permission rule, so secrets stay unreadable without arming Claude Code 2.1.259's `cd-compound-read` approval prompt.
|
|
354
|
-
- **Mockup-link guard** (`PreToolUse` on `AskUserQuestion`): structural backstop for the Prototype rule that a mockup approval ask carries the mockup's link. It hard-blocks an `AskUserQuestion` that asks to approve a mockup (or pick between mockup variants) when the reply text right before it holds
|
|
385
|
+
- **Mockup-link guard** (`PreToolUse` on `AskUserQuestion`): structural backstop for the Prototype rule that a mockup approval ask carries the mockup's link. It hard-blocks an `AskUserQuestion` that asks to approve a mockup (or pick between mockup variants) when neither the reply text right before it nor the approval question itself holds a URL or file path (when the current message is not yet in the transcript, the guard cannot judge and allows), so the agent puts the clickable link in the message before asking again. On Codex, which has no `AskUserQuestion` tool and asks as a plain-text numbered list, its twin `mockup-link-stop` runs on `Stop`: when the final reply asks for that approval without a link, Codex continues the turn so the agent rewrites the ask with it.
|
|
355
386
|
- **Issue injection scanner** (`PostToolUse` on `gh` reads): trailhead reads issue/PR/comment text written by anyone with repo access, i.e. untrusted input. When that text contains prompt-injection phrases, the hook injects an advisory reminding the agent to treat it as data, never as commands. Advisory only: it never blocks.
|
|
356
387
|
|
|
388
|
+
The commit, secret, install, search, and body guards run in **one process**: a single `trailhead-bash-guards.js` registration evaluates them in that order, so a Bash call pays one node start-up instead of five (three hook processes per Bash call in all: the merged hook, the secret-read guard, and the injection scanner). They remain five distinct guards with unchanged verdicts; the first block wins, and a guard that errors allows on its own without stopping the others. A guard whose module is missing or broken is skipped too, but never silently: the hook tells the session which guard did not check the command, so a partial install shows up. On Codex the same hook is registered with `--guards=commit,secret,install,body`, since there is no search guard there.
|
|
389
|
+
|
|
390
|
+
The secret, body, and secret-read guards also **fail closed on shell constructs they cannot interpret with certainty** (`eval`, `source` / `.`, a bash 5.3 `${ cmd; }`, `printf -v`, a function definition, an alias, a quoted `gh api` body key, a backslash-newline inside a word): when one can hide or precede a write (or sits next to a secret path), the command is blocked with a "rewrite it in a simple form" message instead of being emulated; the same constructs with no write or secret nearby pass.
|
|
391
|
+
|
|
357
392
|
All eight are crash-safe (any error → allow) and active whenever the plugin is installed. The commit, secret, install, search, body, and secret-read guards therefore apply in **every** repo, not only trailhead projects: intentional, since conventional commits, no `Co-Authored-By`, no leaked secrets, no unvetted installs, no `cd`-then-relative-read, no clobbered issue bodies, and no readable `.env`/`.secrets` are trailhead's standing rules. To opt out, disable the plugin's hooks in your Claude Code settings.
|
|
358
393
|
|
|
359
394
|
---
|
|
@@ -113,10 +113,10 @@ Codex has a subagent toolkit: the multi_agent tools (\`spawn_agent\`, \`send_inp
|
|
|
113
113
|
The engine's \`/clear\` is Codex's \`/new\` (start a fresh session). Command references in a handoff use the skill form (\`$trailhead work <n>\`).
|
|
114
114
|
|
|
115
115
|
## F. Lifecycle hooks (native Codex hooks)
|
|
116
|
-
Codex has a hook bus, so trailhead's guardrails run as **real Codex hooks**, not degraded prose. The installer registers them in \`~/.codex/hooks.json\`: commit-guard, secret-guard, install-guard and body-guard as \`PreToolUse\` (matcher \`Bash\`) hooks that can veto the command before it runs, secret-read-guard as a \`PreToolUse\` (matcher \`Read|Bash\`) hook that denies reads of \`.env\`/\`.env.*\`/\`.secrets\`, the injection-scanner as a \`PostToolUse\` (matcher \`Bash\`) advisory, mockup-link-stop as a \`Stop\` hook (when your final reply asks to approve a mockup without carrying its link, it continues the turn so you rewrite the ask with the URL or file path in the prose), and check-update as a \`SessionStart\` hook. On Codex, file reads happen through shell commands rather than a native Read-tool event, so the secret-read guard's enforcement here runs via its **Bash** arm (protection is not lost); the \`Read\` half of its matcher is a Claude-Code-native path, registered on Codex too only so it fires without changes if a future Codex version adds an equivalent event. search-guard is Claude-Code-specific (it only prevents arming Claude's \`Read()\` deny rule under bypass permissions, a mechanism Codex lacks), so it is not projected to Codex at all. They use the same wire format they use on Claude Code (JSON on stdin, a \`decision\`/\`permissionDecision\` verdict on stdout), which Codex accepts. The host-independent git \`commit-msg\` hook is still installed at repo first-use as well (defence in depth), exactly as on Claude. Codex gates hooks behind \`features.hooks = true\` and reviews them for trust on first start: the installer enables the flag, and Codex will ask you to trust trailhead's hooks the next time it starts.
|
|
116
|
+
Codex has a hook bus, so trailhead's guardrails run as **real Codex hooks**, not degraded prose. The installer registers them in \`~/.codex/hooks.json\`: commit-guard, secret-guard, install-guard and body-guard as \`PreToolUse\` (matcher \`Bash\`) hooks that can veto the command before it runs (the four run in one process, a single \`trailhead-bash-guards.js --guards=commit,secret,install,body\` registration, so a Bash call pays one start-up instead of four), secret-read-guard as a \`PreToolUse\` (matcher \`Read|Bash\`) hook that denies reads of \`.env\`/\`.env.*\`/\`.secrets\`, the injection-scanner as a \`PostToolUse\` (matcher \`Bash\`) advisory, mockup-link-stop as a \`Stop\` hook (when your final reply asks to approve a mockup without carrying its link, it continues the turn so you rewrite the ask with the URL or file path in the prose), and check-update as a \`SessionStart\` hook. On Codex, file reads happen through shell commands rather than a native Read-tool event, so the secret-read guard's enforcement here runs via its **Bash** arm (protection is not lost); the \`Read\` half of its matcher is a Claude-Code-native path, registered on Codex too only so it fires without changes if a future Codex version adds an equivalent event. search-guard is Claude-Code-specific (it only prevents arming Claude's \`Read()\` deny rule under bypass permissions, a mechanism Codex lacks), so it is not projected to Codex at all. They use the same wire format they use on Claude Code (JSON on stdin, a \`decision\`/\`permissionDecision\` verdict on stdout), which Codex accepts. The host-independent git \`commit-msg\` hook is still installed at repo first-use as well (defence in depth), exactly as on Claude. Codex gates hooks behind \`features.hooks = true\` and reviews them for trust on first start: the installer enables the flag, and Codex will ask you to trust trailhead's hooks the next time it starts.
|
|
117
117
|
|
|
118
|
-
## G. Design / UI mockups -> local disk
|
|
119
|
-
The \`
|
|
118
|
+
## G. Design / UI mockups: artifacts -> stitch offer, else local disk
|
|
119
|
+
The \`artifacts\` mockup mode (a Design canvas Artifact built with Claude Code's \`Artifact\` tool; the legacy \`claude.ai/design\` value means the same) does not exist on Codex: there is no \`Artifact\` tool here. When \`config.design\` names it, follow the Prototype technique's fallback: **offer once to switch this project to \`design: stitch\`** (the Google Stitch MCP runs on Codex too), and only on decline produce a throwaway static HTML mockup next to the code (\`disk\`), link it from the ticket, and say why. Never try to create or edit an Artifact on Codex. \`config.design.approval\` still applies unchanged: \`explicit\` waits for the user's go-ahead before any UI code, \`auto\` surfaces the mockup and proceeds.
|
|
120
120
|
|
|
121
121
|
## H. Shell timeouts & background jobs (plan-review's external CLIs)
|
|
122
122
|
The **Cross-AI plan review** technique invokes each external reviewer CLI (\`codex exec\`, \`gemini -p\`, \`qwen -p\`, …) through Claude Code's **Bash tool**, and tells the engine to beat that tool's ~2-minute default cut by setting its \`timeout\` parameter to the 10-minute max, or launching the call with \`run_in_background: true\` and collecting it later. Those are Claude Bash-tool knobs with no Codex equivalent, so map the *intent* onto Codex's own shell/exec tool. Reviews never block the session (the engine's hard cross-host default), so **the default is to background the reviewers** and collect them by polling, never block on them: launch each with a trailing \`&\` (or your shell tool's background form), **redirect its stdin from \`/dev/null\` and its output to a file** (\`codex exec … "<prompt>" < /dev/null > out.txt 2>&1 &\`), and poll/collect when it finishes, so several reviewers run in parallel instead of serially eating the wall-clock; or wrap each reviewer invocation in a §D subagent so the long-running call sits behind the multi_agent poll loop. Where a reviewer must run synchronously instead, give that call its **own long timeout** (the \`timeout_ms\` / equivalent on Codex's shell/exec tool, set to several minutes, never the short default), so a reviewer that legitimately runs for minutes is not cut mid-review. Either way plan-review's own rule is unchanged: never rely on a short default timeout, retry once at the wide margin, then drop a still-slow reviewer rather than let it wedge the build. **Close the reviewer's stdin when backgrounding it** (\`< /dev/null\`): \`codex exec\` reads additional prompt input from stdin and blocks on EOF, so a backgrounded reviewer that inherits an open or attached stdin sits idle at "Reading additional input from stdin..." (0% CPU) forever instead of running (passing the prompt as an argument does not exempt it). Guard the poll loop too: a reviewer pinned at 0% CPU with no output past a threshold is stuck, so surface it and drop it after the one retry rather than reporting "still running" indefinitely. This is the external-CLI **shell** path; §D covers the separate **subagent** (\`wait_agent\`) path, and the two are distinct (an external reviewer CLI is a plain shell call, not a spawned Codex subagent).
|
|
@@ -323,6 +323,11 @@ policy:
|
|
|
323
323
|
`;
|
|
324
324
|
}
|
|
325
325
|
|
|
326
|
+
// --- CODEX_MOD_ONLY_VERBS --------------------------------------------------
|
|
327
|
+
// Verbs only the Claude Code mod answers (commands/<verb>.md exists for the
|
|
328
|
+
// namespaced command, but nothing on Codex can draw it): never projected.
|
|
329
|
+
const CODEX_MOD_ONLY_VERBS = ['panel'];
|
|
330
|
+
|
|
326
331
|
// --- codexVerbSkillPlan ----------------------------------------------------
|
|
327
332
|
// Given the resolved verb list (each { verb, description? }, or a bare string
|
|
328
333
|
// verb; normally read from plugins/trailhead/commands/*.md by the installer),
|
|
@@ -340,6 +345,7 @@ function codexVerbSkillPlan(codexHome, verbs) {
|
|
|
340
345
|
const v = entry && typeof entry === 'object' ? entry.verb : entry;
|
|
341
346
|
if (v == null || String(v).trim() === '') continue;
|
|
342
347
|
const verb = String(v).trim();
|
|
348
|
+
if (CODEX_MOD_ONLY_VERBS.includes(verb)) continue;
|
|
343
349
|
const description = entry && typeof entry === 'object' ? entry.description : undefined;
|
|
344
350
|
const skillDir = path.join(skillsRoot, `trailhead-${verb}`);
|
|
345
351
|
dirs.push(skillDir);
|
|
@@ -358,10 +364,13 @@ function codexVerbSkillPlan(codexHome, verbs) {
|
|
|
358
364
|
}
|
|
359
365
|
|
|
360
366
|
// --- codexHookEntries -----------------------------------------------------
|
|
361
|
-
// The
|
|
362
|
-
// command} records. Commands run the copied
|
|
363
|
-
// via node, matching Claude's `node "<path>"` form.
|
|
364
|
-
// secret, install, body
|
|
367
|
+
// The guardrail hooks trailhead registers on Codex, as {event, matcher,
|
|
368
|
+
// command} records. Commands run the copied hook scripts under hooksScriptsDir
|
|
369
|
+
// via node, matching Claude's `node "<path>"` form. The four Bash guards
|
|
370
|
+
// (commit, secret, install, body) are ONE registration, the merged
|
|
371
|
+
// trailhead-bash-guards.js with `--guards=commit,secret,install,body` (a single
|
|
372
|
+
// node process per Bash call); secret-read is a separate PreToolUse hook. All
|
|
373
|
+
// PreToolUse guards can veto; the injection-scanner
|
|
365
374
|
// is a PostToolUse advisory; check-update runs at SessionStart. Reused by the
|
|
366
375
|
// installer, which merges them into ~/.codex/hooks.json (same shape as
|
|
367
376
|
// Claude's settings.json hooks block).
|
|
@@ -372,10 +381,7 @@ function codexVerbSkillPlan(codexHome, verbs) {
|
|
|
372
381
|
function codexHookEntries(hooksScriptsDir) {
|
|
373
382
|
const cmd = (name) => `node "${path.join(hooksScriptsDir, name)}"`;
|
|
374
383
|
return [
|
|
375
|
-
{ event: 'PreToolUse', matcher: 'Bash', command: cmd('trailhead-
|
|
376
|
-
{ event: 'PreToolUse', matcher: 'Bash', command: cmd('trailhead-secret-guard.js') },
|
|
377
|
-
{ event: 'PreToolUse', matcher: 'Bash', command: cmd('trailhead-install-guard.js') },
|
|
378
|
-
{ event: 'PreToolUse', matcher: 'Bash', command: cmd('trailhead-body-guard.js') },
|
|
384
|
+
{ event: 'PreToolUse', matcher: 'Bash', command: `${cmd('trailhead-bash-guards.js')} --guards=commit,secret,install,body` },
|
|
379
385
|
{ event: 'PreToolUse', matcher: 'Read|Bash', command: cmd('trailhead-secret-read-guard.js') },
|
|
380
386
|
{ event: 'PostToolUse', matcher: 'Bash', command: cmd('trailhead-issue-injection-scanner.js') },
|
|
381
387
|
{ event: 'Stop', matcher: '', command: cmd('trailhead-mockup-link-stop.js') },
|
|
@@ -447,6 +453,7 @@ module.exports = {
|
|
|
447
453
|
codexVerbSkillContent,
|
|
448
454
|
codexVerbSkillAgentsYaml,
|
|
449
455
|
codexVerbSkillPlan,
|
|
456
|
+
CODEX_MOD_ONLY_VERBS,
|
|
450
457
|
codexHookEntries,
|
|
451
458
|
enableCodexFeatureFlag,
|
|
452
459
|
enableCodexHooksFeature,
|
|
@@ -15,6 +15,7 @@ const {
|
|
|
15
15
|
codexVerbSkillContent,
|
|
16
16
|
codexVerbSkillAgentsYaml,
|
|
17
17
|
codexVerbSkillPlan,
|
|
18
|
+
CODEX_MOD_ONLY_VERBS,
|
|
18
19
|
codexHookEntries,
|
|
19
20
|
enableCodexHooksFeature,
|
|
20
21
|
enableCodexMultiAgentV2Feature,
|
|
@@ -160,19 +161,22 @@ ok('codexVerbSkillPlan: accepts a bare-string verb and skips empty/invalid', (()
|
|
|
160
161
|
})());
|
|
161
162
|
ok('codexVerbSkillPlan: null verbs yields nothing', codexVerbSkillPlan('/c', null).dirs.length === 0 && codexVerbSkillPlan('/c').dirs.length === 0);
|
|
162
163
|
|
|
164
|
+
// #208: a verb only the Claude Code mod answers (the map panel) has no Codex projection.
|
|
165
|
+
ok('CODEX_MOD_ONLY_VERBS names exactly the panel verb', Array.isArray(CODEX_MOD_ONLY_VERBS) && CODEX_MOD_ONLY_VERBS.length === 1 && CODEX_MOD_ONLY_VERBS[0] === 'panel');
|
|
166
|
+
ok('codexVerbSkillPlan: never projects a mod-only verb, object or bare string', (() => {
|
|
167
|
+
const plan = codexVerbSkillPlan('/c', [{ verb: 'panel', description: 'x' }, 'panel', { verb: 'map' }]);
|
|
168
|
+
return plan.dirs.length === 1 && plan.dirs[0] === '/c/skills/trailhead-map' && !plan.writes.some((w) => /panel/.test(w.path));
|
|
169
|
+
})());
|
|
170
|
+
|
|
163
171
|
// --- codexHookEntries ---
|
|
164
172
|
const entries = codexHookEntries('/h');
|
|
165
|
-
ok('codexHookEntries returns
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
ok('codexHookEntries:
|
|
169
|
-
|
|
170
|
-
ok('codexHookEntries:
|
|
171
|
-
|
|
172
|
-
ok('codexHookEntries: search-guard is absent (Claude-Code-only, not projected to Codex)',
|
|
173
|
-
entries.every((e) => !e.command.includes('trailhead-search-guard.js')));
|
|
174
|
-
ok('codexHookEntries: body-guard is PreToolUse/Bash', entries.some((e) =>
|
|
175
|
-
e.event === 'PreToolUse' && e.matcher === 'Bash' && e.command.includes('trailhead-body-guard.js')));
|
|
173
|
+
ok('codexHookEntries returns 5 entries', Array.isArray(entries) && entries.length === 5);
|
|
174
|
+
const bashEntries = entries.filter((e) => e.event === 'PreToolUse' && e.matcher === 'Bash');
|
|
175
|
+
ok('codexHookEntries: exactly one PreToolUse/Bash registration', bashEntries.length === 1);
|
|
176
|
+
ok('codexHookEntries: the Bash entry is the merged hook with exactly --guards=commit,secret,install,body',
|
|
177
|
+
bashEntries[0].command === 'node "/h/trailhead-bash-guards.js" --guards=commit,secret,install,body');
|
|
178
|
+
ok('codexHookEntries: no per-guard script is registered on its own', entries.every((e) =>
|
|
179
|
+
!/trailhead-(commit|secret|install|search|body)-guard\.js/.test(e.command)));
|
|
176
180
|
ok('codexHookEntries: secret-read-guard is PreToolUse/Read|Bash', entries.some((e) =>
|
|
177
181
|
e.event === 'PreToolUse' && e.matcher === 'Read|Bash' && e.command.includes('trailhead-secret-read-guard.js')));
|
|
178
182
|
ok('codexHookEntries: injection-scanner is PostToolUse/Bash', entries.some((e) =>
|
|
@@ -402,4 +406,28 @@ ok('convertToCodex projects the anchor positives to Codex forms', (() => {
|
|
|
402
406
|
!out.includes('/trailhead:work') && !out.includes('/clear');
|
|
403
407
|
})());
|
|
404
408
|
|
|
409
|
+
// #205: the work state helper and its session env var survive the projection;
|
|
410
|
+
// the projected work skill and handoff carry the set/clear calls.
|
|
411
|
+
ok('convertToCodex keeps the trailhead-work-state.js filename',
|
|
412
|
+
convertToCodex('node <templates>/trailhead-work-state.js set').includes('<templates>/trailhead-work-state.js set'));
|
|
413
|
+
ok('convertToCodex keeps CLAUDE_CODE_SESSION_ID intact',
|
|
414
|
+
convertToCodex('session from CLAUDE_CODE_SESSION_ID when set').includes('CLAUDE_CODE_SESSION_ID'));
|
|
415
|
+
{
|
|
416
|
+
const skillsDir = path.join(__dirname, '../../plugins/trailhead/skills');
|
|
417
|
+
const workSkill = convertToCodex(fs.readFileSync(path.join(skillsDir, 'trailhead-work', 'SKILL.md'), 'utf8'));
|
|
418
|
+
ok('projected work skill carries the work-start set call',
|
|
419
|
+
workSkill.includes('trailhead-work-state.js set --repo <working-root> --map <n> --ticket <n> --title "<title>" --step discuss'));
|
|
420
|
+
const handoff = convertToCodex(fs.readFileSync(path.join(skillsDir, '_shared', 'session-handoff.md'), 'utf8'));
|
|
421
|
+
ok('projected session handoff carries the work state clear call',
|
|
422
|
+
handoff.includes('trailhead-work-state.js clear --repo <working-root>'));
|
|
423
|
+
const teamwork = convertToCodex(fs.readFileSync(path.join(skillsDir, '_shared', 'teamwork.md'), 'utf8'));
|
|
424
|
+
ok('projected teamwork carries the step change call',
|
|
425
|
+
teamwork.includes('trailhead-work-state.js set --repo <working-root> --step <step>'));
|
|
426
|
+
ok('projected teamwork carries the autoDone counter flag',
|
|
427
|
+
teamwork.includes('--auto true --auto-done <n>'));
|
|
428
|
+
const autoRef = convertToCodex(fs.readFileSync(path.join(skillsDir, 'trailhead-work', 'references', 'auto.md'), 'utf8'));
|
|
429
|
+
ok('projected auto reference carries the autoDone counter flag',
|
|
430
|
+
autoRef.includes('--auto true --auto-done <n>'));
|
|
431
|
+
}
|
|
432
|
+
|
|
405
433
|
console.log(`✓ codex-projection: ${passed} assertions passed`);
|
|
@@ -135,6 +135,9 @@ function readRegistry(registryFile) {
|
|
|
135
135
|
// token expands to exactly hooksDir/<f>, OR an unresolved token's basename is
|
|
136
136
|
// <f> (stays warning-only, never also missing).
|
|
137
137
|
function checkRegisteredTargets(spec, missing, unverified) {
|
|
138
|
+
// Shipped files (hookFiles) may include modules that are only require()d;
|
|
139
|
+
// the files that must be registered are the entrypoints (default: all shipped).
|
|
140
|
+
const entrypoints = spec.entrypointFiles || spec.hookFiles;
|
|
138
141
|
const registry = readRegistry(spec.registryFile);
|
|
139
142
|
const registryName = path.basename(spec.registryFile);
|
|
140
143
|
const events = (registry && typeof registry.hooks === 'object' && registry.hooks) ? registry.hooks : {};
|
|
@@ -168,7 +171,7 @@ function checkRegisteredTargets(spec, missing, unverified) {
|
|
|
168
171
|
}
|
|
169
172
|
}
|
|
170
173
|
|
|
171
|
-
for (const f of
|
|
174
|
+
for (const f of entrypoints) {
|
|
172
175
|
if (!registeredExact.has(f) && !registeredViaUnresolved.has(f)) {
|
|
173
176
|
missing.push(`hook registration ${f} (${registryName})`);
|
|
174
177
|
}
|
|
@@ -185,8 +188,8 @@ function checkAgents(spec, missing) {
|
|
|
185
188
|
}
|
|
186
189
|
|
|
187
190
|
// --- verifyInstall --------------------------------------------------------------
|
|
188
|
-
// spec = { skillsRoot, skillDirs, sharedFiles, hooksDir, hookFiles,
|
|
189
|
-
// hookLibFiles, registryFile, agentsDir, agentPattern, env, home }
|
|
191
|
+
// spec = { skillsRoot, skillDirs, sharedFiles, hooksDir, hookFiles (shipped),
|
|
192
|
+
// entrypointFiles (registered; defaults to hookFiles), hookLibFiles, registryFile, agentsDir, agentPattern, env, home }
|
|
190
193
|
function verifyInstall(spec) {
|
|
191
194
|
const missing = [];
|
|
192
195
|
const unverified = [];
|
|
@@ -199,13 +202,14 @@ function verifyInstall(spec) {
|
|
|
199
202
|
}
|
|
200
203
|
|
|
201
204
|
// --- spec builders ---------------------------------------------------------
|
|
202
|
-
function claudeVerifySpec(configDir, { skillDirs, sharedFiles, hookFiles, hookLibFiles, env, home }) {
|
|
205
|
+
function claudeVerifySpec(configDir, { skillDirs, sharedFiles, hookFiles, entrypointFiles, hookLibFiles, env, home }) {
|
|
203
206
|
return {
|
|
204
207
|
skillsRoot: path.join(configDir, 'skills'),
|
|
205
208
|
skillDirs,
|
|
206
209
|
sharedFiles,
|
|
207
210
|
hooksDir: path.join(configDir, 'hooks'),
|
|
208
211
|
hookFiles,
|
|
212
|
+
entrypointFiles,
|
|
209
213
|
hookLibFiles,
|
|
210
214
|
registryFile: path.join(configDir, 'settings.json'),
|
|
211
215
|
agentsDir: path.join(configDir, 'agents'),
|
|
@@ -215,7 +219,7 @@ function claudeVerifySpec(configDir, { skillDirs, sharedFiles, hookFiles, hookLi
|
|
|
215
219
|
};
|
|
216
220
|
}
|
|
217
221
|
|
|
218
|
-
function codexVerifySpec(configDir, { skillDirs, sharedFiles, hookFiles, hookLibFiles, env, home }) {
|
|
222
|
+
function codexVerifySpec(configDir, { skillDirs, sharedFiles, hookFiles, entrypointFiles, hookLibFiles, env, home }) {
|
|
219
223
|
const L = codexLayout(configDir);
|
|
220
224
|
return {
|
|
221
225
|
skillsRoot: L.skillsRoot,
|
|
@@ -223,6 +227,7 @@ function codexVerifySpec(configDir, { skillDirs, sharedFiles, hookFiles, hookLib
|
|
|
223
227
|
sharedFiles,
|
|
224
228
|
hooksDir: L.hooksScriptsDir,
|
|
225
229
|
hookFiles,
|
|
230
|
+
entrypointFiles,
|
|
226
231
|
hookLibFiles,
|
|
227
232
|
registryFile: L.hooksJson,
|
|
228
233
|
agentsDir: L.codexAgentsDir,
|