@marcomigozzi/trailhead 0.17.0 → 0.18.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 (116) hide show
  1. package/README.it.md +28 -18
  2. package/README.md +28 -18
  3. package/bin/base-preflight-docs.test.js +4 -1
  4. package/bin/bulk-cross-reference-docs.test.js +36 -0
  5. package/bin/engine-route-docs.test.js +7 -0
  6. package/bin/facet-stamp-docs.test.js +79 -0
  7. package/bin/forced-mutation-docs.test.js +38 -0
  8. package/bin/guard-axis-docs.test.js +69 -0
  9. package/bin/inbox-needs-info-docs.test.js +68 -0
  10. package/bin/lib/codex-projection.js +8 -8
  11. package/bin/lib/codex-projection.test.js +19 -3
  12. package/bin/needs-you-docs.test.js +9 -2
  13. package/bin/options-in-chat-docs.test.js +65 -0
  14. package/bin/pick-stress-test-docs.test.js +1 -1
  15. package/bin/pipeline-review-docs.test.js +118 -0
  16. package/bin/plan-review-budget-docs.test.js +63 -0
  17. package/bin/repo-target-docs.test.js +41 -0
  18. package/bin/seam-coverage-docs.test.js +46 -0
  19. package/bin/standards-brief-docs.test.js +112 -0
  20. package/bin/test-once-docs.test.js +101 -0
  21. package/bin/test-run-docs.test.js +47 -0
  22. package/bin/trailhead.js +9 -4
  23. package/bin/trailhead.test.js +18 -6
  24. package/bin/yes-accepts-recommended-docs.test.js +54 -0
  25. package/package.json +1 -1
  26. package/plugins/trailhead/.claude-plugin/plugin.json +1 -1
  27. package/plugins/trailhead/agents/trailhead-code-review.md +2 -1
  28. package/plugins/trailhead/agents/trailhead-grill.md +2 -2
  29. package/plugins/trailhead/agents/trailhead-plan.md +1 -1
  30. package/plugins/trailhead/agents/trailhead-verify.md +1 -0
  31. package/plugins/trailhead/hooks/lib/audit-walk.test.js +62 -0
  32. package/plugins/trailhead/hooks/lib/git-env-mod.test.js +55 -0
  33. package/plugins/trailhead/hooks/lib/git-env-spawn.test.js +73 -0
  34. package/plugins/trailhead/hooks/lib/git-env.test.js +51 -0
  35. package/plugins/trailhead/hooks/lib/global-config-dir.test.js +48 -0
  36. package/plugins/trailhead/hooks/lib/heredoc-mod.test.js +85 -0
  37. package/plugins/trailhead/hooks/lib/inbox-needs-info.test.js +179 -0
  38. package/plugins/trailhead/hooks/lib/issue-body.test.js +81 -0
  39. package/plugins/trailhead/hooks/lib/needs-you.test.js +45 -1
  40. package/plugins/trailhead/hooks/lib/plan-review-rounds.test.js +272 -0
  41. package/plugins/trailhead/hooks/lib/repo-target.test.js +256 -0
  42. package/plugins/trailhead/hooks/lib/statusline-git-env.test.js +47 -0
  43. package/plugins/trailhead/hooks/lib/test-run.test.js +757 -0
  44. package/plugins/trailhead/hooks/lib/trailhead-git-env.js +40 -0
  45. package/plugins/trailhead/hooks/mod/README.md +30 -14
  46. package/plugins/trailhead/hooks/mod/auto-run.ts +2 -1
  47. package/plugins/trailhead/hooks/mod/git-env.ts +31 -0
  48. package/plugins/trailhead/hooks/mod/heredoc.ts +79 -0
  49. package/plugins/trailhead/hooks/mod/map-pane-view.ts +3 -1
  50. package/plugins/trailhead/hooks/mod/map-pane.tsx +144 -45
  51. package/plugins/trailhead/hooks/mod/repo-pin.ts +105 -4
  52. package/plugins/trailhead/hooks/mod/work-state-locate.ts +2 -1
  53. package/plugins/trailhead/hooks/trailhead-bash-guards.js +8 -8
  54. package/plugins/trailhead/hooks/trailhead-bash-guards.test.js +28 -14
  55. package/plugins/trailhead/hooks/trailhead-check-update.js +3 -1
  56. package/plugins/trailhead/hooks/trailhead-labels-guard.js +2 -2
  57. package/plugins/trailhead/hooks/trailhead-labels-guard.test.js +1 -1
  58. package/plugins/trailhead/hooks/trailhead-test-guard.js +232 -0
  59. package/plugins/trailhead/hooks/trailhead-test-guard.test.js +172 -0
  60. package/plugins/trailhead/skills/_shared/capture-core.md +5 -0
  61. package/plugins/trailhead/skills/_shared/configuration-reference.md +12 -0
  62. package/plugins/trailhead/skills/_shared/load-first.md +2 -0
  63. package/plugins/trailhead/skills/_shared/substrate-commands.md +11 -2
  64. package/plugins/trailhead/skills/_shared/substrate.md +3 -2
  65. package/plugins/trailhead/skills/_shared/teamwork.md +5 -3
  66. package/plugins/trailhead/skills/_shared/techniques/code-review-pipeline.md +24 -0
  67. package/plugins/trailhead/skills/_shared/techniques/code-review.md +18 -5
  68. package/plugins/trailhead/skills/_shared/techniques/codebase-map.md +2 -2
  69. package/plugins/trailhead/skills/_shared/techniques/debug.md +1 -1
  70. package/plugins/trailhead/skills/_shared/techniques/grilling.md +3 -3
  71. package/plugins/trailhead/skills/_shared/techniques/plan-review.md +16 -4
  72. package/plugins/trailhead/skills/_shared/techniques/tdd.md +4 -2
  73. package/plugins/trailhead/skills/_shared/techniques/verify.md +2 -0
  74. package/plugins/trailhead/skills/_shared/techniques.md +3 -3
  75. package/plugins/trailhead/skills/trailhead-chart/SKILL.md +2 -2
  76. package/plugins/trailhead/skills/trailhead-chart/references/charting.md +1 -1
  77. package/plugins/trailhead/skills/trailhead-chart/references/inbox.md +8 -3
  78. package/plugins/trailhead/skills/trailhead-manage/references/auditing.md +17 -2
  79. package/plugins/trailhead/skills/trailhead-work/SKILL.md +1 -0
  80. package/plugins/trailhead/skills/trailhead-work/references/auto.md +2 -0
  81. package/plugins/trailhead/skills/trailhead-work/references/executor-brief.md +2 -0
  82. package/plugins/trailhead/skills/trailhead-work/references/ticket-engines.md +7 -7
  83. package/plugins/trailhead/templates/statusline-trailhead.sh +10 -3
  84. package/plugins/trailhead/templates/trailhead-audit-walk.js +56 -4
  85. package/plugins/trailhead/templates/trailhead-auto-merge.js +2 -1
  86. package/plugins/trailhead/templates/trailhead-auto-queue.js +2 -1
  87. package/plugins/trailhead/templates/trailhead-auto-slot.js +2 -1
  88. package/plugins/trailhead/templates/trailhead-base-preflight.js +10 -5
  89. package/plugins/trailhead/templates/trailhead-capture.js +2 -1
  90. package/plugins/trailhead/templates/trailhead-checkout-find.js +3 -2
  91. package/plugins/trailhead/templates/trailhead-checkout-in.js +2 -1
  92. package/plugins/trailhead/templates/trailhead-commit-msg-sync.js +2 -1
  93. package/plugins/trailhead/templates/trailhead-git-env.js +40 -0
  94. package/plugins/trailhead/templates/trailhead-inbox-needs-info.js +161 -0
  95. package/plugins/trailhead/templates/trailhead-isolation-answer.js +2 -1
  96. package/plugins/trailhead/templates/trailhead-isolation-copy.js +9 -4
  97. package/plugins/trailhead/templates/trailhead-issue-body.js +92 -6
  98. package/plugins/trailhead/templates/trailhead-merge-ticket.js +2 -1
  99. package/plugins/trailhead/templates/trailhead-needs-you.js +38 -4
  100. package/plugins/trailhead/templates/trailhead-plan-review-rounds.js +261 -7
  101. package/plugins/trailhead/templates/trailhead-repo-target.js +302 -0
  102. package/plugins/trailhead/templates/trailhead-test-run.js +752 -0
  103. package/plugins/trailhead/templates/trailhead-uat-backfill.js +2 -1
  104. package/plugins/trailhead/templates/trailhead-uat-followup.js +2 -1
  105. package/plugins/trailhead/templates/trailhead-uat-setup.js +9 -4
  106. package/plugins/trailhead/templates/trailhead-unblock.js +2 -1
  107. package/plugins/trailhead/tests/auto-run-ui.test.ts +2 -1
  108. package/plugins/trailhead/tests/git-env.test.ts +20 -0
  109. package/plugins/trailhead/tests/map-pane-ui.test.ts +268 -17
  110. package/plugins/trailhead/tests/map-pane-view.test.ts +6 -0
  111. package/plugins/trailhead/tests/map-pane-write.test.ts +57 -1
  112. package/plugins/trailhead/tests/repo-pin-late-repo.test.ts +3 -2
  113. package/plugins/trailhead/tests/repo-pin-relocate.test.ts +175 -0
  114. package/plugins/trailhead/tests/unscrub.ts +8 -0
  115. package/plugins/trailhead/tests/work-state-prompt-hook.test.ts +3 -2
  116. package/plugins/trailhead/tests/work-state-worktree.test.ts +2 -1
package/README.it.md CHANGED
@@ -6,13 +6,13 @@
6
6
 
7
7
  🇬🇧 [English](README.md) · 🇮🇹 **Italiano**
8
8
 
