@marcomigozzi/trailhead 0.11.1 → 0.12.1
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 +6 -3
- package/README.md +6 -3
- package/bin/lib/codex-projection.js +2 -1
- package/bin/lib/codex-projection.test.js +4 -1
- package/bin/trailhead.js +23 -13
- package/bin/trailhead.test.js +328 -0
- package/package.json +1 -1
- package/plugins/trailhead/.claude-plugin/plugin.json +1 -1
- package/plugins/trailhead/commands/adopt.md +5 -1
- package/plugins/trailhead/commands/audit.md +10 -0
- package/plugins/trailhead/commands/auto.md +5 -1
- package/plugins/trailhead/commands/bug.md +5 -1
- package/plugins/trailhead/commands/config.md +5 -1
- package/plugins/trailhead/commands/dashboard.md +5 -1
- package/plugins/trailhead/commands/grill.md +5 -1
- package/plugins/trailhead/commands/idea.md +5 -1
- package/plugins/trailhead/commands/inbox.md +5 -1
- package/plugins/trailhead/commands/map.md +5 -1
- package/plugins/trailhead/commands/new.md +5 -1
- package/plugins/trailhead/commands/note.md +5 -1
- package/plugins/trailhead/commands/pause.md +5 -1
- package/plugins/trailhead/commands/prune.md +5 -1
- package/plugins/trailhead/commands/quick.md +6 -2
- package/plugins/trailhead/commands/resume.md +5 -1
- package/plugins/trailhead/commands/seed.md +5 -1
- package/plugins/trailhead/commands/split.md +5 -1
- package/plugins/trailhead/commands/ticket.md +5 -1
- package/plugins/trailhead/commands/todo.md +5 -1
- package/plugins/trailhead/commands/update.md +5 -1
- package/plugins/trailhead/commands/whiteboard.md +5 -1
- package/plugins/trailhead/commands/work.md +5 -1
- package/plugins/trailhead/hooks/hooks.json +9 -0
- package/plugins/trailhead/hooks/lib/audit-walk.test.js +802 -0
- package/plugins/trailhead/hooks/lib/fixtures/label-guard-0.12.0.yml +55 -0
- package/plugins/trailhead/hooks/lib/label-guard-sync.test.js +156 -0
- package/plugins/trailhead/hooks/lib/mockup-link.js +50 -0
- package/plugins/trailhead/hooks/lib/model-defaults-review.test.js +56 -4
- package/plugins/trailhead/hooks/lib/model-defaults.json +5 -1
- package/plugins/trailhead/hooks/trailhead-check-update.js +25 -1
- package/plugins/trailhead/hooks/trailhead-mockup-link-guard.js +95 -0
- package/plugins/trailhead/hooks/trailhead-mockup-link-guard.test.js +145 -0
- package/plugins/trailhead/hooks/trailhead-mockup-link-stop.js +38 -0
- package/plugins/trailhead/hooks/trailhead-mockup-link-stop.test.js +64 -0
- package/plugins/trailhead/hooks/trailhead-secret-read-guard.js +151 -6
- package/plugins/trailhead/hooks/trailhead-secret-read-guard.test.js +56 -0
- package/plugins/trailhead/skills/_shared/choices.md +2 -0
- package/plugins/trailhead/skills/_shared/configuration-reference.md +3 -3
- package/plugins/trailhead/skills/_shared/load-first.md +2 -0
- package/plugins/trailhead/skills/_shared/principles.md +1 -1
- package/plugins/trailhead/skills/_shared/session-handoff.md +1 -1
- package/plugins/trailhead/skills/_shared/substrate-commands.md +9 -2
- package/plugins/trailhead/skills/_shared/teamwork.md +3 -1
- package/plugins/trailhead/skills/_shared/techniques/prototype.md +1 -1
- package/plugins/trailhead/skills/trailhead/SKILL.md +2 -2
- package/plugins/trailhead/skills/trailhead-capture/references/capture.md +1 -1
- package/plugins/trailhead/skills/trailhead-manage/SKILL.md +5 -4
- package/plugins/trailhead/skills/trailhead-manage/references/auditing.md +132 -0
- package/plugins/trailhead/skills/trailhead-manage/references/updating.md +4 -0
- package/plugins/trailhead/skills/trailhead-view/SKILL.md +1 -0
- package/plugins/trailhead/skills/trailhead-work/SKILL.md +1 -1
- package/plugins/trailhead/templates/trailhead-audit-walk.js +685 -0
- package/plugins/trailhead/templates/trailhead-label-guard-sync.js +212 -0
- package/plugins/trailhead/templates/trailhead-label-guard.yml +5 -0
package/README.it.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
🇬🇧 [English](README.md) · 🇮🇹 **Italiano**
|
|
8
8
|
|
|
9
|
-
[](https://trailhead.marcomigozzi.it) [](https://github.com/ToRvaLDz/trailhead/actions/workflows/site-ci.yml) [](https://www.npmjs.com/package/@marcomigozzi/trailhead) [](LICENSE)  ](https://trailhead.marcomigozzi.it) [](https://github.com/ToRvaLDz/trailhead/actions/workflows/site-ci.yml) [](https://www.npmjs.com/package/@marcomigozzi/trailhead) [](LICENSE)   
|
|
10
10
|
|
|
11
11
|
</div>
|
|
12
12
|
|
|
@@ -294,6 +294,8 @@ Tre layer: **il più vicino vince**, chiave per chiave (una chiave non impostata
|
|
|
294
294
|
|
|
295
295
|
`/trailhead:prune [map]` recupera gli slot di sub-issue di una mappa sotto il cap di 100 di GitHub (elimina gli edge chiusi/orfani, mantiene ogni riferimento). Offerto automaticamente quando una mappa è vicina o al cap, ed eseguibile anche su richiesta.
|
|
296
296
|
|
|
297
|
+
`/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. 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, la chiusura di un duplicato, un supersede-and-merge) è una proposta applicata solo con un sì esplicito. `--all` riverifica tutti i ticket chiusi.
|
|
298
|
+
|
|
297
299
|
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.
|
|
298
300
|
|
|
299
301
|
**Offerta al primo uso (una volta per progetto).** Non devi andarla a cercare: la prima volta che configuri un progetto (alla fine di `/trailhead:new`/`:adopt`, o su un `/trailhead` nudo quando esiste una mappa ma non una config) trailhead offre di eseguire il setup guidato, o di continuare sui default. Chiede **solo una volta**: configurare scrive `.trailhead/config.json`, e "continua sui default" scrive un `{}` vuoto. Una volta che quel file esiste non viene più offerto, e non interrompe mai a metà ticket.
|
|
@@ -341,7 +343,7 @@ Se l'MCP della modalità hosted non è connesso, trailhead **offre di installarl
|
|
|
341
343
|
|
|
342
344
|
## 🪝 Hooks
|
|
343
345
|
|
|
344
|
-
Oltre alle istruzioni della skill, trailhead include
|
|
346
|
+
Oltre alle istruzioni della skill, trailhead include otto 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:
|
|
345
347
|
|
|
346
348
|
- **Commit guard** (`PreToolUse` su `git commit`): blocca in modo netto un commit il cui subject non è [Conventional Commits](https://www.conventionalcommits.org), e blocca in modo netto qualsiasi trailer `Co-Authored-By`. Questo rispecchia la validazione dei commit di GSD.
|
|
347
349
|
- **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.
|
|
@@ -349,9 +351,10 @@ Oltre alle istruzioni della skill, trailhead include sette hook propri (in `hook
|
|
|
349
351
|
- **Search guard** (`PreToolUse` su `Bash`): un backstop strutturale per la regola di igiene dei comandi di ricerca (mai `cd` seguito da una lettura/ricerca su percorso relativo). Blocca in modo netto un singolo comando Bash che sia insieme un cambio di directory (`cd`/`pushd`/`env -C`) e, in un'istruzione unita o successiva, una lettura/ricerca su percorso relativo (`grep`/`rg`/`ag`/`cat`/`head`/`tail`/`wc`/`nl`/`sed`/`awk`/`less`/`more`, o `git grep`/`log`/`show`/`diff` senza `-C`), così la forma che fa scattare una regola di deny su `Read()` sotto bypass permissions (ripiegando su un prompt di approvazione manuale invece di girare headless) non viene mai eseguita, anziché essere solo scoraggiata nella prosa dell'agente. Solo Claude Code; non è proiettata su Codex, che non ha una regola di deny su `Read()`.
|
|
350
352
|
- **Body guard** (`PreToolUse` sulle scritture `gh` di body issue/PR): blocca in modo netto una scrittura `gh issue edit` / `gh pr edit` / `gh api` che imposterebbe il body di un issue a vuoto o solo spazi, così un read-modify-write la cui lettura è tornata con un body vuoto/degradato (es. un HTTP 5xx transitorio su `gh issue view --json body`) non può mai cancellare il body esistente.
|
|
351
353
|
- **Secret-read guard** (`PreToolUse` su `Read|Bash`): blocca in modo netto la lettura di `.env`/`.env.*`/`.secrets`, sia tramite il tool Read sia tramite un comando Bash (un operando file, il valore di un'opzione, o un target di redirect `<`). È una decisione `deny` a livello di hook anziché una regola di permesso su `Read()`, quindi i segreti restano illeggibili senza attivare il prompt di approvazione `cd-compound-read` di Claude Code 2.1.259.
|
|
354
|
+
- **Mockup-link guard** (`PreToolUse` su `AskUserQuestion`): rete di sicurezza strutturale per la regola della tecnica Prototype secondo cui la richiesta di approvazione di un mockup porta con sé il link al mockup. Blocca un `AskUserQuestion` che chiede di approvare un mockup (o di scegliere tra varianti) quando il testo della risposta subito prima non contiene un URL o un path, 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.
|
|
352
355
|
- **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.
|
|
353
356
|
|
|
354
|
-
Tutti e
|
|
357
|
+
Tutti e otto sono crash-safe (qualsiasi errore → allow) e attivi ogni volta che il plugin è installato. I guard di commit, secret, install, search, body e secret-read quindi si applicano in **ogni** repo, non solo nei progetti trailhead: intenzionale, dato che i conventional commit, niente `Co-Authored-By`, nessun segreto leakato, nessuna installazione non verificata, niente `cd`-poi-lettura-relativa, nessun body issue cancellato, e niente `.env`/`.secrets` leggibili sono regole permanenti di trailhead. Per fare opt-out, disabilita gli hook del plugin nelle impostazioni di Claude Code.
|
|
355
358
|
|
|
356
359
|
---
|
|
357
360
|
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
🇬🇧 **English** · 🇮🇹 [Italiano](README.it.md)
|
|
8
8
|
|
|
9
|
-
[](https://trailhead.marcomigozzi.it) [](https://github.com/ToRvaLDz/trailhead/actions/workflows/site-ci.yml) [](https://www.npmjs.com/package/@marcomigozzi/trailhead) [](LICENSE)  ](https://trailhead.marcomigozzi.it) [](https://github.com/ToRvaLDz/trailhead/actions/workflows/site-ci.yml) [](https://www.npmjs.com/package/@marcomigozzi/trailhead) [](LICENSE)   
|
|
10
10
|
|
|
11
11
|
</div>
|
|
12
12
|
|
|
@@ -294,6 +294,8 @@ Three layers: **nearest wins**, key by key (a key unset at one layer inherits th
|
|
|
294
294
|
|
|
295
295
|
`/trailhead:prune [map]` reclaims a map's sub-issue slots under GitHub's 100-cap (drops closed/orphan edges, keeps every reference). Offered automatically when a map is near or at the cap, and runnable on demand.
|
|
296
296
|
|
|
297
|
+
`/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 then compares the open ones for duplicates and groupable clusters. It is read-only until you confirm: every fix (a regression bug, a reopen, a duplicate close, a supersede-and-merge) is a proposal applied only on an explicit yes. `--all` re-audits every closed ticket.
|
|
298
|
+
|
|
297
299
|
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.
|
|
298
300
|
|
|
299
301
|
**First-use offer (once per project).** You don't have to seek this out: the first time you set a project up (at the end of `/trailhead:new`/`:adopt`, or on a bare `/trailhead` when a map exists but no config does) trailhead offers to run the guided setup, or to continue on defaults. It asks **only once**: configuring writes `.trailhead/config.json`, and "continue on defaults" writes an empty `{}`. Once that file exists it's never offered again, and it never interrupts mid-ticket.
|
|
@@ -341,7 +343,7 @@ If a hosted mode's MCP isn't connected, trailhead **offers to install it once**
|
|
|
341
343
|
|
|
342
344
|
## 🪝 Hooks
|
|
343
345
|
|
|
344
|
-
Beyond the skill's instructions, trailhead ships
|
|
346
|
+
Beyond the skill's instructions, trailhead ships eight 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:
|
|
345
347
|
|
|
346
348
|
- **Commit guard** (`PreToolUse` on `git commit`): hard-blocks a commit whose subject isn't [Conventional Commits](https://www.conventionalcommits.org), and hard-blocks any `Co-Authored-By` trailer. This mirrors GSD's commit validation.
|
|
347
349
|
- **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.
|
|
@@ -349,9 +351,10 @@ Beyond the skill's instructions, trailhead ships seven of its own hooks (in `hoo
|
|
|
349
351
|
- **Search guard** (`PreToolUse` on `Bash`): structural backstop for the search-command-hygiene rule (never `cd` then read/search a relative path). It hard-blocks a single Bash command that both changes directory (`cd`/`pushd`/`env -C`) and, in a joined or later statement, reads/searches a relative path (`grep`/`rg`/`ag`/`cat`/`head`/`tail`/`wc`/`nl`/`sed`/`awk`/`less`/`more`, or `git grep`/`log`/`show`/`diff` without a `-C`), so the shape that trips a `Read()` deny rule under bypass permissions (falling back to a manual approval prompt instead of running headless) never runs at all, rather than merely being discouraged in the agent's own prose. Claude Code only; not projected to Codex, which has no such `Read()` deny rule.
|
|
350
352
|
- **Body guard** (`PreToolUse` on `gh` issue/PR body writes): hard-blocks a `gh issue edit` / `gh pr edit` / `gh api` write that would set an issue body to empty or whitespace-only, so a read-modify-write whose read returned an empty/degraded body (e.g. a transient HTTP 5xx on `gh issue view --json body`) can never clobber the existing body.
|
|
351
353
|
- **Secret-read guard** (`PreToolUse` on `Read|Bash`): denies reads of `.env`/`.env.*`/`.secrets`, whether via the Read tool or a Bash command (a file operand, an option value, or a `<` redirect target). This is a hook `deny` decision rather than a `Read()` permission rule, so secrets stay unreadable without arming Claude Code 2.1.259's `cd-compound-read` approval prompt.
|
|
354
|
+
- **Mockup-link guard** (`PreToolUse` on `AskUserQuestion`): structural backstop for the Prototype rule that a mockup approval ask carries the mockup's link. It hard-blocks an `AskUserQuestion` that asks to approve a mockup (or pick between mockup variants) when the reply text right before it holds no URL or file path, 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.
|
|
352
355
|
- **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.
|
|
353
356
|
|
|
354
|
-
All
|
|
357
|
+
All eight are crash-safe (any error → allow) and active whenever the plugin is installed. The commit, secret, install, search, body, and secret-read guards therefore apply in **every** repo, not only trailhead projects: intentional, since conventional commits, no `Co-Authored-By`, no leaked secrets, no unvetted installs, no `cd`-then-relative-read, no clobbered issue bodies, and no readable `.env`/`.secrets` are trailhead's standing rules. To opt out, disable the plugin's hooks in your Claude Code settings.
|
|
355
358
|
|
|
356
359
|
---
|
|
357
360
|
|
|
@@ -113,7 +113,7 @@ Codex has a subagent toolkit: the multi_agent tools (\`spawn_agent\`, \`send_inp
|
|
|
113
113
|
The engine's \`/clear\` is Codex's \`/new\` (start a fresh session). Command references in a handoff use the skill form (\`$trailhead work <n>\`).
|
|
114
114
|
|
|
115
115
|
## F. Lifecycle hooks (native Codex hooks)
|
|
116
|
-
Codex has a hook bus, so trailhead's guardrails run as **real Codex hooks**, not degraded prose. The installer registers them in \`~/.codex/hooks.json\`: commit-guard, secret-guard, install-guard and body-guard as \`PreToolUse\` (matcher \`Bash\`) hooks that can veto the command before it runs, secret-read-guard as a \`PreToolUse\` (matcher \`Read|Bash\`) hook that denies reads of \`.env\`/\`.env.*\`/\`.secrets\`, the injection-scanner as a \`PostToolUse\` (matcher \`Bash\`) advisory, and check-update as a \`SessionStart\` hook. On Codex, file reads happen through shell commands rather than a native Read-tool event, so the secret-read guard's enforcement here runs via its **Bash** arm (protection is not lost); the \`Read\` half of its matcher is a Claude-Code-native path, registered on Codex too only so it fires without changes if a future Codex version adds an equivalent event. search-guard is Claude-Code-specific (it only prevents arming Claude's \`Read()\` deny rule under bypass permissions, a mechanism Codex lacks), so it is not projected to Codex at all. They use the same wire format they use on Claude Code (JSON on stdin, a \`decision\`/\`permissionDecision\` verdict on stdout), which Codex accepts. The host-independent git \`commit-msg\` hook is still installed at repo first-use as well (defence in depth), exactly as on Claude. Codex gates hooks behind \`features.hooks = true\` and reviews them for trust on first start: the installer enables the flag, and Codex will ask you to trust trailhead's hooks the next time it starts.
|
|
116
|
+
Codex has a hook bus, so trailhead's guardrails run as **real Codex hooks**, not degraded prose. The installer registers them in \`~/.codex/hooks.json\`: commit-guard, secret-guard, install-guard and body-guard as \`PreToolUse\` (matcher \`Bash\`) hooks that can veto the command before it runs, secret-read-guard as a \`PreToolUse\` (matcher \`Read|Bash\`) hook that denies reads of \`.env\`/\`.env.*\`/\`.secrets\`, the injection-scanner as a \`PostToolUse\` (matcher \`Bash\`) advisory, mockup-link-stop as a \`Stop\` hook (when your final reply asks to approve a mockup without carrying its link, it continues the turn so you rewrite the ask with the URL or file path in the prose), and check-update as a \`SessionStart\` hook. On Codex, file reads happen through shell commands rather than a native Read-tool event, so the secret-read guard's enforcement here runs via its **Bash** arm (protection is not lost); the \`Read\` half of its matcher is a Claude-Code-native path, registered on Codex too only so it fires without changes if a future Codex version adds an equivalent event. search-guard is Claude-Code-specific (it only prevents arming Claude's \`Read()\` deny rule under bypass permissions, a mechanism Codex lacks), so it is not projected to Codex at all. They use the same wire format they use on Claude Code (JSON on stdin, a \`decision\`/\`permissionDecision\` verdict on stdout), which Codex accepts. The host-independent git \`commit-msg\` hook is still installed at repo first-use as well (defence in depth), exactly as on Claude. Codex gates hooks behind \`features.hooks = true\` and reviews them for trust on first start: the installer enables the flag, and Codex will ask you to trust trailhead's hooks the next time it starts.
|
|
117
117
|
|
|
118
118
|
## G. Design / UI mockups -> local disk
|
|
119
119
|
The \`claude.ai/design\` mockup mode (DesignSync: the \`claude_design\` MCP + the \`/design-sync\` skill) does not exist on Codex, so it **collapses to local-disk mockups** here: whatever \`config.design\` says, the Prototype technique's \`disk\` path is the only one available. Produce a throwaway static HTML mockup next to the code, link it from the ticket, and never attempt DesignSync / claude.ai/design on Codex (there is no \`claude_design\` MCP or \`/design-sync\` skill to call). \`config.design.approval\` still applies unchanged: \`explicit\` waits for the user's go-ahead before any UI code, \`auto\` surfaces the mockup and proceeds.
|
|
@@ -378,6 +378,7 @@ function codexHookEntries(hooksScriptsDir) {
|
|
|
378
378
|
{ event: 'PreToolUse', matcher: 'Bash', command: cmd('trailhead-body-guard.js') },
|
|
379
379
|
{ event: 'PreToolUse', matcher: 'Read|Bash', command: cmd('trailhead-secret-read-guard.js') },
|
|
380
380
|
{ event: 'PostToolUse', matcher: 'Bash', command: cmd('trailhead-issue-injection-scanner.js') },
|
|
381
|
+
{ event: 'Stop', matcher: '', command: cmd('trailhead-mockup-link-stop.js') },
|
|
381
382
|
{ event: 'SessionStart', matcher: '', command: cmd('trailhead-check-update.js') },
|
|
382
383
|
];
|
|
383
384
|
}
|
|
@@ -102,6 +102,7 @@ ok('codexSkillAdapterHeader §D offers a background-job fallback for a stalling
|
|
|
102
102
|
ok('codexSkillAdapterHeader §D makes background+poll the hard cross-host default for reviews',
|
|
103
103
|
header.includes('always run this way') && header.includes('hard cross-host default'));
|
|
104
104
|
ok('codexSkillAdapterHeader §F mentions real Codex hooks', header.includes('real Codex hooks'));
|
|
105
|
+
ok('codexSkillAdapterHeader §F lists mockup-link-stop as a Stop hook', header.includes('mockup-link-stop as a `Stop` hook'));
|
|
105
106
|
ok('codexSkillAdapterHeader §F no longer says Codex has no hook bus', !header.includes('Codex has no hook bus'));
|
|
106
107
|
ok('codexSkillAdapterHeader §F does not list search-guard among the projected PreToolUse guards',
|
|
107
108
|
!header.includes('search-guard and body-guard') && !header.includes('search-guard, body-guard'));
|
|
@@ -161,7 +162,7 @@ ok('codexVerbSkillPlan: null verbs yields nothing', codexVerbSkillPlan('/c', nul
|
|
|
161
162
|
|
|
162
163
|
// --- codexHookEntries ---
|
|
163
164
|
const entries = codexHookEntries('/h');
|
|
164
|
-
ok('codexHookEntries returns
|
|
165
|
+
ok('codexHookEntries returns 8 entries', Array.isArray(entries) && entries.length === 8);
|
|
165
166
|
ok('codexHookEntries: commit-guard is PreToolUse/Bash', entries.some((e) =>
|
|
166
167
|
e.event === 'PreToolUse' && e.matcher === 'Bash' && e.command.includes('trailhead-commit-guard.js') && e.command.includes('/h')));
|
|
167
168
|
ok('codexHookEntries: secret-guard is PreToolUse/Bash', entries.some((e) =>
|
|
@@ -176,6 +177,8 @@ ok('codexHookEntries: secret-read-guard is PreToolUse/Read|Bash', entries.some((
|
|
|
176
177
|
e.event === 'PreToolUse' && e.matcher === 'Read|Bash' && e.command.includes('trailhead-secret-read-guard.js')));
|
|
177
178
|
ok('codexHookEntries: injection-scanner is PostToolUse/Bash', entries.some((e) =>
|
|
178
179
|
e.event === 'PostToolUse' && e.matcher === 'Bash' && e.command.includes('trailhead-issue-injection-scanner.js')));
|
|
180
|
+
ok('codexHookEntries: mockup-link-stop is Stop', entries.some((e) =>
|
|
181
|
+
e.event === 'Stop' && e.command.includes('trailhead-mockup-link-stop.js')));
|
|
179
182
|
ok('codexHookEntries: check-update is SessionStart', entries.some((e) =>
|
|
180
183
|
e.event === 'SessionStart' && e.command.includes('trailhead-check-update.js')));
|
|
181
184
|
|
package/bin/trailhead.js
CHANGED
|
@@ -243,21 +243,27 @@ function claudePaths(configDir) {
|
|
|
243
243
|
settings: path.join(configDir, 'settings.json'),
|
|
244
244
|
};
|
|
245
245
|
}
|
|
246
|
-
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-body-guard.js', 'trailhead-check-update.js'];
|
|
246
|
+
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-body-guard.js', 'trailhead-mockup-link-guard.js', 'trailhead-mockup-link-stop.js', 'trailhead-check-update.js'];
|
|
247
247
|
// Runtime libs the hook scripts require (trailhead-commit-guard.js does
|
|
248
248
|
// require('./lib/commit-message-check.js'); trailhead-search-guard.js and
|
|
249
249
|
// trailhead-secret-read-guard.js both do require('./lib/shell-scan.js'), #135
|
|
250
250
|
// follow-up; trailhead-body-guard.js and trailhead-secret-guard.js both do
|
|
251
251
|
// require('./lib/gh-subcommand.js'), #153 follow-up; trailhead-check-update.js
|
|
252
252
|
// does require('./lib/model-defaults-review.js'), which in turn reads the
|
|
253
|
-
// sibling model-defaults.json, #186
|
|
253
|
+
// sibling model-defaults.json, #186; both mockup-link hooks do
|
|
254
|
+
// require('./lib/mockup-link.js')). Copied by name, never a recursive
|
|
254
255
|
// sweep: the Claude hooks/lib dir is shared with other plugins, so we must
|
|
255
256
|
// not clobber a co-tenant's lib nor ship our own *.test.js files.
|
|
256
|
-
const HOOK_LIB_FILES = ['commit-message-check.js', 'shell-scan.js', 'gh-subcommand.js', 'model-defaults-review.js', 'model-defaults.json'];
|
|
257
|
-
// search-guard is Claude-Code-specific (#169)
|
|
258
|
-
//
|
|
259
|
-
// Codex
|
|
260
|
-
|
|
257
|
+
const HOOK_LIB_FILES = ['commit-message-check.js', 'shell-scan.js', 'gh-subcommand.js', 'model-defaults-review.js', 'model-defaults.json', 'mockup-link.js'];
|
|
258
|
+
// search-guard is Claude-Code-specific (#169), and so is mockup-link-guard
|
|
259
|
+
// (it gates the AskUserQuestion tool, which Codex lacks): excluded from both
|
|
260
|
+
// the Codex hook copy and the Codex verify spec's expected hookFiles, so a
|
|
261
|
+
// healthy Codex install is never flagged for a script it never ships.
|
|
262
|
+
const CODEX_HOOK_EXCLUDE = ['trailhead-search-guard.js', 'trailhead-mockup-link-guard.js'];
|
|
263
|
+
// mockup-link-stop is its Codex twin (a Stop hook reading the final reply):
|
|
264
|
+
// Claude Code gates the AskUserQuestion call instead, so it is neither copied
|
|
265
|
+
// nor expected on a Claude install.
|
|
266
|
+
const CLAUDE_HOOK_EXCLUDE = ['trailhead-mockup-link-stop.js'];
|
|
261
267
|
|
|
262
268
|
// Copy the hook scripts and their runtime libs into a destination hooks dir
|
|
263
269
|
// (Claude's ~/.claude/hooks or Codex's skills/trailhead/hooks). Shared by both
|
|
@@ -371,6 +377,7 @@ function uninstallClaude(configDir) {
|
|
|
371
377
|
stripHook(s, 'PreToolUse', 'trailhead-search-guard.js');
|
|
372
378
|
stripHook(s, 'PreToolUse', 'trailhead-secret-read-guard.js');
|
|
373
379
|
stripHook(s, 'PreToolUse', 'trailhead-body-guard.js');
|
|
380
|
+
stripHook(s, 'PreToolUse', 'trailhead-mockup-link-guard.js');
|
|
374
381
|
stripHook(s, 'PostToolUse', 'trailhead-issue-injection-scanner.js');
|
|
375
382
|
stripHook(s, 'SessionStart', 'trailhead-check-update.js');
|
|
376
383
|
writeJSON(P.settings, s);
|
|
@@ -395,7 +402,7 @@ function installClaude(configDir, { useSymlink }) {
|
|
|
395
402
|
}
|
|
396
403
|
place(path.join(SRC, 'commands'), P.commands, useSymlink);
|
|
397
404
|
place(path.join(SRC, 'templates'), P.templates, useSymlink);
|
|
398
|
-
copyHookScripts(P.hooksDir, { useSymlink });
|
|
405
|
+
copyHookScripts(P.hooksDir, { useSymlink, exclude: CLAUDE_HOOK_EXCLUDE });
|
|
399
406
|
// Register the engine agents as Claude subagents: sweep any stale
|
|
400
407
|
// trailhead-*.md first (a removed/renamed agent), then place the current set
|
|
401
408
|
// by name. This is what makes config.models.<key> take effect on the
|
|
@@ -419,6 +426,7 @@ function installClaude(configDir, { useSymlink }) {
|
|
|
419
426
|
addHook(s, 'PreToolUse', 'Bash', hookCmd('trailhead-search-guard.js')),
|
|
420
427
|
addHook(s, 'PreToolUse', 'Bash', hookCmd('trailhead-body-guard.js')),
|
|
421
428
|
addHook(s, 'PreToolUse', 'Read|Bash', hookCmd('trailhead-secret-read-guard.js')),
|
|
429
|
+
addHook(s, 'PreToolUse', 'AskUserQuestion', hookCmd('trailhead-mockup-link-guard.js')),
|
|
422
430
|
addHook(s, 'PostToolUse', 'Bash', hookCmd('trailhead-issue-injection-scanner.js')),
|
|
423
431
|
addHook(s, 'SessionStart', '', hookCmd('trailhead-check-update.js')),
|
|
424
432
|
].some(Boolean);
|
|
@@ -430,7 +438,7 @@ function installClaude(configDir, { useSymlink }) {
|
|
|
430
438
|
const spec = claudeVerifySpec(configDir, {
|
|
431
439
|
skillDirs: shippedSkillDirs(),
|
|
432
440
|
sharedFiles: readSharedCoreFiles(),
|
|
433
|
-
hookFiles: HOOK_FILES,
|
|
441
|
+
hookFiles: HOOK_FILES.filter((f) => !CLAUDE_HOOK_EXCLUDE.includes(f)),
|
|
434
442
|
hookLibFiles: HOOK_LIB_FILES,
|
|
435
443
|
env: process.env,
|
|
436
444
|
home: os.homedir(),
|
|
@@ -501,14 +509,15 @@ function readCommandVerbs() {
|
|
|
501
509
|
.filter((f) => f.endsWith('.md'))
|
|
502
510
|
.map((f) => {
|
|
503
511
|
const raw = fs.readFileSync(path.join(dir, f), 'utf8');
|
|
504
|
-
return { verb: f.slice(0, -3), description: fmValue(raw, 'description'), argumentHint: fmValue(raw, 'argument-hint') };
|
|
512
|
+
return { verb: f.slice(0, -3), description: unquoteYamlScalar(fmValue(raw, 'description')), argumentHint: unquoteYamlScalar(fmValue(raw, 'argument-hint')) };
|
|
505
513
|
})
|
|
506
514
|
.sort((a, b) => a.verb.localeCompare(b.verb));
|
|
507
515
|
}
|
|
508
516
|
|
|
509
|
-
// Unquote a YAML double-quoted scalar
|
|
510
|
-
// frontmatter
|
|
511
|
-
// unquoted scalar untouched, so this is safe to apply
|
|
517
|
+
// Unquote a YAML double-quoted scalar. Applied to both the agent .md and the
|
|
518
|
+
// commands/*.md frontmatter, since either may wrap values in double quotes.
|
|
519
|
+
// Leaves a plain/unquoted scalar untouched, so this is safe to apply
|
|
520
|
+
// unconditionally.
|
|
512
521
|
function unquoteYamlScalar(v) {
|
|
513
522
|
if (typeof v !== 'string' || v.length < 2 || v[0] !== '"' || v[v.length - 1] !== '"') return v;
|
|
514
523
|
return v.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
|
|
@@ -774,6 +783,7 @@ function uninstallCodex(configDir) {
|
|
|
774
783
|
stripHook(h, 'PreToolUse', 'trailhead-secret-read-guard.js');
|
|
775
784
|
stripHook(h, 'PreToolUse', 'trailhead-body-guard.js');
|
|
776
785
|
stripHook(h, 'PostToolUse', 'trailhead-issue-injection-scanner.js');
|
|
786
|
+
stripHook(h, 'Stop', 'trailhead-mockup-link-stop.js');
|
|
777
787
|
stripHook(h, 'SessionStart', 'trailhead-check-update.js');
|
|
778
788
|
writeJSON(L.hooksJson, h);
|
|
779
789
|
}
|