@marcomigozzi/trailhead 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.it.md +12 -6
- package/README.md +12 -6
- package/bin/lib/codex-projection.js +9 -6
- package/bin/lib/codex-projection.test.js +5 -1
- package/bin/trailhead.js +50 -5
- package/bin/trailhead.test.js +55 -0
- package/package.json +1 -1
- package/plugins/trailhead/.claude-plugin/plugin.json +1 -1
- package/plugins/trailhead/agents/trailhead-code-review.md +2 -0
- package/plugins/trailhead/agents/trailhead-codebase-map.md +2 -0
- package/plugins/trailhead/agents/trailhead-debug.md +2 -0
- package/plugins/trailhead/agents/trailhead-executor.md +2 -0
- package/plugins/trailhead/agents/trailhead-fix.md +2 -0
- package/plugins/trailhead/agents/trailhead-plan.md +2 -0
- package/plugins/trailhead/agents/trailhead-research.md +2 -0
- package/plugins/trailhead/agents/trailhead-verify.md +2 -0
- package/plugins/trailhead/commands/auto.md +6 -0
- package/plugins/trailhead/commands/quick.md +1 -1
- package/plugins/trailhead/hooks/hooks.json +13 -0
- package/plugins/trailhead/hooks/lib/shell-scan.js +247 -0
- package/plugins/trailhead/hooks/lib/shell-scan.test.js +49 -0
- package/plugins/trailhead/hooks/trailhead-commit-guard.js +13 -2
- package/plugins/trailhead/hooks/trailhead-commit-guard.test.js +101 -0
- package/plugins/trailhead/hooks/trailhead-search-guard.js +279 -0
- package/plugins/trailhead/hooks/trailhead-search-guard.test.js +116 -0
- package/plugins/trailhead/hooks/trailhead-secret-read-guard.js +291 -0
- package/plugins/trailhead/hooks/trailhead-secret-read-guard.test.js +95 -0
- package/plugins/trailhead/skills/_shared/choices.md +77 -0
- package/plugins/trailhead/skills/_shared/configuration-reference.md +4 -2
- package/plugins/trailhead/skills/_shared/session-handoff.md +4 -3
- package/plugins/trailhead/skills/_shared/substrate-commands.md +1 -1
- package/plugins/trailhead/skills/_shared/substrate.md +1 -1
- package/plugins/trailhead/skills/_shared/teamwork.md +2 -2
- package/plugins/trailhead/skills/_shared/techniques/acceptance-testing.md +1 -1
- package/plugins/trailhead/skills/_shared/techniques/codebase-map.md +3 -0
- package/plugins/trailhead/skills/_shared/techniques/grilling.md +1 -1
- package/plugins/trailhead/skills/_shared/techniques/plan-review.md +1 -1
- package/plugins/trailhead/skills/_shared/techniques/prototype.md +1 -1
- package/plugins/trailhead/skills/_shared/techniques/verify.md +20 -3
- package/plugins/trailhead/skills/_shared/techniques.md +1 -1
- package/plugins/trailhead/skills/trailhead/SKILL.md +4 -2
- package/plugins/trailhead/skills/trailhead-capture/SKILL.md +2 -0
- package/plugins/trailhead/skills/trailhead-chart/SKILL.md +2 -0
- package/plugins/trailhead/skills/trailhead-manage/SKILL.md +2 -0
- package/plugins/trailhead/skills/trailhead-view/SKILL.md +3 -1
- package/plugins/trailhead/skills/trailhead-work/SKILL.md +19 -7
- package/plugins/trailhead/skills/trailhead-work/references/auto.md +54 -0
- package/plugins/trailhead/skills/trailhead-work/references/ticket-engines.md +6 -6
package/README.it.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
🇬🇧 [English](README.md) · 🇮🇹 **Italiano**
|
|
8
8
|
|
|
9
|
-
[](https://trailhead.marcomigozzi.it) [](https://www.npmjs.com/package/@marcomigozzi/trailhead) [](LICENSE)  ](https://trailhead.marcomigozzi.it) [](https://github.com/ToRvaLDz/trailhead/actions/workflows/site-ci.yml) [](https://www.npmjs.com/package/@marcomigozzi/trailhead) [](LICENSE)   
|
|
10
10
|
|
|
11
11
|
</div>
|
|
12
12
|
|
|
@@ -70,7 +70,7 @@ npx @marcomigozzi/trailhead --dir=<path> # punta a una config dir specifica
|
|
|
70
70
|
```
|
|
71
71
|
Su **Claude Code** copia la skill (+ le sue `references/`), i comandi `/trailhead:*`, gli hook (in `hooks/`, registrati in `settings.json`) incluso un **update check** al SessionStart, e i template di label-guard + statusline, in modo idempotente.
|
|
72
72
|
|
|
73
|
-
Su **Codex CLI** (`--codex`) proietta una skill nativa Codex invocata come **`$trailhead`** (`$trailhead work`, `$trailhead new "idea"`, …), una skill sottile **`$trailhead-<verb>`** per ogni verbo per la discoverability, le
|
|
73
|
+
Su **Codex CLI** (`--codex`) proietta una skill nativa Codex invocata come **`$trailhead`** (`$trailhead work`, `$trailhead new "idea"`, …), una skill sottile **`$trailhead-<verb>`** per ogni verbo per la discoverability, le sei guardrail come **hook** nativi di Codex (`~/.codex/hooks.json`, abilitando `features.hooks`), e i pin di modello per-tecnica da **`models.codex.*`** in `~/.codex/agents/`. Serve Codex **0.145.0+** (l'installer fa un gate su `codex --version`). La modalità mockup `claude.ai/design` non è disponibile su Codex; lì l'opzione hosted è la modalità cross-host `design: stitch` (MCP Google Stitch), con il disco locale come ripiego.
|
|
74
74
|
|
|
75
75
|
Ri-esegui l'install per aggiornare, oppure esegui **`/trailhead:update`** (Claude) / **`$trailhead update`** (Codex) da dentro l'agente: rileva come trailhead è stato installato e installa la versione più recente dove è sicuro farlo (un `git pull` per un dev-symlink, una ri-esecuzione `npx` per npm, o `/plugin update` per il plugin). Quando esiste una versione più recente, la statusline mostra un flag `⬆ trailhead <version>` (Claude).
|
|
76
76
|
|
|
@@ -95,7 +95,7 @@ Una mappa ha come scope **un** solo sforzo, quindi la conoscenza che appartiene
|
|
|
95
95
|
|
|
96
96
|
- **`trailhead:codebase`**: la codebase map distillata (architettura, stack, convenzioni, rischi, test/build), scritta una volta all'adopt e aggiornata solo su drift importanti.
|
|
97
97
|
- **`trailhead:conventions`**: il **modo di lavorare** del progetto, leggibile da tutti: un piccolo header machine-read che il motore obbedisce (`git: main|pr`, `release: command|auto`, `isolation: none|worktree|clone`) sopra la prosa umana. `/trailhead:adopt` e `:new` lo chiedono all'inizio. `isolation: worktree` dà a ogni ticket in esecuzione il proprio `git worktree` + branch (le sessioni concorrenti su un clone non condividono mai un working tree); `isolation: clone` gli dà invece un clone dedicato, per le app legate a un path che un worktree non riesce a buildare.
|
|
98
|
-
- **`trailhead:dashboard`**: l'indice pinnato dell'intera superficie, un link a ogni mappa aperta (con le progress bar native di GitHub), la whiteboard, e conteggi live (inbox, frontier della whiteboard). Aggiornato quando una mappa viene tracciata o esaurita, quando un ticket della whiteboard nasce o si risolve (non ha una progress bar nativa, quindi la dashboard è l'unico posto in cui appare), e su richiesta via `/trailhead:dashboard`.
|
|
98
|
+
- **`trailhead:dashboard`**: l'indice pinnato dell'intera superficie, un link a ogni mappa aperta (con le progress bar native di GitHub), la whiteboard, e conteggi live (inbox, frontier della whiteboard). Aggiornato quando una mappa viene tracciata o esaurita, quando un ticket della whiteboard nasce o si risolve (non ha una progress bar nativa, quindi la dashboard è l'unico posto in cui appare; un ticket nato da `quick`, lavorato e risolto nella stessa sessione, si aggiorna al suo handoff o alla pausa, non alla creazione), e su richiesta via `/trailhead:dashboard`.
|
|
99
99
|
|
|
100
100
|
Questi tre riempiono i **3 slot di issue pinnate** di GitHub, così gli anchor di trailhead di un repo restano a un click di distanza; **le mappe stesse non vengono mai pinnate** (sono indicizzate dalla dashboard, dato che la mappa "attiva" è stato locale per-checkout, non un fatto globale del repo). La *config* del progetto (models, TDD, design…) è ancora una cosa separata, un semplice file `.trailhead/config.json` alla radice del repo, mai in una issue.
|
|
101
101
|
|
|
@@ -106,7 +106,7 @@ Questi tre riempiono i **3 slot di issue pinnate** di GitHub, così gli anchor d
|
|
|
106
106
|
**Il ciclo di vita.** Ogni progetto percorre lo stesso loop:
|
|
107
107
|
|
|
108
108
|
1. **Start**: `/trailhead:new "<idea>"` (greenfield) o `/trailhead:adopt` (codice esistente: mappa la codebase una volta, poi vai lean). Questo *traccia la mappa*: dai un nome alla destination, poi mappa la frontier in ampiezza (breadth-first) nei primi ticket e nella fog.
|
|
109
|
-
2. **Lavora la frontier, un ticket per sessione**: `/trailhead:work` prende il prossimo ticket prendibile (o quello che nomini) ed esegue il motore per il suo tipo: `research` raccoglie un fatto · `decision` dirama le opzioni poi fa grilling per scegliere · `prototype` crea un artefatto grezzo a cui reagire · `build` esegue discuss → plan → execute → verify · `bug` esegue repro → diagnose → fix → verify · `task` è plumbing manuale che sblocca una decision.
|
|
109
|
+
2. **Lavora la frontier, un ticket per sessione**: `/trailhead:work` prende il prossimo ticket prendibile (o quello che nomini) ed esegue il motore per il suo tipo: `research` raccoglie un fatto · `decision` dirama le opzioni poi fa grilling per scegliere · `prototype` crea un artefatto grezzo a cui reagire · `build` esegue discuss → plan → execute → verify · `bug` esegue repro → diagnose → fix → verify · `task` è plumbing manuale che sblocca una decision. `/trailhead:auto` incatena la frontier in autonomia, un ticket dopo l'altro, fermandosi solo al safety rail, alla fog, o a una decisione che serve l'umano.
|
|
110
110
|
3. **Chiudi e sblocca**: risolvi il ticket (commento + chiusura), fanne il gist in `Decisions so far`, e rimuovi `trailhead:blocked` da ogni dipendente il cui ultimo blocker si è appena chiuso, diplomandolo sulla frontier.
|
|
111
111
|
4. **Ripeti** finché la frontier è vuota e la fog si è diradata: la destination (un artefatto funzionante) è raggiunta.
|
|
112
112
|
|
|
@@ -181,6 +181,7 @@ Ogni verbo è anche un comando con namespace (`/trailhead:new`, `/trailhead:work
|
|
|
181
181
|
| `/trailhead:pause [note]` | mette un checkpoint sul ticket in gioco così chiunque può riprenderlo |
|
|
182
182
|
| `/trailhead:ticket <type> <title>` | apre un ticket al volo (diverge brevemente prima: un atto di micro-charting) |
|
|
183
183
|
| `/trailhead:split [ticket]` | splitta un ticket sovradimensionato in figli, supersede l'originale |
|
|
184
|
+
| `/trailhead:auto [map]` | esegue la frontier in autonomia, un ticket dopo l'altro, fino a uno stop condition (safety rail / fog / una decisione che serve l'umano) o all'esaurimento; sospende la regola un-ticket-per-sessione |
|
|
184
185
|
| `/trailhead:grill [topic]` | esegue una sessione di grilling autonoma su una decision/topic |
|
|
185
186
|
| `/trailhead:map` | mostra la mappa a bassa risoluzione (destination, decisions, frontier, fog) |
|
|
186
187
|
| `/trailhead:dashboard` | mostra la dashboard del repo: l'indice pinnato di ogni mappa aperta, la whiteboard, e i conteggi live |
|
|
@@ -235,6 +236,8 @@ Ogni tipo ha il proprio motore inline: nessuna skill esterna viene invocata.
|
|
|
235
236
|
|
|
236
237
|
Due regole pratiche: i build ticket **non fanno mai auto-grill**: su un'ambiguità bloccante la skill si ferma e chiede; e il brainstorming (divergenza) vive nel charting, nel micro-charting di `ticket`, e nella fase di opzioni di una `decision`, mai nel grilling stesso, che converge soltanto.
|
|
237
238
|
|
|
239
|
+
Quando trailhead pone una scelta forzata, una scelta advisory (non una decisione che plasma la mappa) può offrire anche "lascia scegliere all'agente (con motivazione, poi conferma)", e qualsiasi scelta può offrire "rimanda questa decisione", che la parcheggia come ticket `decision` bloccato o come `seed` finché il suo prerequisito non si risolve.
|
|
240
|
+
|
|
238
241
|
---
|
|
239
242
|
|
|
240
243
|
## 🏷️ Labels
|
|
@@ -336,13 +339,16 @@ Se l'MCP della modalità hosted non è connesso, trailhead **offre di installarl
|
|
|
336
339
|
|
|
337
340
|
## 🪝 Hooks
|
|
338
341
|
|
|
339
|
-
Oltre alle istruzioni della skill, trailhead include
|
|
342
|
+
Oltre alle istruzioni della skill, trailhead include sei hook propri (in `hooks/`, self-contained via `${CLAUDE_PLUGIN_ROOT}`) che *impongono* le parti della disciplina che non ci si dovrebbe fidare che il modello ricordi:
|
|
340
343
|
|
|
341
344
|
- **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.
|
|
342
345
|
- **Secret guard** (`PreToolUse` sulle scritture `gh` di issue/PR): trailhead pubblica molto sul tracker (corpi dei ticket, commenti del motore, issue codebase/conventions, risoluzioni). Questo scansiona ciò che una scrittura `gh issue`/`gh pr`/`gh api` sta per pubblicare (l'inline `--body`, un heredoc, o un `--body-file`) e lo **blocca in modo netto** se corrisponde a un pattern di credenziale (chiavi private, token GitHub/AWS/OpenAI/Slack/Google/Stripe, JWT, o un `password`/`secret`/`token=…` hardcoded). Il blocco è un **segnale di pulisci-e-riprova, non un vicolo cieco**: trailhead redige il valore segnalato sul posto (`<REDACTED>` o un riferimento a env-var) e ripubblica automaticamente, così il contenuto pulito atterra comunque: il segreto semplicemente non raggiunge mai il tracker. Resta un *blocco* fail-safe anziché una silenziosa auto-riscrittura di proposito: un blocco non fa mai leak, mentre una redazione che silenziosamente non è stata applicata sì. Non scansiona mai le letture, solo le scritture in uscita.
|
|
346
|
+
- **Install guard** (`PreToolUse` sulle installazioni di pacchetti): una barriera anti-slopsquatting. Blocca in modo netto un'installazione da registry pubblico (npm / PyPI / crates.io / Go / RubyGems) finché il pacchetto non è verificato (similarità typosquat a nomi popolari, età / prima apparizione, numero di download, metadati del repository presenti e coerenti); una volta verificato, ri-esegui il comando esatto con il prefisso `TRAILHEAD_VERIFIED_INSTALL=1` per procedere. Il motore Execute / Fix porta lo stesso block-and-ask.
|
|
347
|
+
- **Search guard** (`PreToolUse` su `Bash`): un backstop strutturale per la regola di igiene dei comandi di ricerca (mai `cd` seguito da una lettura/ricerca su percorso relativo). Blocca in modo netto un singolo comando Bash che sia insieme un cambio di directory (`cd`/`pushd`/`env -C`) e, in un'istruzione unita o successiva, una lettura/ricerca su percorso relativo (`grep`/`rg`/`ag`/`cat`/`head`/`tail`/`wc`/`nl`/`sed`/`awk`/`less`/`more`, o `git grep`/`log`/`show`/`diff` senza `-C`), così la forma che fa scattare una regola di deny su `Read()` sotto bypass permissions (ripiegando su un prompt di approvazione manuale invece di girare headless) non viene mai eseguita, anziché essere solo scoraggiata nella prosa dell'agente.
|
|
348
|
+
- **Secret-read guard** (`PreToolUse` su `Read|Bash`): blocca in modo netto la lettura di `.env`/`.env.*`/`.secrets`, sia tramite il tool Read sia tramite un comando Bash (un operando file, il valore di un'opzione, o un target di redirect `<`). È una decisione `deny` a livello di hook anziché una regola di permesso su `Read()`, quindi i segreti restano illeggibili senza attivare il prompt di approvazione `cd-compound-read` di Claude Code 2.1.259.
|
|
343
349
|
- **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.
|
|
344
350
|
|
|
345
|
-
Tutti e
|
|
351
|
+
Tutti e sei sono crash-safe (qualsiasi errore → allow) e attivi ogni volta che il plugin è installato. I guard di commit, secret, install, search e secret-read quindi si applicano in **ogni** repo, non solo nei progetti trailhead: intenzionale, dato che i conventional commit, niente `Co-Authored-By`, nessun segreto leakato, nessuna installazione non verificata, niente `cd`-poi-lettura-relativa, e niente `.env`/`.secrets` leggibili sono regole permanenti di trailhead. Per fare opt-out, disabilita gli hook del plugin nelle impostazioni di Claude Code.
|
|
346
352
|
|
|
347
353
|
---
|
|
348
354
|
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
🇬🇧 **English** · 🇮🇹 [Italiano](README.it.md)
|
|
8
8
|
|
|
9
|
-
[](https://trailhead.marcomigozzi.it) [](https://www.npmjs.com/package/@marcomigozzi/trailhead) [](LICENSE)  ](https://trailhead.marcomigozzi.it) [](https://github.com/ToRvaLDz/trailhead/actions/workflows/site-ci.yml) [](https://www.npmjs.com/package/@marcomigozzi/trailhead) [](LICENSE)   
|
|
10
10
|
|
|
11
11
|
</div>
|
|
12
12
|
|
|
@@ -70,7 +70,7 @@ npx @marcomigozzi/trailhead --dir=<path> # target a specific config dir
|
|
|
70
70
|
```
|
|
71
71
|
On **Claude Code** it copies the skill (+ its `references/`), the `/trailhead:*` commands, the hooks (into `hooks/`, registered in `settings.json`) including a SessionStart **update check**, and the label-guard + statusline templates, idempotently.
|
|
72
72
|
|
|
73
|
-
On **Codex CLI** (`--codex`) it projects a native Codex skill invoked as **`$trailhead`** (`$trailhead work`, `$trailhead new "idea"`, …), one thin **`$trailhead-<verb>`** skill per verb for discoverability, the
|
|
73
|
+
On **Codex CLI** (`--codex`) it projects a native Codex skill invoked as **`$trailhead`** (`$trailhead work`, `$trailhead new "idea"`, …), one thin **`$trailhead-<verb>`** skill per verb for discoverability, the six guardrails as native Codex **hooks** (`~/.codex/hooks.json`, enabling `features.hooks`), and per-technique subagent model pins from **`models.codex.*`** into `~/.codex/agents/`. Codex **0.145.0+** is required (the installer gates on `codex --version`). The `claude.ai/design` mockup mode isn't available on Codex; the cross-host `design: stitch` mode (Google Stitch MCP) is the hosted option there, with local disk as the fallback.
|
|
74
74
|
|
|
75
75
|
Re-run the install to update, or run **`/trailhead:update`** (Claude) / **`$trailhead update`** (Codex) from inside the agent: it detects how trailhead was installed and installs the newer version where that is safe (a `git pull` for a dev-symlink, an `npx` re-run for npm, or `/plugin update` for the plugin). When a newer version exists, the statusline shows a `⬆ trailhead <version>` flag (Claude).
|
|
76
76
|
|
|
@@ -95,7 +95,7 @@ A map is scoped to **one** effort, so knowledge that belongs to the *repo* (not
|
|
|
95
95
|
|
|
96
96
|
- **`trailhead:codebase`**: the distilled codebase map (architecture, stack, conventions, risks, test/build), written once at adopt and refreshed only on major drift.
|
|
97
97
|
- **`trailhead:conventions`**: the project's **way of working**, readable by everyone: a small machine-read header the engine obeys (`git: main|pr`, `release: command|auto`, `isolation: none|worktree|clone`) over human prose. `/trailhead:adopt` and `:new` ask for it up front. `isolation: worktree` gives each executing ticket its own `git worktree` + branch (concurrent sessions on one clone never share a working tree); `isolation: clone` gives it a dedicated clone instead, for path-bound apps a worktree can't build.
|
|
98
|
-
- **`trailhead:dashboard`**: the pinned index of the whole surface, a link to every open map (with GitHub's native progress bars), the whiteboard, and live counts (inbox, whiteboard frontier). Refreshed when a map is charted or exhausted, when a whiteboard ticket is born or resolved (it has no native progress bar, so the dashboard is the only place it shows), and on demand via `/trailhead:dashboard`.
|
|
98
|
+
- **`trailhead:dashboard`**: the pinned index of the whole surface, a link to every open map (with GitHub's native progress bars), the whiteboard, and live counts (inbox, whiteboard frontier). Refreshed when a map is charted or exhausted, when a whiteboard ticket is born or resolved (it has no native progress bar, so the dashboard is the only place it shows; a `quick`-born ticket, worked and resolved in one sitting, refreshes at its handoff or pause, not at creation), and on demand via `/trailhead:dashboard`.
|
|
99
99
|
|
|
100
100
|
These three fill GitHub's **3 pinned-issue slots**, so a repo's trailhead anchors stay one click away; **maps themselves are never pinned** (they're indexed by the dashboard instead, since the "active" map is per-checkout local state, not a repo-global fact). Project *config* (models, TDD, design…) is separate again, a plain `.trailhead/config.json` file at the repo root, never in an issue.
|
|
101
101
|
|
|
@@ -106,7 +106,7 @@ These three fill GitHub's **3 pinned-issue slots**, so a repo's trailhead anchor
|
|
|
106
106
|
**The lifecycle.** Every project walks the same loop:
|
|
107
107
|
|
|
108
108
|
1. **Start**: `/trailhead:new "<idea>"` (greenfield) or `/trailhead:adopt` (existing code: map the codebase once, then go lean). This *charts the map*: name the destination, then map the frontier breadth-first into the first tickets and the fog.
|
|
109
|
-
2. **Work the frontier, one ticket per session**: `/trailhead:work` takes the next takeable ticket (or one you name) and runs the engine for its type: `research` gathers a fact · `decision` diverges options then grills to choose · `prototype` makes a rough artifact to react to · `build` runs discuss → plan → execute → verify · `bug` runs repro → diagnose → fix → verify · `task` is manual plumbing that unblocks a decision.
|
|
109
|
+
2. **Work the frontier, one ticket per session**: `/trailhead:work` takes the next takeable ticket (or one you name) and runs the engine for its type: `research` gathers a fact · `decision` diverges options then grills to choose · `prototype` makes a rough artifact to react to · `build` runs discuss → plan → execute → verify · `bug` runs repro → diagnose → fix → verify · `task` is manual plumbing that unblocks a decision. `/trailhead:auto` chains the frontier unattended, one ticket after another, stopping only at the safety rail, fog, or a human-necessary decision.
|
|
110
110
|
3. **Close & unblock**: resolve the ticket (comment + close), gist it into `Decisions so far`, and drop `trailhead:blocked` from any dependent whose last blocker just closed, graduating it onto the frontier.
|
|
111
111
|
4. **Repeat** until the frontier is empty and the fog has cleared: the destination (a working artifact) is reached.
|
|
112
112
|
|
|
@@ -181,6 +181,7 @@ Every verb is also a namespaced command (`/trailhead:new`, `/trailhead:work`, `/
|
|
|
181
181
|
| `/trailhead:pause [note]` | checkpoint the ticket in play so anyone can resume it |
|
|
182
182
|
| `/trailhead:ticket <type> <title>` | open a ticket on the fly (diverges briefly first: a micro-charting act) |
|
|
183
183
|
| `/trailhead:split [ticket]` | split an oversized ticket into children, supersede the original |
|
|
184
|
+
| `/trailhead:auto [map]` | run the frontier autonomously, one ticket after another, until a stop condition (safety rail / fog / a human-necessary decision) or exhaustion; suspends one-ticket-per-session |
|
|
184
185
|
| `/trailhead:grill [topic]` | run a standalone grilling session on a decision/topic |
|
|
185
186
|
| `/trailhead:map` | show the low-res map (destination, decisions, frontier, fog) |
|
|
186
187
|
| `/trailhead:dashboard` | show the repo dashboard: the pinned index of every open map, the whiteboard, and live counts |
|
|
@@ -235,6 +236,8 @@ Each type has its own inline engine: no external skill is invoked.
|
|
|
235
236
|
|
|
236
237
|
Two rules of thumb: build tickets **never auto-grill**: on blocking ambiguity the skill stops and asks; and brainstorming (divergence) lives in charting, in `ticket`'s micro-charting, and in a `decision`'s option phase, never in the grilling itself, which only converges.
|
|
237
238
|
|
|
239
|
+
When trailhead puts a forced choice to you, an advisory one (not a decision that shapes the map) may also offer "let the agent pick (with rationale, then confirm)", and any choice may offer "defer this decision", which parks it as a blocked `decision` ticket or a `seed` until its prerequisite resolves.
|
|
240
|
+
|
|
238
241
|
---
|
|
239
242
|
|
|
240
243
|
## 🏷️ Labels
|
|
@@ -336,13 +339,16 @@ If a hosted mode's MCP isn't connected, trailhead **offers to install it once**
|
|
|
336
339
|
|
|
337
340
|
## 🪝 Hooks
|
|
338
341
|
|
|
339
|
-
Beyond the skill's instructions, trailhead ships
|
|
342
|
+
Beyond the skill's instructions, trailhead ships six of its own hooks (in `hooks/`, self-contained via `${CLAUDE_PLUGIN_ROOT}`) that *enforce* the parts of the discipline the model shouldn't be trusted to remember:
|
|
340
343
|
|
|
341
344
|
- **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.
|
|
342
345
|
- **Secret guard** (`PreToolUse` on `gh` issue/PR writes): trailhead posts a lot to the tracker (ticket bodies, engine comments, codebase/conventions issues, resolutions). This scans what a `gh issue`/`gh pr`/`gh api` write is about to post (the inline `--body`, a heredoc, or a `--body-file`) and **hard-blocks** it if it matches a credential pattern (private keys, GitHub/AWS/OpenAI/Slack/Google/Stripe tokens, JWTs, or a hardcoded `password`/`secret`/`token=…`). The block is a **clean-and-retry cue, not a dead end**: trailhead redacts the flagged value in place (`<REDACTED>` or an env-var reference) and re-posts automatically, so the cleaned content still lands: the secret just never reaches the tracker. It stays a fail-safe *block* rather than a silent auto-rewrite on purpose: a block never leaks, whereas a redaction that quietly failed to apply would. It never scans reads, only outbound writes.
|
|
346
|
+
- **Install guard** (`PreToolUse` on package installs): a slopsquatting barrier. It hard-blocks a named public-registry install (npm / PyPI / crates.io / Go / RubyGems) until the package is vetted (typosquat similarity to popular names, age / first-seen date, download counts, repository metadata present and matching); once vetted, re-run the exact command prefixed with `TRAILHEAD_VERIFIED_INSTALL=1` to proceed. The Execute / Fix engine carries the same block-and-ask.
|
|
347
|
+
- **Search guard** (`PreToolUse` on `Bash`): structural backstop for the search-command-hygiene rule (never `cd` then read/search a relative path). It hard-blocks a single Bash command that both changes directory (`cd`/`pushd`/`env -C`) and, in a joined or later statement, reads/searches a relative path (`grep`/`rg`/`ag`/`cat`/`head`/`tail`/`wc`/`nl`/`sed`/`awk`/`less`/`more`, or `git grep`/`log`/`show`/`diff` without a `-C`), so the shape that trips a `Read()` deny rule under bypass permissions (falling back to a manual approval prompt instead of running headless) never runs at all, rather than merely being discouraged in the agent's own prose.
|
|
348
|
+
- **Secret-read guard** (`PreToolUse` on `Read|Bash`): denies reads of `.env`/`.env.*`/`.secrets`, whether via the Read tool or a Bash command (a file operand, an option value, or a `<` redirect target). This is a hook `deny` decision rather than a `Read()` permission rule, so secrets stay unreadable without arming Claude Code 2.1.259's `cd-compound-read` approval prompt.
|
|
343
349
|
- **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.
|
|
344
350
|
|
|
345
|
-
All
|
|
351
|
+
All six are crash-safe (any error → allow) and active whenever the plugin is installed. The commit, secret, install, search, and secret-read guards therefore apply in **every** repo, not only trailhead projects: intentional, since conventional commits, no `Co-Authored-By`, no leaked secrets, no unvetted installs, no `cd`-then-relative-read, and no readable `.env`/`.secrets` are trailhead's standing rules. To opt out, disable the plugin's hooks in your Claude Code settings.
|
|
346
352
|
|
|
347
353
|
---
|
|
348
354
|
|
|
@@ -109,7 +109,7 @@ Codex has a subagent toolkit: the multi_agent tools (\`spawn_agent\`, \`send_inp
|
|
|
109
109
|
The engine's \`/clear\` is Codex's \`/new\` (start a fresh session). Command references in a handoff use the skill form (\`$trailhead work <n>\`).
|
|
110
110
|
|
|
111
111
|
## F. Lifecycle hooks (native Codex hooks)
|
|
112
|
-
Codex has a hook bus, so trailhead's guardrails run as **real Codex hooks**, not degraded prose. The installer registers them in \`~/.codex/hooks.json\`: commit-guard, secret-guard and
|
|
112
|
+
Codex has a hook bus, so trailhead's guardrails run as **real Codex hooks**, not degraded prose. The installer registers them in \`~/.codex/hooks.json\`: commit-guard, secret-guard, install-guard and search-guard as \`PreToolUse\` (matcher \`Bash\`) hooks that can veto the command before it runs, secret-read-guard as a \`PreToolUse\` (matcher \`Read|Bash\`) hook that denies reads of \`.env\`/\`.env.*\`/\`.secrets\`, the injection-scanner as a \`PostToolUse\` (matcher \`Bash\`) advisory, and check-update as a \`SessionStart\` hook. On Codex, file reads happen through shell commands rather than a native Read-tool event, so the secret-read guard's enforcement here runs via its **Bash** arm (protection is not lost); the \`Read\` half of its matcher is a Claude-Code-native path, registered on Codex too only so it fires without changes if a future Codex version adds an equivalent event. They use the same wire format they use on Claude Code (JSON on stdin, a \`decision\`/\`permissionDecision\` verdict on stdout), which Codex accepts. The host-independent git \`commit-msg\` hook is still installed at repo first-use as well (defence in depth), exactly as on Claude. Codex gates hooks behind \`features.hooks = true\` and reviews them for trust on first start: the installer enables the flag, and Codex will ask you to trust trailhead's hooks the next time it starts.
|
|
113
113
|
|
|
114
114
|
## G. Design / UI mockups -> local disk
|
|
115
115
|
The \`claude.ai/design\` mockup mode (DesignSync: the \`claude_design\` MCP + the \`/design-sync\` skill) does not exist on Codex, so it **collapses to local-disk mockups** here: whatever \`config.design\` says, the Prototype technique's \`disk\` path is the only one available. Produce a throwaway static HTML mockup next to the code, link it from the ticket, and never attempt DesignSync / claude.ai/design on Codex (there is no \`claude_design\` MCP or \`/design-sync\` skill to call). \`config.design.approval\` still applies unchanged: \`explicit\` waits for the user's go-ahead before any UI code, \`auto\` surfaces the mockup and proceeds.
|
|
@@ -354,18 +354,21 @@ function codexVerbSkillPlan(codexHome, verbs) {
|
|
|
354
354
|
}
|
|
355
355
|
|
|
356
356
|
// --- codexHookEntries -----------------------------------------------------
|
|
357
|
-
// The
|
|
357
|
+
// The six guardrail hooks trailhead registers on Codex, as {event, matcher,
|
|
358
358
|
// command} records. Commands run the copied guard scripts under hooksScriptsDir
|
|
359
|
-
// via node, matching Claude's `node "<path>"` form. PreToolUse guards (commit
|
|
360
|
-
// secret) can veto; the injection-scanner is a
|
|
361
|
-
// runs at SessionStart. Reused by the
|
|
362
|
-
// ~/.codex/hooks.json (same shape as
|
|
359
|
+
// via node, matching Claude's `node "<path>"` form. PreToolUse guards (commit,
|
|
360
|
+
// secret, install, search, secret-read) can veto; the injection-scanner is a
|
|
361
|
+
// PostToolUse advisory; check-update runs at SessionStart. Reused by the
|
|
362
|
+
// installer, which merges them into ~/.codex/hooks.json (same shape as
|
|
363
|
+
// Claude's settings.json hooks block).
|
|
363
364
|
function codexHookEntries(hooksScriptsDir) {
|
|
364
365
|
const cmd = (name) => `node "${path.join(hooksScriptsDir, name)}"`;
|
|
365
366
|
return [
|
|
366
367
|
{ event: 'PreToolUse', matcher: 'Bash', command: cmd('trailhead-commit-guard.js') },
|
|
367
368
|
{ event: 'PreToolUse', matcher: 'Bash', command: cmd('trailhead-secret-guard.js') },
|
|
368
369
|
{ event: 'PreToolUse', matcher: 'Bash', command: cmd('trailhead-install-guard.js') },
|
|
370
|
+
{ event: 'PreToolUse', matcher: 'Bash', command: cmd('trailhead-search-guard.js') },
|
|
371
|
+
{ event: 'PreToolUse', matcher: 'Read|Bash', command: cmd('trailhead-secret-read-guard.js') },
|
|
369
372
|
{ event: 'PostToolUse', matcher: 'Bash', command: cmd('trailhead-issue-injection-scanner.js') },
|
|
370
373
|
{ event: 'SessionStart', matcher: '', command: cmd('trailhead-check-update.js') },
|
|
371
374
|
];
|
|
@@ -143,13 +143,17 @@ ok('codexVerbSkillPlan: null verbs yields nothing', codexVerbSkillPlan('/c', nul
|
|
|
143
143
|
|
|
144
144
|
// --- codexHookEntries ---
|
|
145
145
|
const entries = codexHookEntries('/h');
|
|
146
|
-
ok('codexHookEntries returns
|
|
146
|
+
ok('codexHookEntries returns 7 entries', Array.isArray(entries) && entries.length === 7);
|
|
147
147
|
ok('codexHookEntries: commit-guard is PreToolUse/Bash', entries.some((e) =>
|
|
148
148
|
e.event === 'PreToolUse' && e.matcher === 'Bash' && e.command.includes('trailhead-commit-guard.js') && e.command.includes('/h')));
|
|
149
149
|
ok('codexHookEntries: secret-guard is PreToolUse/Bash', entries.some((e) =>
|
|
150
150
|
e.event === 'PreToolUse' && e.matcher === 'Bash' && e.command.includes('trailhead-secret-guard.js')));
|
|
151
151
|
ok('codexHookEntries: install-guard is PreToolUse/Bash', entries.some((e) =>
|
|
152
152
|
e.event === 'PreToolUse' && e.matcher === 'Bash' && e.command.includes('trailhead-install-guard.js')));
|
|
153
|
+
ok('codexHookEntries: search-guard is PreToolUse/Bash', entries.some((e) =>
|
|
154
|
+
e.event === 'PreToolUse' && e.matcher === 'Bash' && e.command.includes('trailhead-search-guard.js')));
|
|
155
|
+
ok('codexHookEntries: secret-read-guard is PreToolUse/Read|Bash', entries.some((e) =>
|
|
156
|
+
e.event === 'PreToolUse' && e.matcher === 'Read|Bash' && e.command.includes('trailhead-secret-read-guard.js')));
|
|
153
157
|
ok('codexHookEntries: injection-scanner is PostToolUse/Bash', entries.some((e) =>
|
|
154
158
|
e.event === 'PostToolUse' && e.matcher === 'Bash' && e.command.includes('trailhead-issue-injection-scanner.js')));
|
|
155
159
|
ok('codexHookEntries: check-update is SessionStart', entries.some((e) =>
|
package/bin/trailhead.js
CHANGED
|
@@ -64,6 +64,43 @@ const useSymlink = has('--symlink');
|
|
|
64
64
|
const uninstall = has('--uninstall');
|
|
65
65
|
const dirArg = (args.find((a) => a.startsWith('--dir=')) || '').split('=')[1];
|
|
66
66
|
|
|
67
|
+
// --- usage / arg guard --------------------------------------------------------
|
|
68
|
+
// Single source for the flag list (mirrors the header comment above) so
|
|
69
|
+
// --help/-h and the unknown-flag error both print the same text.
|
|
70
|
+
const USAGE = `Usage:
|
|
71
|
+
npx @marcomigozzi/trailhead install (auto-detects the CLI on your PATH; asks if it can't tell)
|
|
72
|
+
npx @marcomigozzi/trailhead --claude force install for Claude Code (~/.claude or $CLAUDE_CONFIG_DIR)
|
|
73
|
+
npx @marcomigozzi/trailhead --codex force install for Codex ($CODEX_HOME or ~/.codex)
|
|
74
|
+
npx @marcomigozzi/trailhead --symlink dev install (symlink to the package, live edits; Claude only)
|
|
75
|
+
npx @marcomigozzi/trailhead --uninstall
|
|
76
|
+
npx @marcomigozzi/trailhead --dir=/path/to/configdir
|
|
77
|
+
npx @marcomigozzi/trailhead --help, -h print this usage`;
|
|
78
|
+
|
|
79
|
+
function printUsage(stream) {
|
|
80
|
+
stream.write(USAGE + '\n');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Help wins: if a help flag is present anywhere, print usage and exit 0 even
|
|
84
|
+
// if an unknown flag is also present.
|
|
85
|
+
if (has('--help') || has('-h')) {
|
|
86
|
+
printUsage(process.stdout);
|
|
87
|
+
process.exit(0);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Unknown-flag guard: any `-`-prefixed arg not on this recognised list (or a
|
|
91
|
+
// recognised value-prefix) is rejected up front, instead of silently falling
|
|
92
|
+
// through into a default install (#136). `--host=` is legacy and tolerated
|
|
93
|
+
// (ignored; auto-detect wins instead).
|
|
94
|
+
const RECOGNISED_FLAGS = new Set(['--symlink', '--uninstall', '--codex', '--claude', '--help', '-h']);
|
|
95
|
+
const RECOGNISED_PREFIXES = ['--dir=', '--host='];
|
|
96
|
+
const unknownArg = args.find((a) =>
|
|
97
|
+
a.startsWith('-') && !RECOGNISED_FLAGS.has(a) && !RECOGNISED_PREFIXES.some((p) => a.startsWith(p)));
|
|
98
|
+
if (unknownArg) {
|
|
99
|
+
process.stderr.write(`unknown option: ${unknownArg}\n`);
|
|
100
|
+
printUsage(process.stderr);
|
|
101
|
+
process.exit(2);
|
|
102
|
+
}
|
|
103
|
+
|
|
67
104
|
const rmrf = (p) => fs.rmSync(p, { recursive: true, force: true });
|
|
68
105
|
const ensure = (p) => fs.mkdirSync(p, { recursive: true });
|
|
69
106
|
const readJSON = (p) => { try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch { return {}; } };
|
|
@@ -176,12 +213,14 @@ function claudePaths(configDir) {
|
|
|
176
213
|
settings: path.join(configDir, 'settings.json'),
|
|
177
214
|
};
|
|
178
215
|
}
|
|
179
|
-
const HOOK_FILES = ['trailhead-commit-guard.js', 'trailhead-issue-injection-scanner.js', 'trailhead-secret-guard.js', 'trailhead-install-guard.js', 'trailhead-check-update.js'];
|
|
216
|
+
const HOOK_FILES = ['trailhead-commit-guard.js', 'trailhead-issue-injection-scanner.js', 'trailhead-secret-guard.js', 'trailhead-install-guard.js', 'trailhead-search-guard.js', 'trailhead-secret-read-guard.js', 'trailhead-check-update.js'];
|
|
180
217
|
// Runtime libs the hook scripts require (trailhead-commit-guard.js does
|
|
181
|
-
// require('./lib/commit-message-check.js')
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
|
|
218
|
+
// require('./lib/commit-message-check.js'); trailhead-search-guard.js and
|
|
219
|
+
// trailhead-secret-read-guard.js both do require('./lib/shell-scan.js'), #135
|
|
220
|
+
// follow-up). Copied by name, never a recursive sweep: the Claude hooks/lib
|
|
221
|
+
// dir is shared with other plugins, so we must not clobber a co-tenant's lib
|
|
222
|
+
// nor ship our own *.test.js files.
|
|
223
|
+
const HOOK_LIB_FILES = ['commit-message-check.js', 'shell-scan.js'];
|
|
185
224
|
|
|
186
225
|
// Copy the hook scripts and their runtime libs into a destination hooks dir
|
|
187
226
|
// (Claude's ~/.claude/hooks or Codex's skills/trailhead/hooks). Shared by both
|
|
@@ -288,6 +327,8 @@ function uninstallClaude(configDir) {
|
|
|
288
327
|
stripHook(s, 'PreToolUse', 'trailhead-commit-guard.js');
|
|
289
328
|
stripHook(s, 'PreToolUse', 'trailhead-secret-guard.js');
|
|
290
329
|
stripHook(s, 'PreToolUse', 'trailhead-install-guard.js');
|
|
330
|
+
stripHook(s, 'PreToolUse', 'trailhead-search-guard.js');
|
|
331
|
+
stripHook(s, 'PreToolUse', 'trailhead-secret-read-guard.js');
|
|
291
332
|
stripHook(s, 'PostToolUse', 'trailhead-issue-injection-scanner.js');
|
|
292
333
|
stripHook(s, 'SessionStart', 'trailhead-check-update.js');
|
|
293
334
|
writeJSON(P.settings, s);
|
|
@@ -333,6 +374,8 @@ function installClaude(configDir, { useSymlink }) {
|
|
|
333
374
|
addHook(s, 'PreToolUse', 'Bash', hookCmd('trailhead-commit-guard.js')),
|
|
334
375
|
addHook(s, 'PreToolUse', 'Bash', hookCmd('trailhead-secret-guard.js')),
|
|
335
376
|
addHook(s, 'PreToolUse', 'Bash', hookCmd('trailhead-install-guard.js')),
|
|
377
|
+
addHook(s, 'PreToolUse', 'Bash', hookCmd('trailhead-search-guard.js')),
|
|
378
|
+
addHook(s, 'PreToolUse', 'Read|Bash', hookCmd('trailhead-secret-read-guard.js')),
|
|
336
379
|
addHook(s, 'PostToolUse', 'Bash', hookCmd('trailhead-issue-injection-scanner.js')),
|
|
337
380
|
addHook(s, 'SessionStart', '', hookCmd('trailhead-check-update.js')),
|
|
338
381
|
].some(Boolean);
|
|
@@ -636,6 +679,8 @@ function uninstallCodex(configDir) {
|
|
|
636
679
|
stripHook(h, 'PreToolUse', 'trailhead-commit-guard.js');
|
|
637
680
|
stripHook(h, 'PreToolUse', 'trailhead-secret-guard.js');
|
|
638
681
|
stripHook(h, 'PreToolUse', 'trailhead-install-guard.js');
|
|
682
|
+
stripHook(h, 'PreToolUse', 'trailhead-search-guard.js');
|
|
683
|
+
stripHook(h, 'PreToolUse', 'trailhead-secret-read-guard.js');
|
|
639
684
|
stripHook(h, 'PostToolUse', 'trailhead-issue-injection-scanner.js');
|
|
640
685
|
stripHook(h, 'SessionStart', 'trailhead-check-update.js');
|
|
641
686
|
writeJSON(L.hooksJson, h);
|
package/bin/trailhead.test.js
CHANGED
|
@@ -96,6 +96,11 @@ ok('codex: per-verb skills/trailhead-bug/SKILL.md exists', fs.existsSync(path.jo
|
|
|
96
96
|
ok('codex: trailhead-bug SKILL.md frontmatter is at byte 0', fs.readFileSync(path.join(codexDir, 'skills', 'trailhead-bug', 'SKILL.md'), 'utf8').startsWith('---\nname: trailhead-bug\n'));
|
|
97
97
|
ok('codex: trailhead-bug skill delegates to $trailhead bug', fs.readFileSync(path.join(codexDir, 'skills', 'trailhead-bug', 'SKILL.md'), 'utf8').includes('$trailhead bug'));
|
|
98
98
|
ok('codex: per-verb skill is explicit-only', fs.readFileSync(path.join(codexDir, 'skills', 'trailhead-bug', 'agents', 'openai.yaml'), 'utf8').includes('allow_implicit_invocation: false'));
|
|
99
|
+
// auto is a plain verb skill too: no trailhead-auto CLUSTER dir exists, so it
|
|
100
|
+
// projects a thin per-verb skill exactly like bug/quick/pause.
|
|
101
|
+
ok('codex: per-verb skills/trailhead-auto/SKILL.md exists', fs.existsSync(path.join(codexDir, 'skills', 'trailhead-auto', 'SKILL.md')));
|
|
102
|
+
ok('codex: trailhead-auto SKILL.md frontmatter is at byte 0', fs.readFileSync(path.join(codexDir, 'skills', 'trailhead-auto', 'SKILL.md'), 'utf8').startsWith('---\nname: trailhead-auto\n'));
|
|
103
|
+
ok('codex: trailhead-auto skill delegates to $trailhead auto', fs.readFileSync(path.join(codexDir, 'skills', 'trailhead-auto', 'SKILL.md'), 'utf8').includes('$trailhead auto'));
|
|
99
104
|
// The trailhead-work dir is the WORK CLUSTER skill (not a thin verb delegator).
|
|
100
105
|
ok('codex: trailhead-work is the cluster skill (frontmatter name at byte 0)', fs.readFileSync(path.join(codexDir, 'skills', 'trailhead-work', 'SKILL.md'), 'utf8').startsWith('---\nname: trailhead-work\n'));
|
|
101
106
|
ok('codex: trailhead-work cluster carries the adapter header', fs.readFileSync(path.join(codexDir, 'skills', 'trailhead-work', 'SKILL.md'), 'utf8').includes('<codex_skill_adapter>'));
|
|
@@ -245,6 +250,7 @@ for (const cl of ['trailhead-chart', 'trailhead-work', 'trailhead-view', 'trailh
|
|
|
245
250
|
}
|
|
246
251
|
ok('claude: cluster ../_shared/ reference resolves', fs.existsSync(path.join(claudeDir, 'skills', 'trailhead-work', '..', '_shared', 'substrate.md')));
|
|
247
252
|
ok('claude: commands/trailhead/work.md exists', fs.existsSync(path.join(claudeDir, 'commands', 'trailhead', 'work.md')));
|
|
253
|
+
ok('claude: commands/trailhead/auto.md exists', fs.existsSync(path.join(claudeDir, 'commands', 'trailhead', 'auto.md')));
|
|
248
254
|
ok('claude: hooks/trailhead-commit-guard.js exists', fs.existsSync(path.join(claudeDir, 'hooks', 'trailhead-commit-guard.js')));
|
|
249
255
|
ok('claude: hooks/lib/commit-message-check.js exists (commit-guard require target)',
|
|
250
256
|
fs.existsSync(path.join(claudeDir, 'hooks', 'lib', 'commit-message-check.js')));
|
|
@@ -456,6 +462,55 @@ ok('codex: config.toml enables multi_agent_v2 when a models.codex.* pin is proje
|
|
|
456
462
|
runInstaller([`--codex`, `--dir=${pinCodexHomeDir}`, '--uninstall']);
|
|
457
463
|
ok('codex uninstall: trailhead-execute.toml is gone', !fs.existsSync(pinnedTomlPath));
|
|
458
464
|
|
|
465
|
+
// --- #136: --help / -h print usage and install nothing -----------------------
|
|
466
|
+
// Asserts no install artifacts landed under a fresh --dir=, by checking for the
|
|
467
|
+
// two markers common to both host layouts (settings.json is Claude-only, but
|
|
468
|
+
// skills/ is written by both, so its absence proves nothing at all ran).
|
|
469
|
+
function nothingInstalledUnder(dir) {
|
|
470
|
+
return !fs.existsSync(path.join(dir, 'settings.json')) &&
|
|
471
|
+
!fs.existsSync(path.join(dir, 'skills')) &&
|
|
472
|
+
!fs.existsSync(path.join(dir, 'commands')) &&
|
|
473
|
+
!fs.existsSync(path.join(dir, 'agents'));
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const helpDir = mktmp();
|
|
477
|
+
const helpOut = String(runInstaller([`--help`, `--dir=${helpDir}`]));
|
|
478
|
+
ok('--help: prints usage (mentions --uninstall)', helpOut.includes('--uninstall'));
|
|
479
|
+
ok('--help: prints usage (mentions --codex)', helpOut.includes('--codex'));
|
|
480
|
+
ok('--help: installs nothing', nothingInstalledUnder(helpDir));
|
|
481
|
+
|
|
482
|
+
const hDir = mktmp();
|
|
483
|
+
const hOut = String(runInstaller([`-h`, `--dir=${hDir}`]));
|
|
484
|
+
ok('-h: prints usage (mentions --uninstall)', hOut.includes('--uninstall'));
|
|
485
|
+
ok('-h: installs nothing', nothingInstalledUnder(hDir));
|
|
486
|
+
|
|
487
|
+
// --- #136: unknown flag is rejected, not silently ignored ---------------------
|
|
488
|
+
const unknownDir = mktmp();
|
|
489
|
+
let unknownRejected = false;
|
|
490
|
+
let unknownOut = '';
|
|
491
|
+
try {
|
|
492
|
+
runInstaller([`--frobnicate`, `--dir=${unknownDir}`]);
|
|
493
|
+
} catch (e) {
|
|
494
|
+
unknownRejected = true;
|
|
495
|
+
unknownOut = String(e.stdout || '') + String(e.stderr || '');
|
|
496
|
+
}
|
|
497
|
+
ok('unknown flag: rejected (non-zero exit)', unknownRejected);
|
|
498
|
+
ok('unknown flag: error message names the bad flag', unknownOut.includes('unknown option') && unknownOut.includes('--frobnicate'));
|
|
499
|
+
ok('unknown flag: error output also includes usage (mentions --codex)', unknownOut.includes('--codex'));
|
|
500
|
+
ok('unknown flag: installs nothing', nothingInstalledUnder(unknownDir));
|
|
501
|
+
|
|
502
|
+
// Unknown flag after a valid one: proves the whole arg list is scanned, not
|
|
503
|
+
// just a single expected position.
|
|
504
|
+
const unknownAfterValidDir = mktmp();
|
|
505
|
+
let unknownAfterValidRejected = false;
|
|
506
|
+
try {
|
|
507
|
+
runInstaller([`--claude`, `--bogus`, `--dir=${unknownAfterValidDir}`]);
|
|
508
|
+
} catch (e) {
|
|
509
|
+
unknownAfterValidRejected = true;
|
|
510
|
+
}
|
|
511
|
+
ok('unknown flag after a valid flag: still rejected', unknownAfterValidRejected);
|
|
512
|
+
ok('unknown flag after a valid flag: installs nothing', nothingInstalledUnder(unknownAfterValidDir));
|
|
513
|
+
|
|
459
514
|
// --- cleanup -------------------------------------------------------------------
|
|
460
515
|
for (const d of tmpDirs) {
|
|
461
516
|
fs.rmSync(d, { recursive: true, force: true });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marcomigozzi/trailhead",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Start and drive large projects as a map of tickets on GitHub Issues. Map-based onboarding + a discuss→plan→execute→verify engine, all on the Issues. Installs the trailhead skill, commands, and hooks into your agent's config dir.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"trailhead": "bin/trailhead.js"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trailhead",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Start and drive large projects as a map of tickets on GitHub Issues, resolving one at a time. Map-based onboarding + a discuss→plan→execute→verify engine with atomic commits, all on the Issues. Ships as a dispatcher plus cohesion-clustered skills over a shared core; depends on nothing beyond an authenticated gh CLI.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Marco Migozzi",
|
|
@@ -8,3 +8,5 @@ tools: Read, Grep, Glob, Bash, WebFetch, WebSearch, TodoWrite
|
|
|
8
8
|
trailhead's code-review subagent. Review the ticket's diff adversarially on the Code review technique's axes, verifying each finding before reporting. Do not defer to a convention or a settled-decision memory.
|
|
9
9
|
|
|
10
10
|
The full protocol is injected at spawn from the trailhead skill's single source (`_shared/techniques/code-review.md`); follow that, not any embedded copy. Read-only by policy: it reviews and reports into trailhead's `VERIFY` comment, it must never Edit/Write or commit. Model-agnostic by design: the engine passes the `config.models.review` model as the spawn-time override, so config stays authoritative.
|
|
11
|
+
|
|
12
|
+
**Search-command hygiene.** Prefer the Grep and Read tools over shelling out; when a Bash `grep`/read is genuinely needed, pass the path as an explicit argument (`grep -niE "<patterns>" <abs>/<file>`, `wc -l <abs>/<file>`) and run git as `git -C <abs> ...`. **Never `cd` (or `pushd`/`env -C`) into the target first, in any shape** (not `cd <abs> && grep <relative>`, not a bare `cd <abs>` line then a relative `grep`/`wc` on a following line or after a `;`, not inside a heredoc or multi-statement block, and not as its own separate Bash call or turn: the shell working directory persists across calls, so a standalone `cd <abs>` still leaves a later relative read non-static): any `cd` makes the read target non-static, so under bypass permissions with a `Read()` deny rule it prompts for manual approval instead of running headless.
|
|
@@ -8,3 +8,5 @@ tools: Read, Grep, Glob, Bash, WebFetch, WebSearch, TodoWrite
|
|
|
8
8
|
trailhead's codebase-map reader subagent. Map ONE non-overlapping facet of the repo for the distilled `trailhead:codebase` issue; do not read another reader's territory.
|
|
9
9
|
|
|
10
10
|
The full protocol is injected at spawn from the trailhead skill's single source (`_shared/techniques/codebase-map.md`); follow that, not any embedded copy. Read-only by policy: read-only by construction. Model-agnostic by design: the engine passes the session model at spawn (no dedicated model key), so config stays authoritative.
|
|
11
|
+
|
|
12
|
+
**Search-command hygiene.** Prefer the Grep and Read tools over shelling out; when a Bash `grep`/read is genuinely needed, pass the path as an explicit argument (`grep -niE "<patterns>" <abs>/<file>`, `wc -l <abs>/<file>`) and run git as `git -C <abs> ...`. **Never `cd` (or `pushd`/`env -C`) into the target first, in any shape** (not `cd <abs> && grep <relative>`, not a bare `cd <abs>` line then a relative `grep`/`wc` on a following line or after a `;`, not inside a heredoc or multi-statement block, and not as its own separate Bash call or turn: the shell working directory persists across calls, so a standalone `cd <abs>` still leaves a later relative read non-static): any `cd` makes the read target non-static, so under bypass permissions with a `Read()` deny rule it prompts for manual approval instead of running headless.
|
|
@@ -8,3 +8,5 @@ tools: Read, Write, Edit, Grep, Glob, Bash, WebFetch, WebSearch, TodoWrite
|
|
|
8
8
|
trailhead's debug subagent. Find the root cause of the ticket's defect by the scientific method: reproduce, localise, falsifiable hypotheses, confirm cause, verify.
|
|
9
9
|
|
|
10
10
|
The full protocol is injected at spawn from the trailhead skill's single source (`_shared/techniques/debug.md`); follow that, not any embedded copy. Full tools by policy: it adds instrumentation and regression tests, so it needs Write. Model-agnostic by design: the engine passes the `config.models.debug` model as the spawn-time override, so config stays authoritative.
|
|
11
|
+
|
|
12
|
+
**Search-command hygiene.** Prefer the Grep and Read tools over shelling out; when a Bash `grep`/read is genuinely needed, pass the path as an explicit argument (`grep -niE "<patterns>" <abs>/<file>`, `wc -l <abs>/<file>`) and run git as `git -C <abs> ...`. **Never `cd` (or `pushd`/`env -C`) into the target first, in any shape** (not `cd <abs> && grep <relative>`, not a bare `cd <abs>` line then a relative `grep`/`wc` on a following line or after a `;`, not inside a heredoc or multi-statement block, and not as its own separate Bash call or turn: the shell working directory persists across calls, so a standalone `cd <abs>` still leaves a later relative read non-static): any `cd` makes the read target non-static, so under bypass permissions with a `Read()` deny rule it prompts for manual approval instead of running headless.
|
|
@@ -8,3 +8,5 @@ tools: Read, Write, Edit, Grep, Glob, Bash, WebFetch, WebSearch, TodoWrite
|
|
|
8
8
|
trailhead's execute subagent. Implement the approved PLAN for a build ticket with atomic conventional commits, each carrying a `Refs: #<n>` trailer for the ticket, following the repo's `trailhead:conventions`.
|
|
9
9
|
|
|
10
10
|
The full protocol is injected at spawn from the trailhead skill's single source (the Execute step in `trailhead-work/references/ticket-engines.md`); follow that, not any embedded copy. Full tools by policy: it can Edit/Write/commit. Model-agnostic by design: the engine passes the `config.models.execute` model as the spawn-time override, so config stays authoritative.
|
|
11
|
+
|
|
12
|
+
**Search-command hygiene.** Prefer the Grep and Read tools over shelling out; when a Bash `grep`/read is genuinely needed, pass the path as an explicit argument (`grep -niE "<patterns>" <abs>/<file>`, `wc -l <abs>/<file>`) and run git as `git -C <abs> ...`. **Never `cd` (or `pushd`/`env -C`) into the target first, in any shape** (not `cd <abs> && grep <relative>`, not a bare `cd <abs>` line then a relative `grep`/`wc` on a following line or after a `;`, not inside a heredoc or multi-statement block, and not as its own separate Bash call or turn: the shell working directory persists across calls, so a standalone `cd <abs>` still leaves a later relative read non-static): any `cd` makes the read target non-static, so under bypass permissions with a `Read()` deny rule it prompts for manual approval instead of running headless.
|
|
@@ -8,3 +8,5 @@ tools: Read, Write, Edit, Grep, Glob, Bash, WebFetch, WebSearch, TodoWrite
|
|
|
8
8
|
trailhead's fix subagent. Implement the correction for a bug ticket with atomic conventional commits, each carrying a `Refs: #<n>` trailer for the ticket, following the repo's `trailhead:conventions`; where sensible, a test that fails before and passes after.
|
|
9
9
|
|
|
10
10
|
The full protocol is injected at spawn from the trailhead skill's single source (the Fix step in `trailhead-work/references/ticket-engines.md`); follow that, not any embedded copy. Full tools by policy: it can Edit/Write/commit. Model-agnostic by design: the engine passes the `config.models.execute` model as the spawn-time override (fix tracks the execute key), so config stays authoritative.
|
|
11
|
+
|
|
12
|
+
**Search-command hygiene.** Prefer the Grep and Read tools over shelling out; when a Bash `grep`/read is genuinely needed, pass the path as an explicit argument (`grep -niE "<patterns>" <abs>/<file>`, `wc -l <abs>/<file>`) and run git as `git -C <abs> ...`. **Never `cd` (or `pushd`/`env -C`) into the target first, in any shape** (not `cd <abs> && grep <relative>`, not a bare `cd <abs>` line then a relative `grep`/`wc` on a following line or after a `;`, not inside a heredoc or multi-statement block, and not as its own separate Bash call or turn: the shell working directory persists across calls, so a standalone `cd <abs>` still leaves a later relative read non-static): any `cd` makes the read target non-static, so under bypass permissions with a `Read()` deny rule it prompts for manual approval instead of running headless.
|
|
@@ -8,3 +8,5 @@ tools: Read, Grep, Glob, Bash, WebFetch, WebSearch, TodoWrite
|
|
|
8
8
|
trailhead's planning subagent. Produce the PLAN for a build ticket: the steps, the seams, the files touched, and the verification criteria, applying TDD per the ticket's config.
|
|
9
9
|
|
|
10
10
|
The full protocol is injected at spawn from the trailhead skill's single source (the Plan step in `trailhead-work/references/ticket-engines.md`); follow that, not any embedded copy. Read-only by policy: plan, do not implement or commit. Model-agnostic by design: the engine passes the `config.models.plan` model as the spawn-time override, so config stays authoritative.
|
|
11
|
+
|
|
12
|
+
**Search-command hygiene.** Prefer the Grep and Read tools over shelling out; when a Bash `grep`/read is genuinely needed, pass the path as an explicit argument (`grep -niE "<patterns>" <abs>/<file>`, `wc -l <abs>/<file>`) and run git as `git -C <abs> ...`. **Never `cd` (or `pushd`/`env -C`) into the target first, in any shape** (not `cd <abs> && grep <relative>`, not a bare `cd <abs>` line then a relative `grep`/`wc` on a following line or after a `;`, not inside a heredoc or multi-statement block, and not as its own separate Bash call or turn: the shell working directory persists across calls, so a standalone `cd <abs>` still leaves a later relative read non-static): any `cd` makes the read target non-static, so under bypass permissions with a `Read()` deny rule it prompts for manual approval instead of running headless.
|
|
@@ -8,3 +8,5 @@ tools: Read, Write, Edit, Grep, Glob, Bash, WebFetch, WebSearch, TodoWrite
|
|
|
8
8
|
trailhead's research subagent. Gather the decision-ready fact a ticket waits on from primary sources, recording findings with citations to a throwaway `research/<name>` branch; change no product code beyond the research artifact.
|
|
9
9
|
|
|
10
10
|
The full protocol is injected at spawn from the trailhead skill's single source (`_shared/techniques/research.md`); follow that, not any embedded copy. Full tools by policy: it needs Write for the research artifact. Model-agnostic by design: the engine passes the `config.models.research` model as the spawn-time override, so config stays authoritative.
|
|
11
|
+
|
|
12
|
+
**Search-command hygiene.** Prefer the Grep and Read tools over shelling out; when a Bash `grep`/read is genuinely needed, pass the path as an explicit argument (`grep -niE "<patterns>" <abs>/<file>`, `wc -l <abs>/<file>`) and run git as `git -C <abs> ...`. **Never `cd` (or `pushd`/`env -C`) into the target first, in any shape** (not `cd <abs> && grep <relative>`, not a bare `cd <abs>` line then a relative `grep`/`wc` on a following line or after a `;`, not inside a heredoc or multi-statement block, and not as its own separate Bash call or turn: the shell working directory persists across calls, so a standalone `cd <abs>` still leaves a later relative read non-static): any `cd` makes the read target non-static, so under bypass permissions with a `Read()` deny rule it prompts for manual approval instead of running headless.
|
|
@@ -8,3 +8,5 @@ tools: Read, Grep, Glob, Bash, WebFetch, WebSearch, TodoWrite
|
|
|
8
8
|
trailhead's verification subagent. Run the Goal-backward verification: check that the ticket's implemented change actually delivers what the ticket promised (its `Question` and the `PLAN`'s verification criteria), not merely that steps ran or the tests are green. Report the verdict into trailhead's `VERIFY` comment with a leading `status:` tag.
|
|
9
9
|
|
|
10
10
|
The full protocol is injected at spawn from the trailhead skill's single source (`_shared/techniques/verify.md`); follow that, not any embedded copy. Read-only by policy: it verifies and reports, it must never Edit/Write or commit. Model-agnostic by design: the engine passes the `config.models.verify` model as the spawn-time override, so config stays authoritative.
|
|
11
|
+
|
|
12
|
+
**Search-command hygiene.** Prefer the Grep and Read tools over shelling out; when a Bash `grep`/read is genuinely needed, pass the path as an explicit argument (`grep -niE "<patterns>" <abs>/<file>`, `wc -l <abs>/<file>`) and run git as `git -C <abs> ...`. **Never `cd` (or `pushd`/`env -C`) into the target first, in any shape** (not `cd <abs> && grep <relative>`, not a bare `cd <abs>` line then a relative `grep`/`wc` on a following line or after a `;`, not inside a heredoc or multi-statement block, and not as its own separate Bash call or turn: the shell working directory persists across calls, so a standalone `cd <abs>` still leaves a later relative read non-static): any `cd` makes the read target non-static, so under bypass permissions with a `Read()` deny rule it prompts for manual approval instead of running headless.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Run the frontier autonomously until a stop condition or exhaustion
|
|
3
|
+
argument-hint: "[map]"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Invoke the `trailhead` skill for the **auto** action: call the Skill tool with skill name `trailhead-work` and arguments `auto $ARGUMENTS`, then carry out the skill's instructions for that verb, per the auto engine in `references/auto.md` (the whole-frontier autonomous run: suspends one-ticket-per-session, takes advisory choices as delegate without the confirm gate, and stops only at the safety rail, fog, human-necessary decisions, or human interrupt). Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
@@ -5,4 +5,4 @@ argument-hint: "[ticket | \"text\"]"
|
|
|
5
5
|
|
|
6
6
|
Invoke the `trailhead` skill for the **quick** action: call the Skill tool with skill name `trailhead-work` and arguments `quick $ARGUMENTS`, then carry out the skill's instructions for that verb (open a `trailhead:whiteboard` ticket from the `"text"`, or take the existing ticket you name, then run the full engine on it without splitting and without map book-keeping). Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
7
7
|
|
|
8
|
-
**When `quick "<text>"` opens a NEW whiteboard ticket, refresh the pinned `trailhead:dashboard`
|
|
8
|
+
**When `quick "<text>"` opens a NEW whiteboard ticket, refresh the pinned `trailhead:dashboard` at the handoff (Resolve), not at ticket creation** (the whiteboard has no native progress bar, so the dashboard is where the ticket shows; a quick is born and resolved in one session, so the refresh rides with its end, a full regeneration from the live tracker, never a mid-work partial patch). If the session pauses before Resolve (`/trailhead:pause`), refresh there instead. `quick <n>` on an existing *map* ticket does not refresh. See the skill's *quick* section and the dashboard freshness rule.
|
|
@@ -15,6 +15,19 @@
|
|
|
15
15
|
{
|
|
16
16
|
"type": "command",
|
|
17
17
|
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/trailhead-install-guard.js\""
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"type": "command",
|
|
21
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/trailhead-search-guard.js\""
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"matcher": "Read|Bash",
|
|
27
|
+
"hooks": [
|
|
28
|
+
{
|
|
29
|
+
"type": "command",
|
|
30
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/trailhead-secret-read-guard.js\""
|
|
18
31
|
}
|
|
19
32
|
]
|
|
20
33
|
}
|