9
- [![sito](https://img.shields.io/badge/sito-trailhead.marcomigozzi.it-3C7A5A?style=flat-square)](https://trailhead.marcomigozzi.it) [![CI sito](https://img.shields.io/github/actions/workflow/status/ToRvaLDz/trailhead/site-ci.yml?branch=main&style=flat-square&label=CI%20sito)](https://github.com/ToRvaLDz/trailhead/actions/workflows/site-ci.yml) [![npm](https://img.shields.io/npm/v/@marcomigozzi/trailhead?style=flat-square&label=npm&color=CB3837)](https://www.npmjs.com/package/@marcomigozzi/trailhead) [![license](https://img.shields.io/github/license/ToRvaLDz/trailhead?style=flat-square&color=3C7A5A)](LICENSE) ![host](https://img.shields.io/badge/host-Claude%20Code%20%C2%B7%20Codex-1E2A54?style=flat-square) ![comandi](https://img.shields.io/badge/comandi-24%20verbi-0e8a16?style=flat-square) ![guardrail](https://img.shields.io/badge/guardrail-8%20hook-e8710a?style=flat-square)
9
+ [![sito](https://img.shields.io/badge/sito-trailhead.marcomigozzi.it-3C7A5A?style=flat-square)](https://trailhead.marcomigozzi.it) [![CI sito](https://img.shields.io/github/actions/workflow/status/ToRvaLDz/trailhead/site-ci.yml?branch=main&style=flat-square&label=CI%20sito)](https://github.com/ToRvaLDz/trailhead/actions/workflows/site-ci.yml) [![npm](https://img.shields.io/npm/v/@marcomigozzi/trailhead?style=flat-square&label=npm&color=CB3837)](https://www.npmjs.com/package/@marcomigozzi/trailhead) [![license](https://img.shields.io/github/license/ToRvaLDz/trailhead?style=flat-square&color=3C7A5A)](LICENSE) ![host](https://img.shields.io/badge/host-Claude%20Code%20%C2%B7%20Codex-1E2A54?style=flat-square) ![comandi](https://img.shields.io/badge/comandi-24%20verbi-0e8a16?style=flat-square) ![guardrail](https://img.shields.io/badge/guardrail-10%20hook-e8710a?style=flat-square)
10
10
 
11
11
  </div>
12
12
 
13
13
  **Avvia e conduci grandi progetti come una mappa di ticket su GitHub Issues, risolvendone uno alla volta finché la via verso la destination non è chiara.**
14
14
 
15
- `trailhead` è una skill orchestratrice per coding agent, che gira su [Claude Code](https://docs.claude.com/en/docs/claude-code) e [Codex CLI](https://developers.openai.com/codex/cli) da un'unica sorgente. Dà a un'idea grande e nebbiosa un punto da cui partire (un *trailhead*) e un modo disciplinato per portarla fino a un risultato funzionante, con l'intero piano che vive sul tuo issue tracker invece che in file locali sparsi.
15
+ `trailhead` è una skill orchestratrice per coding agent, che gira su [Claude Code](https://code.claude.com/docs/en/overview) e [Codex CLI](https://developers.openai.com/codex/cli) da un'unica sorgente. Dà a un'idea grande e nebbiosa un punto da cui partire (un *trailhead*) e un modo disciplinato per portarla fino a un risultato funzionante, con l'intero piano che vive sul tuo issue tracker invece che in file locali sparsi.
16
16
 
17
17
  ---
18
18
 
@@ -71,7 +71,7 @@ npx @marcomigozzi/trailhead --dir=<path> # punta a una config dir specifica
71
71
  ```
72
72
  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 (che esegue anche il controllo del token `gh`), e i template (workflow label-guard, statusline, l'hook git `commit-msg` e gli script di supporto che il motore esegue), in modo idempotente.
73
73
 
74
- 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 otto guardrail come **hook** nativi di Codex (`~/.codex/hooks.json`, abilitando `features.hooks`; i cinque 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
+ 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 nove guardrail come **hook** nativi di Codex (`~/.codex/hooks.json`, abilitando `features.hooks`; i sei 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.
75
75
 
76
76
  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).
77
77
 
@@ -94,7 +94,7 @@ La fog contiene solo ciò che non si sa ancora formulare: una domanda già formu
94
94
 
95
95
  Una mappa ha come scope **un** solo sforzo, quindi la conoscenza che appartiene al *repo* (non a una singola mappa) vive in **anchor issue con scope di repo**, create una volta e condivise da ogni mappa (le Notes di ciascuna mappa le collegano soltanto, così nulla resta orfano quando una mappa finisce):
96
96
 
97
- - **`trailhead:codebase`**: la codebase map distillata (architettura, stack, convenzioni, rischi, test/build), scritta una volta all'adopt e aggiornata solo su drift importanti.
97
+ - **`trailhead:codebase`**: la codebase map distillata (architettura, stack, convenzioni, rischi, test/build), scritta una volta all'adopt e aggiornata solo su drift importanti. Ogni facet porta un timbro `_as of <short sha>_` del commit rispetto a cui è stata scritta o aggiornata l'ultima volta.
98
98
  - **`trailhead:conventions`**: il **modo di lavorare** del progetto, leggibile da tutti: un piccolo header machine-read che il motore obbedisce (`git: main|pr`, `release: command|auto`, `isolation: auto|none|worktree|clone`) sopra la prosa umana. `/trailhead:adopt` e `:new` lo chiedono all'inizio. `isolation: auto` (il default) decide per ogni ticket al work-start: chiede se lavorerai più di un ticket alla volta e, se sì, usa un `git worktree` (repo semplice) o un `clone` (superrepo con submodule); altrimenti lavora nel checkout corrente. `isolation: worktree` dà a ogni ticket in esecuzione il proprio `git worktree` + branch (le sessioni concorrenti su un clone non condividono mai un working tree); `isolation: clone` gli dà invece un clone dedicato, per le app legate a un path che un worktree non riesce a buildare e per i superrepo con submodule (dove `worktree` viene scalato a `clone`).
99
99
  - **`trailhead:dashboard`**: l'indice pinnato dell'intera superficie, un link a ogni mappa aperta (con le progress bar native di GitHub), la whiteboard, e conteggi live (inbox, frontier della whiteboard). Aggiornato quando una mappa viene tracciata o esaurita, quando un ticket della whiteboard nasce o si risolve (non ha una progress bar nativa, quindi la dashboard è l'unico posto in cui appare; un ticket nato da `quick`, lavorato e risolto nella stessa sessione, si aggiorna al suo handoff o alla pausa, non alla creazione), e su richiesta via `/trailhead:dashboard`.
100
100
 
@@ -219,7 +219,7 @@ Ogni verbo è anche un comando con namespace (`/trailhead:new`, `/trailhead:work
219
219
  | `/trailhead:ticket <type> <title>` | apre un ticket al volo (diverge brevemente prima: un atto di micro-charting) |
220
220
  | `/trailhead:split [ticket]` | splitta un ticket sovradimensionato in figli, supersede l'originale |
221
221
  | `/trailhead:auto [map]` | esegue uno scope scelto (una mappa, la whiteboard o tutto) fino a 4 slot paralleli, fino all'esaurimento o a uno stop; parcheggia ciò che serve a te |
222
- | `/trailhead:grill [topic]` | esegue una sessione di grilling autonoma su una decision/topic |
222
+ | `/trailhead:grill [topic\|ticket]` | esegue una sessione di grilling autonoma su una decision/topic |
223
223
  | `/trailhead:map` | mostra la mappa a bassa risoluzione (destination, decisions, frontier, fog) |
224
224
  | `/trailhead:dashboard` | mostra la dashboard del repo: l'indice pinnato di ogni mappa aperta, la whiteboard, e i conteggi live |
225
225
  | `/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+) |
@@ -228,15 +228,15 @@ Ogni verbo è anche un comando con namespace (`/trailhead:new`, `/trailhead:work
228
228
 
229
229
  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.
230
230
 
231
- ![Il pannello della mappa affiancato al transcript: mappe aperte, frontier con un pulsante work per ticket, in corso, bloccati e un footer con refresh e close](assets/screenshots/map-panel.png)
231
+ <img src="assets/screenshots/map-panel.png" width="520" alt="Il pannello della mappa con ogni sezione nel suo colore: mappe aperte, in corso, needs you, frontier con un pulsante work per ticket, UAT, in pausa, bloccati, il burn-down e un footer con refresh e close">
232
232
 
233
- Il pannello della mappa elenca le mappe aperte da consultare, subito sotto di esse i tuoi ticket in lavorazione (in un gruppo `In progress`, da ogni mappa e dalla whiteboard), i ticket che una run autonoma ha parcheggiato per te in un gruppo `Needs you`, la frontier con un pulsante `work` per ogni ticket che pulisce il contesto e avvia il ticket (nel checkout della mappa; se quel checkout è stato spostato, viene ritrovato tramite il suo remote `origin`, e scegli tu quando ne corrispondono diversi), chi tiene cosa (il tuo ticket è segnato `this session`), i follow-up UAT in sospeso in un gruppo `UAT` (ognuno con il ticket che collauda), i ticket in pausa in un gruppo `Paused` e cosa è bloccato. Una riga dim sotto la riga Whiteboard somma i ticket aperti di tutte le mappe aperte più la whiteboard, lo stesso numero che dichiara `/trailhead:map` (`at least N` quando le mappe aperte sono più di quelle lette dal pannello). Il footer ha `refresh` e `close`. `/trailhead:panel` lo apre e lo chiude (se richiesto all'avvio della sessione, prima che lo schermo sia pronto, si apre appena lo è), e si apre da solo una volta in fullscreen.
233
+ Ogni sezione del pannello della mappa ha il suo glifo e il suo colore, con un filetto colorato lungo le righe. Il pannello elenca le mappe aperte da consultare, subito sotto di esse i tuoi ticket in lavorazione (in un gruppo `In progress`, da ogni mappa e dalla whiteboard), i ticket che una run autonoma ha parcheggiato per te in un gruppo `Needs you`, la frontier con un pulsante `work` per ogni ticket che pulisce il contesto e avvia il ticket (nel checkout della mappa; se quel checkout è stato spostato, viene ritrovato tramite il suo remote `origin`, e scegli tu quando ne corrispondono diversi), chi tiene cosa (il tuo ticket è segnato `this session`), i follow-up UAT in sospeso in un gruppo `UAT` (ognuno con il ticket che collauda), i ticket in pausa in un gruppo `Paused` e cosa è bloccato. Una riga dim sotto la riga Whiteboard somma i ticket aperti di tutte le mappe aperte più la whiteboard, lo stesso numero che dichiara `/trailhead:map` (`at least N` quando le mappe aperte sono più di quelle lette dal pannello). Il footer ha `refresh` e `close`. `/trailhead:panel` lo apre e lo chiude (se richiesto all'avvio della sessione, prima che lo schermo sia pronto, si apre appena lo è), e si apre da solo una volta in fullscreen. Nell'app desktop una sessione parte al primo messaggio, quindi il pannello non può comparire in una sessione nuova in cui non è stato scritto nulla; `/trailhead:panel` come primo messaggio lo apre.
234
234
 
235
- ![Il pannello che mostra un'altra mappa, con l'intestazione viewing e active e il pulsante back to active](assets/screenshots/map-panel-viewing.png)
235
+ <img src="assets/screenshots/map-panel-viewing.png" width="520" alt="Il pannello che mostra un'altra mappa, con l'intestazione viewing e active e il pulsante back to active">
236
236
 
237
237
  Un click mostra soltanto una mappa, e `back to active` torna a quella attiva. La mappa attiva cambia quando lavori uno dei suoi ticket. Una riga `Whiteboard` sopra l'elenco delle mappe, con i conteggi `N open / M`, mostra invece l'indice del repo: tutte le mappe aperte (un click ne apre una) e i ticket sciolti della whiteboard per stato, ognuno della frontier con il suo pulsante `work`; `map` torna indietro.
238
238
 
239
- ![La sezione Burn-down del pannello: riga dei conteggi e grafico dei ticket aperti nel tempo](assets/screenshots/burndown.png)
239
+ <img src="assets/screenshots/burndown.png" width="520" alt="La sezione Burn-down del pannello: riga dei conteggi e grafico dei ticket aperti nel tempo">
240
240
 
241
241
  Il burn-down mostra i ticket aperti nel tempo, dal primo ticket della mappa a oggi, con chiusi e rimossi contati a parte.
242
242
 
@@ -388,7 +388,7 @@ Tutto ciò di cui la mappa ha bisogno è espresso come label GitHub, così lo st
388
388
  - **Strutturali:** `trailhead:map`, `trailhead:ticket`
389
389
  - **Anchor con scope di repo (uno ciascuno per repo, pinnati):** `trailhead:codebase` 🧱 (la codebase map distillata), `trailhead:conventions` 📜 (il modo di lavorare), `trailhead:dashboard` 📊 (l'indice pinnato di mappe + whiteboard + conteggi); ogni titolo porta la sua icona (come il 🗺 della mappa) così gli anchor pinnati si distinguono a colpo d'occhio
390
390
  - **Type (una per ticket):** 🧭 `trailhead:decision` · 🔬 `research` · 🎨 `prototype` · 🔨 `build` · 🐛 `bug` · 🔧 `task`
391
- - **State:** `trailhead:blocked` (ha un blocker aperto) · `seed` (parcheggiato su un trigger) · `out-of-scope` (chiuso, oltre la destination) · `superseded` (chiuso, splittato in figli) · `paused` (con checkpoint, fuori dalla frontier) · `needs-you` (parcheggiato per te da una run autonoma: le run lo saltano, resta sulla tua frontier) · `unverified` (in quarantena finché un maintainer non lo adotta) · `fog` (tenuto aperto per chiarimenti)
391
+ - **State:** `trailhead:blocked` (ha un blocker aperto) · `seed` (parcheggiato su un trigger) · `out-of-scope` (chiuso, oltre la destination) · `superseded` (chiuso, splittato in figli) · `paused` (con checkpoint, fuori dalla frontier) · `needs-you` (parcheggiato per te da una run autonoma: le run lo saltano, resta sulla tua frontier) · `unverified` (in quarantena finché un maintainer non lo adotta) · `fog` (tenuto aperto per chiarimenti) · `needs-info` (issue dell'inbox in attesa di chi l'ha aperta)
392
392
  - **Container:** `trailhead:whiteboard` (un ticket sciolto, senza mappa, fuori dalla frontier di ogni mappa, sulla frontier propria della whiteboard)
393
393
  - **Marker:** `trailhead:uat` (un follow-up UAT in sospeso; mai un filtro della frontier) · `trailhead:map-<n>` (lega un ticket alla sua mappa)
394
394
 
@@ -407,8 +407,9 @@ Molte persone (e le loro sessioni di agente) condividono una mappa e la lavorano
407
407
  - **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).
408
408
  - **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.
409
409
  - **Lo stato vive sui ticket** (claim, label, commenti; tutti conflict-free); il corpo della mappa è un indice ri-leggibile, non la fonte di verità. Il record durevole di una risoluzione è il commento del ticket stesso.
410
+ - **Nei fork si scrive dove vuoi.** In un clone di un fork `gh` può risolvere il repository parent, quindi una volta per sessione, prima della prima scrittura sul tracker, trailhead controlla dove finirebbero le issue. Se è ambiguo chiede in quale repository scrivere, lo imposta come default di `gh` e ricorda la risposta in `.trailhead/repo-target.json`. Come lato fork viene letto solo il remote `origin` su github.com.
410
411
  - **Le label sono protette dagli esterni.** Le label `trailhead:*` guidano la mappa, quindi trailhead si fida di una issue etichettata solo quando ogni sua label `trailhead:*` è stata applicata da un etichettatore fidato (un collaboratore con accesso in scrittura, `github-actions[bot]`, o un login nella variabile di repo `TRAILHEAD_LABEL_ALLOWLIST`: gli stessi etichettatori che la label guard non rimuove mai) e (per un ticket) porta un `Parent:` valido. Qualsiasi altra cosa è messa in quarantena come `trailhead:unverified`, fuori dalla frontier. GitHub già blocca gli utenti senza accesso in scrittura dall'applicare label; contro gli utenti di triage e le automazioni, trailhead può **installare per te la GitHub Action di label guard** (da `templates/trailhead-label-guard.yml`) nella `.github/workflows/` del tuo repo, dove rimuove alla fonte le label `trailhead:*` non autorizzate. Installare un workflow richiede un token con lo scope `workflow`, quindi trailhead esegue `gh auth refresh -s workflow` se il push viene rifiutato. Per le automazioni (CI, cron, bot) che aprono ticket senza una sessione di chat, `templates/trailhead-capture.js` cattura un bug, todo, seed o idea con tutte le label e si rifiuta di girare con un'identità che la guard strapperebbe, e su GitHub la composite Action [`actions/capture`](#catturare-da-ci) lo incapsula in poche righe di workflow.
411
- - **Le issue aperte da altri vengono triage-ate, non fidate alla cieca.** `/trailhead:inbox` elenca le issue in ingresso (bug report, richieste, domande che non sono ancora trailhead ticket) e integra quelle valide **sul posto**: riformulandole come ticket, mantenendo l'autorialità di chi ha segnalato, e applicando le label all'adozione. Il resto viene instradato a fog, out-of-scope, duplicato, o needs-info. Lì sono elencate anche le issue in quarantena `trailhead:unverified`, da adottare o rifiutare.
412
+ - **Le issue aperte da altri vengono triage-ate, non fidate alla cieca.** `/trailhead:inbox` elenca le issue in ingresso (bug report, richieste, domande che non sono ancora trailhead ticket) e integra quelle valide **sul posto**: riformulandole come ticket, mantenendo l'autorialità di chi ha segnalato, e applicando le label all'adozione. Il resto viene instradato a fog, out-of-scope, duplicato, o needs-info. Una issue needs-info viene parcheggiata dopo aver postato la domanda ed esce dalla coda; torna nella sezione *In attesa di chi ha segnalato* dell'inbox solo quando chi ha segnalato risponde, e dopo `inbox.needs_info_days` (default 14) senza risposta l'inbox propone di chiuderla, senza mai chiuderla da sola. Lì sono elencate anche le issue in quarantena `trailhead:unverified`, da adottare o rifiutare.
412
413
 
413
414
  ### Catturare da CI
414
415
 
@@ -424,7 +425,7 @@ jobs:
424
425
  steps:
425
426
  # ... i tuoi step di build e test ...
426
427
  - if: failure()
427
- uses: ToRvaLDz/trailhead/actions/capture@v0.17.0
428
+ uses: ToRvaLDz/trailhead/actions/capture@v0.18.0
428
429
  with:
429
430
  type: bug
430
431
  title: "CI fallita su ${{ github.ref_name }}"
@@ -478,11 +479,11 @@ Tre layer: **il più vicino vince**, chiave per chiave (una chiave non impostata
478
479
  - **Global**: `trailhead/config.json` nella directory di config dell'host: `$CLAUDE_CONFIG_DIR` (default `~/.claude`) su Claude Code, `$CODEX_HOME` (default `~/.codex`) su Codex. I tuoi default permanenti su ogni progetto.
479
480
  - **Defaults**: i valori integrati.
480
481
 
481
- `/trailhead:config` esegue un **setup guidato, a menu**: scegli lo scope, poi percorri ogni impostazione (⚙️ modo di lavorare (git · release · isolation) · 🌐 ticket language · 🧠 models (un profilo High / Balanced / Low, o Manual) · 🎨 design + approval · 🧪 TDD · ⚖️ effort · 🖥️ acceptance testing (browser, setup UAT, un account di test) · 🧑‍⚖️ 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.
482
+ `/trailhead:config` esegue un **setup guidato, a menu**: scegli lo scope, poi percorri ogni impostazione (⚙️ modo di lavorare (git · release · isolation) · 🌐 ticket language · 🧠 models (un profilo High / Balanced / Low, o Manual) · 🎨 design + approval · 🧪 TDD (e suite di test) · ⚖️ effort · 🖥️ acceptance testing (browser, setup UAT, un account di test) · 🧑‍⚖️ 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.
482
483
 
483
484
  `/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.
484
485
 
485
- `/trailhead:audit [mappa] [--all]` cerca le regressioni: verifica che i ticket chiusi dall'ultimo audit siano ancora veri rispetto al codice attuale. Il codice che i commit di un ticket non hanno più toccato è classificato invariato, senza agente (un controllo git veloce e deterministico che si basa sui trailer `Refs: #<n>` dei commit), decisioni, ricerche, task e prototipi ricevono un controllo leggero inline, e un agente di verifica completo parte solo dove il codice è cambiato o il controllo veloce non può decidere (per esempio senza commit `Refs:`). Ogni ticket è classificato come implementato, invariato, evoluto (un ticket successivo l'ha cambiato di proposito, elencato senza proposte), implementato male o non implementato. Segnala anche le decisioni chiuse senza build, cioè senza alcun ticket che porti avanti la loro risposta. Poi confronta quelli aperti per trovare duplicati e cluster raggruppabili. Resta in sola lettura finché non confermi: ogni correzione (un bug di regressione, una riapertura, i ticket build mancanti di una decisione, la chiusura di un duplicato, un supersede-and-merge) è una proposta applicata solo con un sì esplicito. `--all` riverifica tutti i ticket chiusi.
486
+ `/trailhead:audit [mappa] [--all]` cerca le regressioni: verifica che i ticket chiusi dall'ultimo audit siano ancora veri rispetto al codice attuale. Il codice che i commit di un ticket non hanno più toccato è classificato invariato, senza agente (un controllo git veloce e deterministico che si basa sui trailer `Refs: #<n>` dei commit), decisioni, ricerche, task e prototipi ricevono un controllo leggero inline, e un agente di verifica completo parte solo dove il codice è cambiato o il controllo veloce non può decidere (per esempio senza commit `Refs:`). Un audit dell'intero repo segnala anche le facet della codebase i cui file citati sono cambiati dopo il loro timbro `_as of <short sha>_` (solo report). Ogni ticket è classificato come implementato, invariato, evoluto (un ticket successivo l'ha cambiato di proposito, elencato senza proposte), implementato male o non implementato. Segnala anche le decisioni chiuse senza build, cioè senza alcun ticket che porti avanti la loro risposta. Poi confronta quelli aperti per trovare duplicati e cluster raggruppabili. Resta in sola lettura finché non confermi: ogni correzione (un bug di regressione, una riapertura, i ticket build mancanti di una decisione, la chiusura di un duplicato, un supersede-and-merge) è una proposta applicata solo con un sì esplicito. `--all` riverifica tutti i ticket chiusi.
486
487
 
487
488
  Il passo **📊 statusline** offre di installare la status bar di trailhead per Claude Code: una riga con **model · project · branch · plan usage (`5h %` · reset · `7d %`) · una barra della context-window**, più una **seconda riga con il ticket attivo** (`▸ #N Title`) ogni volta che ne stai lavorando uno, e un flag `⬆ trailhead <version>` quando è disponibile un trailhead più recente (esegui `/trailhead:update`). Il project è sempre il nome del repo principale anche da un checkout isolato, e il branch porta un tag `(WT)` in un worktree o `(C)` in un clone per-ticket (niente sul checkout originale). È un'impostazione globale di Claude Code; se già usi una statusline (es. `ccstatusline`) il setup chiede prima di rimpiazzarla, e lo script espone anche i segmenti `--ticket-only` / `--context-only` / `--usage-only` da inserire in un tool esistente.
488
489
 
@@ -501,8 +502,13 @@ Il passo **📊 statusline** offre di installare la status bar di trailhead per
501
502
  | `tdd` | **`seams`** \| `on` \| `off` | come il motore `build` testa |
502
503
  | `acceptance.browser` | **`auto`** \| `on` \| `off` | guida il browser in Verify (dopo aver chiesto chi guida, AI o umano, o skip); `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) |
503
504
  | `testing.webapp` / `testing.url` | bool / URL | è browser-drivabile, e dove |
505
+ | `testing.full` | comando shell (**non impostato**) | la suite completa; se impostato, il motore la esegue tramite la [coda e cache dei test](#esecuzioni-dei-test) |
506
+ | `testing.targeted` | comando shell con `{files}` (**non impostato**) | un'esecuzione mirata; `{files}` diventa l'elenco dei file tra apici singoli |
507
+ | `testing.maxParallel` | intero positivo (**`1`**) | quante esecuzioni pesanti di test possono girare insieme su tutta la macchina |
504
508
  | `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 |
505
509
  | `plan_review.rounds` | integer (**`2`**) | numero massimo di round converge-and-re-review |
510
+ | `plan_review.context` | object (`{"<cli>": <tokens>}`) | sovrascrive la finestra di contesto di una CLI reviewer; un reviewer il cui prompt stimato supera il 65% della finestra viene saltato e registrato in `PLAN-REVIEW` |
511
+ | `inbox.needs_info_days` | intero positivo (**`14`**) | giorni in cui una issue dell'inbox può attendere chi l'ha aperta prima che l'inbox proponga di chiuderla (non chiude mai da sola) |
506
512
  | `code_review.rounds` | integer (**`2`**) | numero massimo di round fix-and-re-review in Code review prima del checkpoint HITL di confine |
507
513
  | `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 |
508
514
  | `isolation.copy` | array di path relativi al repo (**unset**) | file e directory gitignorati (`.env`, `.npmrc`, config locale) copiati dal checkout principale in ogni nuovo workspace `worktree`/`clone`, senza mai sovrascrivere; i path non sicuri (assoluti, `..`, `.git`) vengono rifiutati. Se non impostata, trailhead suggerisce i file `.env` che hanno un fratello `*.example` tracciato |
@@ -528,13 +534,16 @@ Esempio di file di config (la stessa forma funziona per il `.trailhead/config.js
528
534
 
529
535
  **Mockup di design e acceptance testing.** Come si comportano le chiavi `design.*` (canvas, design system, approvazione, passaggio da claude.ai/design) è in [Mockup prima del codice](#-mockup-prima-del-codice); come `acceptance.browser` e `testing.*` guidano la Verify è in [Acceptance testing](#-acceptance-testing-uat).
530
536
 
531
- **Annidata o piatta.** Le chiavi a lista qui sopra (`isolation.copy`, `preflight.checks`, `uat.setup`, `uat.accounts`) si possono scrivere annidate (`{"uat": {"setup": [...]}}`) o piatte (`"uat.setup": [...]`); se un file ha entrambe vince la forma annidata con un avviso, e un valore di progetto sostituisce quello globale per intero (le liste non si uniscono). `git`, `release` e la *modalità* isolation restano nell'header delle conventions; solo `isolation.copy` sta in `config.json`.
537
+ <a id="esecuzioni-dei-test"></a>
538
+ **Esecuzioni dei test.** Con `testing.full` impostato, il motore esegue la suite tramite `trailhead-test-run.js`. **Coda**: al massimo `testing.maxParallel` esecuzioni pesanti alla volta su tutte le sessioni e i worktree della macchina (vince il limite più severo tra quelle attive); chi aspetta dice dietro a cosa è in coda. **Cache**: un risultato verde è salvato sotto tree SHA + comando + git common dir, quindi lo stesso codice non viene mai testato due volte (una modifica non committata ha la sua chiave; un submodule con contenuto non committato disattiva la cache per quell'esecuzione; file ignorati da git, env e toolchain non sono nella chiave, `--no-cache` forza l'esecuzione). **Priorità**: `nice -n 10` e `ionice -c 3` se presenti. Lo stato sta in `$TRAILHEAD_CACHE_DIR`, altrimenti `${XDG_CACHE_HOME:-~/.cache}/trailhead`. La cache si può cancellare quando nessuna esecuzione dei test è attiva (costa solo una riesecuzione). Il gruppo di processi lanciato viene chiuso a fine comando; un comando che ne esce (`setsid`, un daemon) è fuori da questa garanzia. **Una volta per tree**: la suite completa gira al massimo una volta per tree, al Verify (quindi una volta per ticket se dopo il Verify non cambia nulla), mentre Execute e Fix eseguono solo test mirati; senza `testing.full` cambia soltanto la regola dei test mirati.
539
+
540
+ **Annidata o piatta.** Le chiavi a lista qui sopra (`isolation.copy`, `preflight.checks`, `uat.setup`, `uat.accounts`) e le chiavi `testing.full` / `testing.targeted` / `testing.maxParallel` si possono scrivere annidate (`{"uat": {"setup": [...]}}`) o piatte (`"uat.setup": [...]`); se un file ha entrambe vince la forma annidata con un avviso, e un valore di progetto sostituisce quello globale per intero (le liste non si uniscono). `git`, `release` e la *modalità* isolation restano nell'header delle conventions; solo `isolation.copy` sta in `config.json`.
532
541
 
533
542
  ---
534
543
 
535
544
  ## 🪝 Hooks
536
545
 
537
- Oltre alle istruzioni della skill, trailhead include nove hook propri (in `hooks/`, self-contained via `${CLAUDE_PLUGIN_ROOT}`) che *impongono* le parti della disciplina che non ci si dovrebbe fidare che il modello ricordi:
546
+ Oltre alle istruzioni della skill, trailhead include dieci hook propri (in `hooks/`, self-contained via `${CLAUDE_PLUGIN_ROOT}`) che *impongono* le parti della disciplina che non ci si dovrebbe fidare che il modello ricordi:
538
547
 
539
548
  - **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.
540
549
  - **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.
@@ -542,17 +551,18 @@ Oltre alle istruzioni della skill, trailhead include nove hook propri (in `hooks
542
551
  - **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. I target di redirezione (`2>/dev/null`) non contano come letture, e un `cd` alla directory della sessione stessa non la attiva. Solo Claude Code; non è proiettata su Codex, che non ha una regola di deny su `Read()`.
543
552
  - **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.
544
553
  - **Labels guard** (`PreToolUse` su `gh issue create`): controlla il set di label di un ticket già alla creazione. Una create con un'etichetta `trailhead:*` deve portare la forma completa del ticket: `trailhead:ticket`, esattamente un tipo (`decision`, `research`, `prototype`, `build`, `bug`, `task`) e esattamente una destinazione (una `trailhead:map-<n>` oppure `trailhead:whiteboard`, mai entrambe, mai due mappe); il blocco nomina cosa manca o è in conflitto e chiede di riprovare con tutte le label nella stessa create, mai con un `--add-label` successivo. Gli issue non-ticket (`trailhead:map`, `dashboard`, `codebase`, `conventions`, `fog`, `unverified`) sono esenti. Anche una create senza alcuna label `trailhead:*` viene bloccata, ma solo dentro un repo trailhead (la working directory è in un work tree git il cui top level ha `.trailhead/`) e non quando punta a un altro repo con `-R`/`--repo`; un issue non-trailhead voluto si esenta anteponendo `TRAILHEAD_PLAIN_ISSUE=1` al comando. Un valore di label non risolvibile staticamente (`$VAR`, backtick, `$(...)`) non viene mai bloccato.
554
+ - **Test guard** (`PreToolUse` su `Bash`): più sessioni che lanciano l'intera suite di test insieme affamano la macchina, quindi quando `testing.full` è impostato (`.trailhead/config.json` del progetto o config globale) una chiamata Bash che esegue quel comando nudo viene bloccata, indicando i test mirati (i file toccati, o `testing.targeted`) o l'helper di coda `trailhead-test-run.js`. Confronta il comando statement per statement dopo aver tolto prefissi env, redirezioni e i wrapper `env`, `time`, `command`, `nice` e `timeout`, quindi `CI=1 npm test`, `timeout 600 npm test` e `npm test 2>&1 | tail -20` vengono presi, come un `testing.full` composto (`npm run build && npm test`) eseguito così com'è; flag extra (`npm test -- --coverage`) contano ancora come suite completa, mentre qualsiasi argomento non-flag (`npm test -- hooks/x.test.js`) è una run mirata e passa. Un `cd` prima del comando (working directory ignota) e l'invocazione dell'helper stesso passano. Spento se `testing.full` non è impostato. Un guard lessicale per le esecuzioni accidentali: `bash -c` ed `eval` non vengono scartocciati.
545
555
  - **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 `<`), compreso il file di script o di pattern passato con `-f`/`--file` a `grep`, `sed` e `awk`. Viene bloccato anche un wrapper di comando (`env`, `nice`, `timeout`, `nohup`, `sudo`, `stdbuf`, `time`, `setsid`, `doas`) davanti a una shell annidata (`bash -c`) o a `eval` vicino a un path che sembra un segreto, perché il wrapper nasconderebbe il comando interno. È 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.
546
556
  - **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.
547
557
  - **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.
548
558
 
549
- I guard di commit, secret, install, search, body e labels 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 sei (tre processi hook per chiamata Bash in tutto: l'hook unificato, il secret-read guard e l'injection scanner). Restano sei 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,labels`, perché lì non c'è la search guard.
559
+ I guard di commit, secret, install, search, body, labels e test 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 sette (tre processi hook per chiamata Bash in tutto: l'hook unificato, il secret-read guard e l'injection scanner). Restano sette 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,labels,test`, perché lì non c'è la search guard.
550
560
 
551
561
  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.
552
562
 
553
563
  Accanto a questi trailhead installa un **hook git `commit-msg`** (un wrapper POSIX `sh`, quindi funziona anche nei repo con `"type": "module"`) che impone le stesse regole di commit su ogni host, più il trailer `Refs: #<n>` mentre c'è un ticket di sessione attivo. Se non può girare (script mancante, nessun `node`) stampa `trailhead: commit-msg hook inactive (<reason>); commit rules not enforced.` su stderr e lascia passare il commit; un hook già installato viene aggiornato al prossimo avvio di un ticket.
554
564
 
555
- Tutti e nove 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.
565
+ Tutti e dieci 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. Il test guard è l'eccezione: agisce solo dove `testing.full` è impostato. Per fare opt-out, disabilita gli hook del plugin nelle impostazioni di Claude Code.
556
566
 
557
567
  ---
558
568
 
package/README.md CHANGED
@@ -6,13 +6,13 @@
6
6
 
7
7
  🇬🇧 **English** · 🇮🇹 [Italiano](README.it.md)
8
8
 
9
- [![website](https://img.shields.io/badge/website-trailhead.marcomigozzi.it-3C7A5A?style=flat-square)](https://trailhead.marcomigozzi.it) [![site CI](https://img.shields.io/github/actions/workflow/status/ToRvaLDz/trailhead/site-ci.yml?branch=main&style=flat-square&label=site%20CI)](https://github.com/ToRvaLDz/trailhead/actions/workflows/site-ci.yml) [![npm](https://img.shields.io/npm/v/@marcomigozzi/trailhead?style=flat-square&label=npm&color=CB3837)](https://www.npmjs.com/package/@marcomigozzi/trailhead) [![license](https://img.shields.io/github/license/ToRvaLDz/trailhead?style=flat-square&color=3C7A5A)](LICENSE) ![hosts](https://img.shields.io/badge/hosts-Claude%20Code%20%C2%B7%20Codex-1E2A54?style=flat-square) ![commands](https://img.shields.io/badge/commands-24%20verbs-0e8a16?style=flat-square) ![guardrails](https://img.shields.io/badge/guardrails-9%20hooks-e8710a?style=flat-square)
9
+ [![website](https://img.shields.io/badge/website-trailhead.marcomigozzi.it-3C7A5A?style=flat-square)](https://trailhead.marcomigozzi.it) [![site CI](https://img.shields.io/github/actions/workflow/status/ToRvaLDz/trailhead/site-ci.yml?branch=main&style=flat-square&label=site%20CI)](https://github.com/ToRvaLDz/trailhead/actions/workflows/site-ci.yml) [![npm](https://img.shields.io/npm/v/@marcomigozzi/trailhead?style=flat-square&label=npm&color=CB3837)](https://www.npmjs.com/package/@marcomigozzi/trailhead) [![license](https://img.shields.io/github/license/ToRvaLDz/trailhead?style=flat-square&color=3C7A5A)](LICENSE) ![hosts](https://img.shields.io/badge/hosts-Claude%20Code%20%C2%B7%20Codex-1E2A54?style=flat-square) ![commands](https://img.shields.io/badge/commands-24%20verbs-0e8a16?style=flat-square) ![guardrails](https://img.shields.io/badge/guardrails-10%20hooks-e8710a?style=flat-square)
10
10
 
11
11
  </div>
12
12
 
13
13
  **Start and drive large projects as a map of tickets on GitHub Issues, resolving one at a time until the way to the destination is clear.**
14
14
 
15
- `trailhead` is an orchestrator skill for coding agents, running on [Claude Code](https://docs.claude.com/en/docs/claude-code) and [Codex CLI](https://developers.openai.com/codex/cli) from a single source. It gives a big, foggy idea a place to begin (a *trailhead*) and a disciplined way to walk it to a working result, with the whole plan living on your issue tracker instead of in scattered local files.
15
+ `trailhead` is an orchestrator skill for coding agents, running on [Claude Code](https://code.claude.com/docs/en/overview) and [Codex CLI](https://developers.openai.com/codex/cli) from a single source. It gives a big, foggy idea a place to begin (a *trailhead*) and a disciplined way to walk it to a working result, with the whole plan living on your issue tracker instead of in scattered local files.
16
16
 
17
17
  ---
18
18
 
@@ -71,7 +71,7 @@ npx @marcomigozzi/trailhead --dir=<path> # target a specific config dir
71
71
  ```
72
72
  On **Claude Code** it copies the skill (+ its `references/`), the `/trailhead:*` commands, the hooks (into `hooks/`, registered in `settings.json`) including a SessionStart **update check** (which also runs the `gh` token check), and the templates (label-guard workflow, statusline, the git `commit-msg` hook and the helper scripts the engine runs), idempotently.
73
73
 
74
- On **Codex CLI** (`--codex`) it projects a native Codex skill invoked as **`$trailhead`** (`$trailhead work`, `$trailhead new "idea"`, …), one thin **`$trailhead-<verb>`** skill per verb for discoverability, the eight guardrails as native Codex **hooks** (`~/.codex/hooks.json`, enabling `features.hooks`; the five Bash guards share one process behind a single registration; the search guard is Claude-Code-only and isn't projected to Codex), and per-technique subagent model pins from **`models.codex.*`** into `~/.codex/agents/`. Codex **0.145.0+** is required (the installer gates on `codex --version`). The `artifacts` mockup mode (Claude Code's `Artifact` tool) isn't available on Codex; the cross-host `design: stitch` mode (Google Stitch MCP) is the hosted option there, with local disk as the fallback.
74
+ 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 nine guardrails as native Codex **hooks** (`~/.codex/hooks.json`, enabling `features.hooks`; the six 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.
75
75
 
76
76
  Re-run the install to update, or run **`/trailhead:update`** (Claude) / **`$trailhead update`** (Codex) from inside the agent: it detects how trailhead was installed and installs the newer version where that is safe (a `git pull` for a dev-symlink, an `npx` re-run for npm, or `/plugin update` for the plugin). When a newer version exists, the statusline shows a `⬆ trailhead <version>` flag (Claude).
77
77
 
@@ -94,7 +94,7 @@ The fog holds only what you cannot phrase yet: a question you can already phrase
94
94
 
95
95
  A map is scoped to **one** effort, so knowledge that belongs to the *repo* (not to any single map) lives in **repo-scoped anchor issues**, created once and shared by every map (each map's Notes just links them, so nothing is stranded when a map finishes):
96
96
 
97
- - **`trailhead:codebase`**: the distilled codebase map (architecture, stack, conventions, risks, test/build), written once at adopt and refreshed only on major drift.
97
+ - **`trailhead:codebase`**: the distilled codebase map (architecture, stack, conventions, risks, test/build), written once at adopt and refreshed only on major drift. Each facet carries an `_as of <short sha>_` stamp of the commit it was written or last patched against.
98
98
  - **`trailhead:conventions`**: the project's **way of working**, readable by everyone: a small machine-read header the engine obeys (`git: main|pr`, `release: command|auto`, `isolation: auto|none|worktree|clone`) over human prose. `/trailhead:adopt` and `:new` ask for it up front. `isolation: auto` (the default) decides per ticket at work-start: it asks whether you'll work more than one ticket at once and, if so, uses a `git worktree` (plain repo) or a `clone` (submodule superrepo); otherwise it works in the current checkout. `isolation: worktree` gives each executing ticket its own `git worktree` + branch (concurrent sessions on one clone never share a working tree); `isolation: clone` gives it a dedicated clone instead, for path-bound apps a worktree can't build and for submodule-bearing superrepos (where `worktree` is escalated to `clone`).
99
99
  - **`trailhead:dashboard`**: the pinned index of the whole surface, a link to every open map (with GitHub's native progress bars), the whiteboard, and live counts (inbox, whiteboard frontier). Refreshed when a map is charted or exhausted, when a whiteboard ticket is born or resolved (it has no native progress bar, so the dashboard is the only place it shows; a `quick`-born ticket, worked and resolved in one sitting, refreshes at its handoff or pause, not at creation), and on demand via `/trailhead:dashboard`.
100
100
 
@@ -219,7 +219,7 @@ Every verb is also a namespaced command (`/trailhead:new`, `/trailhead:work`, `/
219
219
  | `/trailhead:ticket <type> <title>` | open a ticket on the fly (diverges briefly first: a micro-charting act) |
220
220
  | `/trailhead:split [ticket]` | split an oversized ticket into children, supersede the original |
221
221
  | `/trailhead:auto [map]` | run a chosen scope (one map, the whiteboard or everything) in up to 4 parallel slots, until exhaustion or a stop; parks what needs you |
222
- | `/trailhead:grill [topic]` | run a standalone grilling session on a decision/topic |
222
+ | `/trailhead:grill [topic\|ticket]` | run a standalone grilling session on a decision/topic |
223
223
  | `/trailhead:map` | show the low-res map (destination, decisions, frontier, fog) |
224
224
  | `/trailhead:dashboard` | show the repo dashboard: the pinned index of every open map, the whiteboard, and live counts |
225
225
  | `/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+) |
@@ -228,15 +228,15 @@ Every verb is also a namespaced command (`/trailhead:new`, `/trailhead:work`, `/
228
228
 
229
229
  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.
230
230
 
231
- ![The map panel docked beside the transcript: open maps, frontier with a work button per ticket, in progress, blocked, and a footer with refresh and close](assets/screenshots/map-panel.png)
231
+ <img src="assets/screenshots/map-panel.png" width="520" alt="The map panel with each section in its own colour: open maps, in progress, needs you, frontier with a work button per ticket, UAT, paused, blocked, the burn-down, and a footer with refresh and close">
232
232
 
233
- The map panel lists the open maps to view, your tickets in progress right under them (in an `In progress` group, from every map and the whiteboard), the tickets an autonomous run parked for you in a `Needs you` group, the frontier with a `work` button per ticket that clears the context and starts the ticket (in the map's checkout; if that checkout has moved, it is found again by its `origin` remote, and you pick one when several match), who holds what (your own ticket is marked `this session`), the pending UAT follow-ups in a `UAT` group (each naming the ticket it tests), the paused ones in a `Paused` group, and what is blocked. A dim line under the Whiteboard row totals the open tickets across every open map plus the whiteboard, the same number `/trailhead:map` states (`at least N` when more open maps exist than the panel reads). The footer has `refresh` and `close`. `/trailhead:panel` toggles it (asked at session start, before the screen is ready, it opens as soon as it is), and it opens by itself once in fullscreen.
233
+ Each section of the map panel has its own glyph and colour, with a coloured gutter along its rows. The panel lists the open maps to view, your tickets in progress right under them (in an `In progress` group, from every map and the whiteboard), the tickets an autonomous run parked for you in a `Needs you` group, the frontier with a `work` button per ticket that clears the context and starts the ticket (in the map's checkout; if that checkout has moved, it is found again by its `origin` remote, and you pick one when several match), who holds what (your own ticket is marked `this session`), the pending UAT follow-ups in a `UAT` group (each naming the ticket it tests), the paused ones in a `Paused` group, and what is blocked. A dim line under the Whiteboard row totals the open tickets across every open map plus the whiteboard, the same number `/trailhead:map` states (`at least N` when more open maps exist than the panel reads). The footer has `refresh` and `close`. `/trailhead:panel` toggles it (asked at session start, before the screen is ready, it opens as soon as it is), and it opens by itself once in fullscreen. In the desktop app a session starts at its first message, so the panel cannot appear on a new session where nothing has been typed; `/trailhead:panel` as that first message opens it.
234
234
 
235
- ![The panel viewing another map, with a viewing and active header and a back to active button](assets/screenshots/map-panel-viewing.png)
235
+ <img src="assets/screenshots/map-panel-viewing.png" width="520" alt="The panel viewing another map, with a viewing and active header and a back to active button">
236
236
 
237
237
  A click only views a map, and `back to active` returns to the active one. The active map changes when you work one of its tickets. A `Whiteboard` row above the maps list, with its `N open / M` counts, shows the repo index instead: every open map (a click opens it) and the whiteboard's loose tickets by state, each frontier one with its `work` button; `map` goes back.
238
238
 
239
- ![The Burn-down section of the panel: counts line and a chart of open tickets over time](assets/screenshots/burndown.png)
239
+ <img src="assets/screenshots/burndown.png" width="520" alt="The Burn-down section of the panel: counts line and a chart of open tickets over time">
240
240
 
241
241
  The burn-down shows the open tickets over time, from the map's first ticket to now, with done and removed counted apart.
242
242
 
@@ -388,7 +388,7 @@ Everything the map needs is expressed as GitHub labels, so state is queryable in
388
388
  - **Structural:** `trailhead:map`, `trailhead:ticket`
389
389
  - **Repo-scoped anchors (one each per repo, pinned):** `trailhead:codebase` 🧱 (the distilled codebase map), `trailhead:conventions` 📜 (the way of working), `trailhead:dashboard` 📊 (the pinned index of maps + whiteboard + counts); each title carries its icon (like the map's 🗺) so the pinned anchors stand apart at a glance
390
390
  - **Type (one per ticket):** 🧭 `trailhead:decision` · 🔬 `research` · 🎨 `prototype` · 🔨 `build` · 🐛 `bug` · 🔧 `task`
391
- - **State:** `trailhead:blocked` (has an open blocker) · `seed` (parked on a trigger) · `out-of-scope` (closed, beyond the destination) · `superseded` (closed, split into children) · `paused` (checkpointed, off the frontier) · `needs-you` (parked by an autonomous run for you: runs skip it, it stays on your frontier) · `unverified` (quarantined until a maintainer adopts it) · `fog` (kept open for clarification)
391
+ - **State:** `trailhead:blocked` (has an open blocker) · `seed` (parked on a trigger) · `out-of-scope` (closed, beyond the destination) · `superseded` (closed, split into children) · `paused` (checkpointed, off the frontier) · `needs-you` (parked by an autonomous run for you: runs skip it, it stays on your frontier) · `unverified` (quarantined until a maintainer adopts it) · `fog` (kept open for clarification) · `needs-info` (inbox issue waiting on its reporter)
392
392
  - **Container:** `trailhead:whiteboard` (a loose, map-less ticket, off every map's frontier, on the whiteboard's own)
393
393
  - **Marker:** `trailhead:uat` (a pending UAT follow-up; never a frontier filter) · `trailhead:map-<n>` (scopes a ticket to its map)
394
394
 
@@ -407,8 +407,9 @@ Many people (and their agent sessions) share one map and work it concurrently:
407
407
  - **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).
408
408
  - **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.
409
409
  - **State lives on the tickets** (claim, labels, comments; all conflict-free); the map body is a re-readable index, not the source of truth. The durable record of a resolution is the ticket's own comment.
410
+ - **Forks write where you mean.** In a fork clone `gh` can resolve the parent repository, so once per session, before the first tracker write, trailhead checks where issue writes would land. When that is ambiguous it asks which repository to write to, makes it the `gh` default and remembers the answer in `.trailhead/repo-target.json`. Only the remote named `origin` on github.com is read as the fork side.
410
411
  - **Labels are protected from outsiders.** `trailhead:*` labels drive the map, so trailhead trusts a labelled issue only when every `trailhead:*` label on it was applied by a trusted labeller (a write-access collaborator, `github-actions[bot]`, or a login in the `TRAILHEAD_LABEL_ALLOWLIST` repo variable: the same labellers the label guard never strips) and (for a ticket) it carries a valid `Parent:`. Anything else is quarantined as `trailhead:unverified`, off the frontier. GitHub already blocks non-write users from labelling; against triage users and automations, trailhead can **install the label guard GitHub Action for you** (from `templates/trailhead-label-guard.yml`) into your repo's `.github/workflows/`, where it strips unauthorized `trailhead:*` labels at the source. Installing a workflow needs a token with the `workflow` scope, so trailhead runs `gh auth refresh -s workflow` if the push is rejected. For automations (CI, cron, bots) that open tickets without a chat session, `templates/trailhead-capture.js` captures a bug, todo, seed or idea with the full label wiring and refuses to run as an identity the guard would strip, and on GitHub the [`actions/capture`](#capture-from-ci) composite Action wraps it in a few lines of workflow.
411
- - **Issues opened by others are triaged, not trusted blindly.** `/trailhead:inbox` lists inbound issues (bug reports, requests, questions that aren't yet trailhead tickets) and integrates the worthwhile ones **in place**: reframing them as tickets, keeping the reporter's authorship, and applying the labels on adoption. The rest routes to fog, out-of-scope, duplicate, or needs-info. Quarantined `trailhead:unverified` issues are listed there too, to adopt or reject.
412
+ - **Issues opened by others are triaged, not trusted blindly.** `/trailhead:inbox` lists inbound issues (bug reports, requests, questions that aren't yet trailhead tickets) and integrates the worthwhile ones **in place**: reframing them as tickets, keeping the reporter's authorship, and applying the labels on adoption. The rest routes to fog, out-of-scope, duplicate, or needs-info. A needs-info issue is parked with the question posted first and leaves the queue; it comes back in the inbox's *Waiting on the reporter* section only when the reporter answers, and after `inbox.needs_info_days` (default 14) with no answer the inbox offers to close it, never closing it on its own. Quarantined `trailhead:unverified` issues are listed there too, to adopt or reject.
412
413
 
413
414
  ### Capture from CI
414
415
 
@@ -424,7 +425,7 @@ jobs:
424
425
  steps:
425
426
  # ... your build and test steps ...
426
427
  - if: failure()
427
- uses: ToRvaLDz/trailhead/actions/capture@v0.17.0
428
+ uses: ToRvaLDz/trailhead/actions/capture@v0.18.0
428
429
  with:
429
430
  type: bug
430
431
  title: "CI failed on ${{ github.ref_name }}"
@@ -478,11 +479,11 @@ Three layers: **nearest wins**, key by key (a key unset at one layer inherits th
478
479
  - **Global**: `trailhead/config.json` in your host's config directory: `$CLAUDE_CONFIG_DIR` (default `~/.claude`) on Claude Code, `$CODEX_HOME` (default `~/.codex`) on Codex. Your standing defaults across every project.
479
480
  - **Defaults**: the built-in values.
480
481
 
481
- `/trailhead:config` runs a **guided, menu-driven setup**: pick the scope, then walk each setting (⚙️ way of working (git · release · isolation) · 🌐 ticket language · 🧠 models (a High / Balanced / Low profile, or Manual) · 🎨 design + approval · 🧪 TDD · ⚖️ effort · 🖥️ acceptance testing (browser, UAT setup, a test account) · 🧑‍⚖️ plan review · 📊 statusline) as an icon-labelled menu; no hand-editing JSON. Every step is asked (none skipped), and **plan and execute models are always two separate, version-pinned choices**. `config get` prints the effective merged config; `config set <key> <value>` writes one key.
482
+ `/trailhead:config` runs a **guided, menu-driven setup**: pick the scope, then walk each setting (⚙️ way of working (git · release · isolation) · 🌐 ticket language · 🧠 models (a High / Balanced / Low profile, or Manual) · 🎨 design + approval · 🧪 TDD (and test suite) · ⚖️ effort · 🖥️ acceptance testing (browser, UAT setup, a test account) · 🧑‍⚖️ plan review · 📊 statusline) as an icon-labelled menu; no hand-editing JSON. Every step is asked (none skipped), and **plan and execute models are always two separate, version-pinned choices**. `config get` prints the effective merged config; `config set <key> <value>` writes one key.
482
483
 
483
484
  `/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.
484
485
 
485
- `/trailhead:audit [map] [--all]` hunts regressions: it checks that the tickets closed since the last audit are still true against the current code. Code a ticket's commits left untouched is classed unchanged with no agent (a fast, deterministic git check that relies on the `Refs: #<n>` commit trailers), decisions, research, tasks and prototypes get a light inline check, and a full verification agent runs only where the code changed or the fast path cannot decide (no `Refs:` commits, for example). Each ticket is classed implemented, unchanged, evolved (a later ticket changed it on purpose, listed with no proposal), implemented wrong, or not implemented. It also flags decisions closed with no build ticket carrying their answer. It then compares the open ones for duplicates and groupable clusters. It is read-only until you confirm: every fix (a regression bug, a reopen, the missing build tickets of a decision, a duplicate close, a supersede-and-merge) is a proposal applied only on an explicit yes. `--all` re-audits every closed ticket.
486
+ `/trailhead:audit [map] [--all]` hunts regressions: it checks that the tickets closed since the last audit are still true against the current code. Code a ticket's commits left untouched is classed unchanged with no agent (a fast, deterministic git check that relies on the `Refs: #<n>` commit trailers), decisions, research, tasks and prototypes get a light inline check, and a full verification agent runs only where the code changed or the fast path cannot decide (no `Refs:` commits, for example). A whole-repo audit also flags codebase facets whose cited files changed after their `_as of <short sha>_` stamp (report-only). Each ticket is classed implemented, unchanged, evolved (a later ticket changed it on purpose, listed with no proposal), implemented wrong, or not implemented. It also flags decisions closed with no build ticket carrying their answer. It then compares the open ones for duplicates and groupable clusters. It is read-only until you confirm: every fix (a regression bug, a reopen, the missing build tickets of a decision, a duplicate close, a supersede-and-merge) is a proposal applied only on an explicit yes. `--all` re-audits every closed ticket.
486
487
 
487
488
  The **📊 statusline** step offers to install trailhead's Claude Code status bar: one line with **model · project · branch · plan usage (`5h %` · reset · `7d %`) · a context-window bar**, plus a **second line with the active ticket** (`▸ #N Title`) whenever you're working one, and a `⬆ trailhead <version>` flag when a newer trailhead is available (run `/trailhead:update`). The project is always the main repo's name even from an isolated checkout, and the branch carries a `(WT)` tag in a worktree or `(C)` in a per-ticket clone (nothing on the original checkout). It's a global Claude Code setting; if you already run a statusline (e.g. `ccstatusline`) the setup asks before replacing it, and the script also exposes `--ticket-only` / `--context-only` / `--usage-only` segments to slot into an existing tool.
488
489
 
@@ -501,8 +502,13 @@ The **📊 statusline** step offers to install trailhead's Claude Code status ba
501
502
  | `tdd` | **`seams`** \| `on` \| `off` | how the `build` engine tests |
502
503
  | `acceptance.browser` | **`auto`** \| `on` \| `off` | drive the browser in Verify (after asking who drives, AI or human, or skip); `off` still runs AI-driven checks via commands, and walks you through a conversational UAT for anything only you can do (step by step in chat, not a checklist to self-serve) |
503
504
  | `testing.webapp` / `testing.url` | bool / URL | is it browser-drivable, and where |
505
+ | `testing.full` | shell command (**unset**) | the full test suite; when set, the engine runs it through the [test queue and cache](#test-runs) |
506
+ | `testing.targeted` | shell command with `{files}` (**unset**) | a focused run; `{files}` becomes the single-quoted files |
507
+ | `testing.maxParallel` | positive integer (**`1`**) | how many heavy test runs may run at once across the whole machine |
504
508
  | `plan_review` | **`off`** \| `on` \| CLI list | send `build` PLANs to external AI CLIs (Gemini, Codex, …) for a second opinion and converge on their concerns |
505
509
  | `plan_review.rounds` | integer (**`2`**) | max converge-and-re-review rounds |
510
+ | `plan_review.context` | object (`{"<cli>": <tokens>}`) | override a reviewer CLI's context window; a reviewer whose estimated prompt is over 65% of its window is skipped and recorded in `PLAN-REVIEW` |
511
+ | `inbox.needs_info_days` | positive integer (**`14`**) | days an inbox issue may wait on its reporter before the inbox offers to close it (never closes on its own) |
506
512
  | `code_review.rounds` | integer (**`2`**) | max fix-and-re-review rounds in Code review before its boundary HITL checkpoint |
507
513
  | `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 |
508
514
  | `isolation.copy` | array of repo-relative paths (**unset**) | gitignored files and directories (`.env`, `.npmrc`, local config) copied from the main checkout into every new `worktree`/`clone` workspace, never overwriting; unsafe paths (absolute, `..`, `.git`) are rejected. When unset, trailhead suggests the `.env` files that have a tracked `*.example` sibling |
@@ -528,13 +534,16 @@ Example config file (the same shape works for the project `.trailhead/config.jso
528
534
 
529
535
  **Design mockups and acceptance testing.** How the `design.*` keys play out (canvas, design system, approval, moving from claude.ai/design) is in [Mockups before code](#-mockups-before-code); how `acceptance.browser` and `testing.*` drive Verify is in [Acceptance testing](#-acceptance-testing-uat).
530
536
 
531
- **Nested or flat.** The list keys above (`isolation.copy`, `preflight.checks`, `uat.setup`, `uat.accounts`) can be written nested (`{"uat": {"setup": [...]}}`) or flat (`"uat.setup": [...]`); if a file has both, the nested form wins with a warning, and a project value replaces the global one as a whole (lists are not merged). `git`, `release` and the isolation *mode* stay in the conventions header; only `isolation.copy` lives in `config.json`.
537
+ <a id="test-runs"></a>
538
+ **Test runs.** With `testing.full` set, the engine runs the suite through `trailhead-test-run.js`. **Queue**: at most `testing.maxParallel` heavy runs at once across every session and worktree on the machine (the strictest limit among live runs wins); a waiting run says what it is queued behind. **Cache**: a green result is stored under tree SHA + command + git common dir, so identical code is never tested twice (an uncommitted edit gets its own key; a submodule with uncommitted content disables caching for that run; gitignored files, env and toolchain are not in the key, `--no-cache` forces a run). **Priority**: `nice -n 10` and `ionice -c 3` when present. State lives in `$TRAILHEAD_CACHE_DIR`, else `${XDG_CACHE_HOME:-~/.cache}/trailhead`. The cache dir is safe to delete when no test run is active (it only costs a re-run). The launched process group is torn down when the command ends; a command that escapes it (`setsid`, a daemon) is outside that guarantee. **Once per tree**: the full suite runs at most once per tree, at Verify (so once per ticket when nothing changes after Verify), while Execute and Fix run focused tests only; without `testing.full` only the focused-tests rule changes.
539
+
540
+ **Nested or flat.** The list keys above (`isolation.copy`, `preflight.checks`, `uat.setup`, `uat.accounts`) and the `testing.full` / `testing.targeted` / `testing.maxParallel` keys can be written nested (`{"uat": {"setup": [...]}}`) or flat (`"uat.setup": [...]`); if a file has both, the nested form wins with a warning, and a project value replaces the global one as a whole (lists are not merged). `git`, `release` and the isolation *mode* stay in the conventions header; only `isolation.copy` lives in `config.json`.
532
541
 
533
542
  ---
534
543
 
535
544
  ## 🪝 Hooks
536
545
 
537
- Beyond the skill's instructions, trailhead ships nine of its own hooks (in `hooks/`, self-contained via `${CLAUDE_PLUGIN_ROOT}`) that *enforce* the parts of the discipline the model shouldn't be trusted to remember:
546
+ Beyond the skill's instructions, trailhead ships ten of its own hooks (in `hooks/`, self-contained via `${CLAUDE_PLUGIN_ROOT}`) that *enforce* the parts of the discipline the model shouldn't be trusted to remember:
538
547
 
539
548
  - **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.
540
549
  - **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.
@@ -542,17 +551,18 @@ Beyond the skill's instructions, trailhead ships nine of its own hooks (in `hook
542
551
  - **Search guard** (`PreToolUse` on `Bash`): structural backstop for the search-command-hygiene rule (never `cd` then read/search a relative path). It hard-blocks a single Bash command that both changes directory (`cd`/`pushd`/`env -C`) and, in a joined or later statement, reads/searches a relative path (`grep`/`rg`/`ag`/`cat`/`head`/`tail`/`wc`/`nl`/`sed`/`awk`/`less`/`more`, or `git grep`/`log`/`show`/`diff` without a `-C`), so the shape that trips a `Read()` deny rule under bypass permissions (falling back to a manual approval prompt instead of running headless) never runs at all, rather than merely being discouraged in the agent's own prose. Redirection targets (`2>/dev/null`) are not treated as reads, and a `cd` to the session's own directory does not arm it. Claude Code only; not projected to Codex, which has no such `Read()` deny rule.
543
552
  - **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.
544
553
  - **Labels guard** (`PreToolUse` on `gh issue create`): checks the label set of a ticket at the create call. A create carrying any `trailhead:*` label must carry the whole ticket shape: `trailhead:ticket`, exactly one type (`decision`, `research`, `prototype`, `build`, `bug`, `task`) and exactly one destination (one `trailhead:map-<n>` or `trailhead:whiteboard`, never both, never two maps); the block names what is missing or conflicting and asks to retry with every label in the same create call, never a later `--add-label`. Non-ticket issues (`trailhead:map`, `dashboard`, `codebase`, `conventions`, `fog`, `unverified`) are exempt. A create with no `trailhead:*` label at all is blocked too, but only inside a trailhead repo (the working directory sits in a git work tree whose top level has `.trailhead/`) and not when aimed at another repo with `-R`/`--repo`; a deliberate non-trailhead issue opts out by prefixing the command with `TRAILHEAD_PLAIN_ISSUE=1`. A label value the guard cannot resolve statically (`$VAR`, backticks, `$(...)`) is never blocked.
554
+ - **Test guard** (`PreToolUse` on `Bash`): several sessions running the whole test suite at once starve the machine, so when `testing.full` is set (project `.trailhead/config.json` or the global config) a Bash call that runs that bare command is hard-blocked, pointing at focused tests (the files you touched, or `testing.targeted`) or the queue helper `trailhead-test-run.js`. It matches the command statement by statement after dropping env prefixes, redirections and the wrappers `env`, `time`, `command`, `nice` and `timeout`, so `CI=1 npm test`, `timeout 600 npm test` and `npm test 2>&1 | tail -20` are caught, as is a compound `testing.full` (`npm run build && npm test`) run as written; extra flags (`npm test -- --coverage`) still count as the full suite, while any non-flag argument (`npm test -- hooks/x.test.js`) is a focused run and passes. A `cd` before the command (working directory unknown) and the helper's own invocation pass. Off when `testing.full` is unset. A lexical guard for accidental runs: `bash -c` and `eval` are not unwrapped.
545
555
  - **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), including the `-f`/`--file` script or pattern file of `grep`, `sed`, and `awk`. A command wrapper (`env`, `nice`, `timeout`, `nohup`, `sudo`, `stdbuf`, `time`, `setsid`, `doas`) in front of a nested shell (`bash -c`) or `eval` next to a secret-looking path is blocked too, since the wrapper would hide the inner command. 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.
546
556
  - **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.
547
557
  - **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.
548
558
 
549
- The commit, secret, install, search, body, and labels guards run in **one process**: a single `trailhead-bash-guards.js` registration evaluates them in that order, so a Bash call pays one node start-up instead of six (three hook processes per Bash call in all: the merged hook, the secret-read guard, and the injection scanner). They remain six distinct guards with unchanged verdicts; the first block wins, and a guard that errors allows on its own without stopping the others. A guard whose module is missing or broken is skipped too, but never silently: the hook tells the session which guard did not check the command, so a partial install shows up. On Codex the same hook is registered with `--guards=commit,secret,install,body,labels`, since there is no search guard there.
559
+ The commit, secret, install, search, body, labels, and test 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 seven (three hook processes per Bash call in all: the merged hook, the secret-read guard, and the injection scanner). They remain seven distinct guards with unchanged verdicts; the first block wins, and a guard that errors allows on its own without stopping the others. A guard whose module is missing or broken is skipped too, but never silently: the hook tells the session which guard did not check the command, so a partial install shows up. On Codex the same hook is registered with `--guards=commit,secret,install,body,labels,test`, since there is no search guard there.
550
560
 
551
561
  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.
552
562
 
553
563
  Alongside them trailhead installs a git **`commit-msg` hook** (a POSIX `sh` wrapper, so it also works in `"type": "module"` repos) that enforces the same commit rules on every host, plus the `Refs: #<n>` trailer while a session ticket is active. If it cannot run (script missing, no `node`) it prints `trailhead: commit-msg hook inactive (<reason>); commit rules not enforced.` to stderr and lets the commit through; an installed hook is upgraded at the next ticket start.
554
564
 
555
- All nine are crash-safe (any error → allow) and active whenever the plugin is installed. The commit, secret, install, search, body, and secret-read guards therefore apply in **every** repo, not only trailhead projects: intentional, since conventional commits, no `Co-Authored-By`, no leaked secrets, no unvetted installs, no `cd`-then-relative-read, no clobbered issue bodies, and no readable `.env`/`.secrets` are trailhead's standing rules. To opt out, disable the plugin's hooks in your Claude Code settings.
565
+ All ten 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. The test guard is the exception: it only acts where `testing.full` is set. To opt out, disable the plugin's hooks in your Claude Code settings.
556
566
 
557
567
  ---
558
568
 
@@ -49,7 +49,10 @@ ok('build/bug Verify start with the preflight, before the tests and Code review'
49
49
  return p >= 0 && p < l.indexOf('**Code review**') && p < l.indexOf('**Goal-backward verification**') && p < l.indexOf('**Acceptance testing**');
50
50
  }));
51
51
  ok('Verify runs the tests after the sync, before the Code review',
52
- [buildVerify, bugVerify].every((i) => lines[i].indexOf('then re-run the tests') > 0 && lines[i].indexOf('then re-run the tests') < lines[i].indexOf('**Code review**')));
52
+ [buildVerify, bugVerify].every((i) => {
53
+ const run = lines[i].indexOf('runs the full suite once on the synced tree');
54
+ return run > lines[i].indexOf('then sync') && run < lines[i].indexOf('**Code review**');
55
+ }));
53
56
  ok('Resolve names git: pr without isolation',
54
57
  [buildResolve, bugResolve].every((i) => lines[i].includes('`git: pr` without isolation: on the ticket branch, immediately before the push and PR')));
55
58
  ok('Resolve runs it before the Pending-UAT guard',
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ // Docs test for #361: a bulk create (chart, adopt, split, a cluster) writes a cross-reference
3
+ // to a ticket of the same batch only in the wiring pass, from the ids just created, never guessed.
4
+ // Plain asserts, no framework. Run: node bin/bulk-cross-reference-docs.test.js
5
+ const assert = require('assert');
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ const skills = path.resolve(__dirname, '..', 'plugins', 'trailhead', 'skills');
10
+ const read = (p) => fs.readFileSync(path.join(skills, p), 'utf8');
11
+ const commands = read('_shared/substrate-commands.md');
12
+ const charting = read('trailhead-chart/references/charting.md');
13
+ const chart = read('trailhead-chart/SKILL.md');
14
+ const teamwork = read('_shared/teamwork.md');
15
+ const RULE = '**Cross-references in a bulk create.**';
16
+ let passed = 0;
17
+ const ok = (name, cond) => { assert.ok(cond, name); passed++; };
18
+
19
+ const rule = (commands.split('\n').find((l) => l.startsWith(RULE))) || '';
20
+ ok('cookbook: states the rule', rule !== '');
21
+ ok('cookbook: covers every #n and ticket link, not only blockers', /every `#n` and ticket link/.test(rule));
22
+ ok('cookbook: names the bulk paths', ['chart', 'adopt', 'split'].every((w) => rule.includes(w)));
23
+ ok('cookbook: the first pass refers to a sibling by name only', /first pass/.test(rule) && /by name/.test(rule));
24
+ ok('cookbook: written in the wiring pass from the ids just created', /wiring pass/.test(rule) && /ids just created/.test(rule));
25
+ ok('cookbook: says why a guess is harmful', /auto-links? to an unrelated issue/.test(rule));
26
+ ok('cookbook: the body edit obeys the fail-safe rule', /Fail-safe body rewrites/.test(rule));
27
+
28
+ const step4 = charting.split('\n').find((l) => l.startsWith('4. **Create the tickets you can specify now**')) || '';
29
+ ok('charting: step 4 applies the rule', step4.includes('Cross-references in a bulk create') && /never a guessed/.test(step4));
30
+
31
+ const splitStep = teamwork.split('\n').find((l) => l.startsWith('2. **Create the child tickets**')) || '';
32
+ ok('split: the children step applies the rule', splitStep.includes('Cross-references in a bulk create') && /never a guessed/.test(splitStep));
33
+
34
+ ok('ticket verb: a cluster applies the rule', chart.includes('Cross-references in a bulk create'));
35
+
36
+ console.log(`bulk-cross-reference-docs: ${passed} assertions passed`);
@@ -145,4 +145,11 @@ for (const [file, key, agent] of [
145
145
  }
146
146
  }
147
147
 
148
+ // --- Parte E: #350, ogni agent del motore parte con contesto fresco, mai un fork ---------
149
+ {
150
+ const techniques = read('_shared/techniques.md');
151
+ ok('E: techniques.md states engine agents spawn with fresh context, never a fork',
152
+ /fresh context, never a fork/.test(techniques) && techniques.includes('fork_turns'));
153
+ }
154
+
148
155
  console.log(`engine-route-docs.test.js: ${passed} checks passed`);
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env node
2
+ // Docs test for #360: every codebase facet is stamped with the commit it is true
3
+ // at, the Resolve patches of build and bug restamp the facet they touch, and the
4
+ // audit flags facets whose files moved after their stamp.
5
+ // Run: node facet-stamp-docs.test.js
6
+ const assert = require('assert');
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+
10
+ const root = path.join(__dirname, '..');
11
+ const skills = path.join(root, 'plugins', 'trailhead', 'skills');
12
+ const docs = path.join(root, 'site', 'src', 'content', 'docs', 'docs');
13
+ const read = (...p) => fs.readFileSync(path.join(...p), 'utf8');
14
+
15
+ let passed = 0;
16
+ const ok = (name, cond) => { assert.ok(cond, name); passed++; };
17
+
18
+ const map = read(skills, '_shared', 'techniques', 'codebase-map.md');
19
+ const substrate = read(skills, '_shared', 'substrate.md');
20
+ const engines = read(skills, 'trailhead-work', 'references', 'ticket-engines.md');
21
+ const auditing = read(skills, 'trailhead-manage', 'references', 'auditing.md');
22
+ const manage = read(docs, 'commands', 'manage.md');
23
+ const concepts = read(docs, 'concepts.md');
24
+ const readmes = [['README.md', read(root, 'README.md')], ['README.it.md', read(root, 'README.it.md')]];
25
+
26
+ // The format and who writes it: codebase-map.md.
27
+ ok('map: facet stamp format', map.includes('`_as of <short sha>_`'));
28
+ ok('map: stamp comes from the scan root HEAD', map.includes('`git -C <scan root> rev-parse --short HEAD`'));
29
+ ok('map: a full re-run restamps every facet', /full re-run[^.]*restamps? (?:all|every)/i.test(map));
30
+ ok('map: the restamp rule is not a parenthetical splitting the sentence', !/restamps all of them\)/.test(map));
31
+ ok('map: a patch passes --stamp', map.includes('--section "<facet>" --stamp <short sha>'));
32
+ ok('map: an unstamped facet gains it on its next patch', /no stamp[^.]*gains it on its next patch/i.test(map));
33
+ ok('map: an untouched facet keeps its stamp', /untouched[^.]*keeps its (?:old )?stamp/i.test(map));
34
+
35
+ // The substrate row states the stamp.
36
+ const codebaseRow = substrate.split('\n').find((l) => l.startsWith('| Codebase |')) || '';
37
+ ok('substrate: Codebase row states the stamp', codebaseRow.includes('`_as of <short sha>_`'));
38
+
39
+ // Resolve of build and bug.
40
+ const resolves = engines.split('\n').filter((l) => l.startsWith('5. **Resolve**'));
41
+ ok('engines: build and bug Resolve both found', resolves.length === 2);
42
+ for (const [name, line] of [['build', resolves[0] || ''], ['bug', resolves[1] || '']]) {
43
+ ok(`engines ${name}: the facet patch passes --stamp`, line.includes('--stamp'));
44
+ ok(`engines ${name}: stamp is the trunk tip after integration`, /trunk tip/.test(line));
45
+ ok(`engines ${name}: a pr not merged yet stamps the fork point`, line.includes('git merge-base HEAD origin/<trunk>'));
46
+ ok(`engines ${name}: by-hand fallback updates the stamp line`, /by hand[^.]*stamp line/i.test(line));
47
+ }
48
+
49
+ // The audit.
50
+ const secStart = auditing.indexOf('## 7b. Codebase facets');
51
+ const secEnd = auditing.indexOf('## 8. Report');
52
+ const sec = secStart >= 0 ? auditing.slice(secStart, secEnd) : '';
53
+ ok('auditing: 7b sits between 7 and 8', secStart > auditing.indexOf('## 7. Compare the open tickets') && secEnd > secStart);
54
+ ok('auditing 7b: runs the script facets command', sec.includes('trailhead-audit-walk.js facets --repo'));
55
+ ok('auditing 7b: whole-repo runs only', /whole-repo/.test(sec) && /\[map\]/.test(sec));
56
+ ok('auditing 7b: no codebase issue skips it', /no `trailhead:codebase` issue/.test(sec));
57
+ ok('auditing 7b: singleton fail-closed lookup', /fail-closed/.test(sec));
58
+ ok('auditing 7b: body in a private mktemp directory removed on every path', sec.includes('mktemp -d') && /every path/.test(sec));
59
+ ok('auditing 7b: a script failure never blocks the audit or the marker', /never blocks the audit or the marker/.test(sec));
60
+ for (const status of ['current', 'stale', 'unstamped', 'unresolved', 'no-paths']) {
61
+ ok(`auditing 7b: status ${status}`, sec.includes(`\`${status}\``));
62
+ }
63
+ ok('auditing 7b: current is not proof either, a patch moves the stamp over the whole facet', /`current` is not proof/.test(sec) && /whole facet/.test(sec));
64
+ const report = auditing.slice(secEnd, auditing.indexOf('## 9. Proposals'));
65
+ ok('auditing 8: report lists stale facets with changed files, outside the sum', /stale facets?[^.]*changed files/i.test(report) && /outside the (?:class )?sum/.test(report));
66
+ const proposals = auditing.slice(auditing.indexOf('## 9. Proposals'), auditing.indexOf('## 10.'));
67
+ ok('auditing 9: stale facets get no proposal', /\*\*Stale facets\*\* → no proposal/.test(proposals));
68
+
69
+ // Reader-facing docs.
70
+ ok('site manage: audit mentions stale codebase facets', /codebase facet/i.test(manage) && /as of/.test(manage));
71
+ ok('site concepts: codebase bullet mentions the stamp', concepts.split('\n').some((l) => l.includes('`trailhead:codebase`') && /as of/.test(l)));
72
+
73
+ for (const [name, text] of readmes) {
74
+ const lines = text.split('\n');
75
+ ok(`${name}: codebase bullet mentions the stamp`, lines.some((l) => l.includes('`trailhead:codebase`') && l.includes('`_as of <short sha>_`')));
76
+ ok(`${name}: audit paragraph mentions stale codebase facets`, lines.some((l) => l.startsWith('`/trailhead:audit') && l.includes('`_as of <short sha>_`')));
77
+ }
78
+
79
+ console.log(`facet-stamp-docs.test.js: ${passed} checks passed`);