@marcomigozzi/trailhead 0.5.0 → 0.6.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 +8 -5
- package/README.md +8 -5
- package/bin/lib/codex-projection.js +79 -58
- package/bin/lib/codex-projection.test.js +103 -15
- package/bin/trailhead.js +106 -15
- package/bin/trailhead.test.js +76 -11
- package/package.json +2 -2
- package/plugins/trailhead/.claude-plugin/plugin.json +1 -1
- package/plugins/trailhead/agents/trailhead-code-review.md +10 -0
- package/plugins/trailhead/agents/trailhead-codebase-map.md +10 -0
- package/plugins/trailhead/agents/trailhead-debug.md +10 -0
- package/plugins/trailhead/agents/trailhead-executor.md +10 -0
- package/plugins/trailhead/agents/trailhead-fix.md +10 -0
- package/plugins/trailhead/agents/trailhead-plan.md +10 -0
- package/plugins/trailhead/agents/trailhead-research.md +10 -0
- package/plugins/trailhead/agents/trailhead-verify.md +10 -0
- package/plugins/trailhead/hooks/hooks.json +4 -0
- package/plugins/trailhead/hooks/trailhead-install-guard.js +176 -0
- package/plugins/trailhead/hooks/trailhead-install-guard.test.js +118 -0
- package/plugins/trailhead/skills/_shared/configuration-reference.md +39 -12
- package/plugins/trailhead/skills/_shared/configuration.md +1 -1
- package/plugins/trailhead/skills/_shared/session-handoff.md +1 -1
- package/plugins/trailhead/skills/_shared/substrate-commands.md +4 -2
- package/plugins/trailhead/skills/_shared/substrate.md +1 -1
- package/plugins/trailhead/skills/_shared/techniques/code-review.md +2 -2
- package/plugins/trailhead/skills/_shared/techniques/codebase-map.md +1 -1
- package/plugins/trailhead/skills/_shared/techniques/debug.md +4 -1
- package/plugins/trailhead/skills/_shared/techniques/plan-review.md +1 -1
- package/plugins/trailhead/skills/_shared/techniques/prototype.md +6 -3
- package/plugins/trailhead/skills/_shared/techniques/research.md +2 -1
- package/plugins/trailhead/skills/_shared/techniques/verify.md +12 -0
- package/plugins/trailhead/skills/_shared/techniques.md +4 -3
- package/plugins/trailhead/skills/trailhead/SKILL.md +1 -1
- package/plugins/trailhead/skills/trailhead-capture/SKILL.md +1 -0
- package/plugins/trailhead/skills/trailhead-chart/SKILL.md +2 -1
- package/plugins/trailhead/skills/trailhead-chart/references/charting.md +1 -1
- package/plugins/trailhead/skills/trailhead-chart/references/inbox.md +6 -6
- package/plugins/trailhead/skills/trailhead-manage/SKILL.md +1 -0
- package/plugins/trailhead/skills/trailhead-manage/references/updating.md +12 -0
- package/plugins/trailhead/skills/trailhead-view/SKILL.md +2 -1
- package/plugins/trailhead/skills/trailhead-work/SKILL.md +2 -0
- package/plugins/trailhead/skills/trailhead-work/references/ticket-engines.md +12 -8
package/README.it.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
+

|
|
4
|
+
|
|
3
5
|
# Trailhead
|
|
4
6
|
|
|
5
7
|
🇬🇧 [English](README.md) · 🇮🇹 **Italiano**
|
|
6
8
|
|
|
7
|
-
[](https://www.npmjs.com/package/@marcomigozzi/trailhead) [](LICENSE)   
|
|
9
|
+
[](https://trailhead.marcomigozzi.it) [](https://www.npmjs.com/package/@marcomigozzi/trailhead) [](LICENSE)   
|
|
8
10
|
|
|
9
11
|
</div>
|
|
10
12
|
|
|
@@ -24,7 +26,7 @@ Due approcci al lavoro di progetto guidato dagli agenti risolvono ciascuno una m
|
|
|
24
26
|
|
|
25
27
|
Lavorando su entrambi, uno schema continuava a ripresentarsi: **usa una mappa e un onboarding in stile Wayfinder per decidere la forma del lavoro, poi un motore in stile GSD per costruire ogni pezzo, senza mai lasciare l'issue tracker.** `trailhead` è quello schema trasformato in un'unica skill, una sua interpretazione autonoma di entrambe le idee anziché un wrapper attorno all'una o all'altra.
|
|
26
28
|
|
|
27
|
-
> **trailhead è self-contained
|
|
29
|
+
> **trailhead è self-contained** nel senso che ha **zero dipendenze da plugin esterni**: gli agent di trailhead e i built-in dell'host vanno bene, e nessun'altra skill o plugin è richiesta. Wayfinder e GSD sono *ispirazione*, non dipendenze. Ogni tecnica che quei sistemi impacchettano come skill separata (grilling, TDD, debugging sistematico, codebase mapping, code review) è **integrata qui come protocollo inline**. Il core di trailhead non invoca nessun'altra skill; ogni tecnica del motore dispatcha al **proprio agent `trailhead-*`** (`trailhead-executor`, `trailhead-plan`, `trailhead-research`, `trailhead-code-review`, `trailhead-debug`, più `trailhead-fix` e `trailhead-codebase-map`), scritto una volta come file `agents/trailhead-*.md` versionato e **proiettato su ogni host** (su Codex diventa `~/.codex/agents/trailhead-*.toml` generato dalla stessa sorgente). Questi agent sono **thin** (identità + tool policy + un modello guidato dalla config, con il protocollo della tecnica iniettato allo spawn) e girano sul toolkit di subagent nativo dell'host (il tool `Agent` integrato di Claude Code, oppure il toolkit `multi_agent` di Codex). L'unica cosa da installare è trailhead stesso. C'è un'unica eccezione opt-in: la modalità mockup hosted. Quando abilitata, `claude.ai/design` costruisce il mockup su claude.ai/design (solo Claude): per default una canvas (la skill integrata `/design` pubblicata come Artifact, o un progetto creato a mano nella web app, non guidato da MCP) o, per una libreria di componenti condivisa, un progetto design-system tramite DesignSync (l'MCP `claude-design` più la skill `/design-sync`, l'unica superficie guidata da MCP). Una terza modalità, `stitch`, usa l'MCP Google Stitch e funziona sia su **Claude Code sia su Codex**, quindi è l'opzione hosted cross-host. Se l'MCP della modalità configurata non è connesso, offre un'installazione una tantum e ripiega sui mockup su disco locale se rifiuti; su Codex, dove `claude.ai/design` non è disponibile, offre di passare a `stitch` prima di ripiegare sul disco locale.
|
|
28
30
|
|
|
29
31
|
Le scelte di design che ne derivano:
|
|
30
32
|
|
|
@@ -73,7 +75,7 @@ Su **Codex CLI** (`--codex`) proietta una skill nativa Codex invocata come **`$t
|
|
|
73
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).
|
|
74
76
|
|
|
75
77
|
### Dopo l'installazione
|
|
76
|
-
Riavvia o ricarica il tuo agente così i comandi si registrano, poi esegui **`/trailhead`** per iniziare (smart entry), o `/trailhead:new "<idea>"` per tracciare una mappa. **Su Codex** la superficie è `$trailhead`: esegui **`$trailhead`** per lo smart entry, `$trailhead new "<idea>"` per tracciare, o una skill per-verbo `$trailhead-<verb>`. Nota: una volta installato, l'**hook di commit guard gira a ogni `git commit`** (imponendo i Conventional Commits e bloccando `Co-Authored-By`); disabilita gli hook del plugin nelle impostazioni se non lo vuoi. In ogni caso trailhead è self-contained:
|
|
78
|
+
Riavvia o ricarica il tuo agente così i comandi si registrano, poi esegui **`/trailhead`** per iniziare (smart entry), o `/trailhead:new "<idea>"` per tracciare una mappa. **Su Codex** la superficie è `$trailhead`: esegui **`$trailhead`** per lo smart entry, `$trailhead new "<idea>"` per tracciare, o una skill per-verbo `$trailhead-<verb>`. Nota: una volta installato, l'**hook di commit guard gira a ogni `git commit`** (imponendo i Conventional Commits e bloccando `Co-Authored-By`); disabilita gli hook del plugin nelle impostazioni se non lo vuoi. In ogni caso trailhead è self-contained: nessuna skill o plugin esterna è richiesta, niente preflight, niente version drift.
|
|
77
79
|
|
|
78
80
|
---
|
|
79
81
|
|
|
@@ -294,7 +296,7 @@ Il passo **📊 statusline** offre di installare la status bar di trailhead per
|
|
|
294
296
|
| Key | Values (default **bold**) | Effetto |
|
|
295
297
|
|---|---|---|
|
|
296
298
|
| `ticket.language` | un codice ISO 639-1 (**`en`**) | la lingua in cui trailhead **scrive** la sua prosa GitHub e le descrizioni dei commit, disaccoppiata dalla lingua in cui conversa |
|
|
297
|
-
| `models.{plan,execute,research,review,debug}` | un id modello completo **versionato** (**inherit session**) | quale modello esegue ogni attività; `plan` ed `execute` sono sempre impostati separatamente |
|
|
299
|
+
| `models.{plan,execute,research,review,debug,verify,codebase-map}` | un id modello completo **versionato** (**inherit session**) | quale modello esegue ogni attività; `plan` ed `execute` sono sempre impostati separatamente |
|
|
298
300
|
| `design` | **`disk`** \| `claude.ai/design` \| `stitch` | dove vanno i mockup UI: HTML usa-e-getta locale, claude.ai/design via l'MCP ufficiale Claude Design di Anthropic, o Google Stitch (cross-host, via l'MCP Stitch) |
|
|
299
301
|
| `design.surface` | **`canvas`** \| `design-system` | sotto `claude.ai/design` o `stitch`, quale superficie: un progetto canvas (default) o un progetto design-system via `/design-sync` |
|
|
300
302
|
| `design.approval` | **`explicit`** \| `auto` | attendi l'approvazione del mockup prima del codice UI, o procedi senza bloccare |
|
|
@@ -304,11 +306,12 @@ Il passo **📊 statusline** offre di installare la status bar di trailhead per
|
|
|
304
306
|
| `plan_review` | **`off`** \| `on` \| CLI list | invia i PLAN di `build` ad AI CLI esterne (Gemini, Codex, …) per un secondo parere e converge sulle loro obiezioni |
|
|
305
307
|
| `plan_review.rounds` | integer (**`2`**) | numero massimo di round converge-and-re-review |
|
|
306
308
|
| `code_review.rounds` | integer (**`2`**) | numero massimo di round fix-and-re-review in Code review prima del checkpoint HITL di confine |
|
|
309
|
+
| `effort` | **`standard`** \| `lean` | quanto lavora il motore `build`/`bug`/`quick` su una modifica: `standard` esegue ogni step, `lean` fa una triage di dimensione sulle modifiche piccole e offre di saltare quelli pesanti (plan, TDD, cross-AI review, code review completa, verify goal-backward) |
|
|
307
310
|
|
|
308
311
|
**Models.** Ogni chiave esegue la sua attività come **subagent** sul modello che nomini, così l'intera suddivisione per-attività si applica dentro una singola sessione di lavoro, qualunque modello quella sessione esegua:
|
|
309
312
|
|
|
310
313
|
- `plan` ed `execute` sono i passi del motore build/bug. Execute è anch'esso un subagent, e committa su `main`.
|
|
311
|
-
- `research`, `review`, `debug`, e il fan-out della codebase-map girano ciascuno sulla propria chiave.
|
|
314
|
+
- `research`, `review`, `debug`, `verify` (il controllo goal-backward in Verify), e il fan-out della codebase-map girano ciascuno sulla propria chiave.
|
|
312
315
|
|
|
313
316
|
Scegli `plan` ed `execute` separatamente, ciascuno per **id completo versionato** (mai un `opus` o `sonnet` nudo). Una chiave non impostata, o uguale al modello della sessione, gira semplicemente inline.
|
|
314
317
|
|
package/README.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
+

|
|
4
|
+
|
|
3
5
|
# Trailhead
|
|
4
6
|
|
|
5
7
|
🇬🇧 **English** · 🇮🇹 [Italiano](README.it.md)
|
|
6
8
|
|
|
7
|
-
[](https://www.npmjs.com/package/@marcomigozzi/trailhead) [](LICENSE)   
|
|
9
|
+
[](https://trailhead.marcomigozzi.it) [](https://www.npmjs.com/package/@marcomigozzi/trailhead) [](LICENSE)   
|
|
8
10
|
|
|
9
11
|
</div>
|
|
10
12
|
|
|
@@ -24,7 +26,7 @@ Two approaches to agent-driven project work each nail one half of the problem:
|
|
|
24
26
|
|
|
25
27
|
Working across both, one pattern kept recurring: **use a Wayfinder-style map and onboarding to decide the shape of the work, then a GSD-style engine to build each piece, without ever leaving the issue tracker.** `trailhead` is that pattern made into a set of cohesion-clustered skills over a shared core, its own self-contained take on both ideas rather than a wrapper around either.
|
|
26
28
|
|
|
27
|
-
> **trailhead is self-contained
|
|
29
|
+
> **trailhead is self-contained** in the sense that it has **zero external-plugin dependencies**: trailhead-owned agents and the host's built-ins are fine, and no other skill or plugin is required. Wayfinder and GSD are *inspiration*, not dependencies. Every technique those systems package as a separate skill (grilling, TDD, systematic debugging, codebase mapping, code review) is **built in here as an inline protocol**. trailhead's core invokes no other skill; each engine technique dispatches to **trailhead's own `trailhead-*` agent** (`trailhead-executor`, `trailhead-plan`, `trailhead-research`, `trailhead-code-review`, `trailhead-debug`, plus `trailhead-fix` and `trailhead-codebase-map`), authored once as a committed `agents/trailhead-*.md` file and **projected to each host** (Codex gets `~/.codex/agents/trailhead-*.toml` generated from that same source). These agents are **thin** (identity + tool policy + a config-driven model, with the technique protocol injected at spawn) and run on the host's native subagent toolkit (Claude Code's built-in `Agent` tool, or Codex's `multi_agent` toolkit). The only thing to install is trailhead itself. There is one opt-in exception: hosted mockup mode. When enabled, `claude.ai/design` builds the mockup on claude.ai/design (Claude only): by default a canvas (the built-in `/design` skill published as an Artifact, or a hand-made web-app project, not MCP-driven) or, for a shared component library, a design-system project via DesignSync (the `claude-design` MCP plus the `/design-sync` skill, the only MCP-driven surface). A third mode, `stitch`, uses the Google Stitch MCP and works on **both Claude Code and Codex**, so it's the cross-host hosted option. If a hosted mode's MCP isn't connected, it offers a one-time install and falls back to local-disk mockups if you decline; on Codex, where `claude.ai/design` isn't available, it offers to switch to `stitch` before falling to local disk.
|
|
28
30
|
|
|
29
31
|
The design choices that fall out of it:
|
|
30
32
|
|
|
@@ -73,7 +75,7 @@ On **Codex CLI** (`--codex`) it projects a native Codex skill invoked as **`$tra
|
|
|
73
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).
|
|
74
76
|
|
|
75
77
|
### After installing
|
|
76
|
-
Restart or reload your agent so the commands register, then run **`/trailhead`** to start (smart entry), or `/trailhead:new "<idea>"` to chart a map. **On Codex** the surface is `$trailhead` instead: run **`$trailhead`** for smart entry, `$trailhead new "<idea>"` to chart, or a per-verb `$trailhead-<verb>` skill. Note: once installed, the **commit guard hook runs on every `git commit`** (enforcing Conventional Commits and blocking `Co-Authored-By`); disable the plugin's hooks in settings if you don't want that. Either way trailhead is self-contained: no
|
|
78
|
+
Restart or reload your agent so the commands register, then run **`/trailhead`** to start (smart entry), or `/trailhead:new "<idea>"` to chart a map. **On Codex** the surface is `$trailhead` instead: run **`$trailhead`** for smart entry, `$trailhead new "<idea>"` to chart, or a per-verb `$trailhead-<verb>` skill. Note: once installed, the **commit guard hook runs on every `git commit`** (enforcing Conventional Commits and blocking `Co-Authored-By`); disable the plugin's hooks in settings if you don't want that. Either way trailhead is self-contained: no external skill or plugin is required, no preflight, no version drift.
|
|
77
79
|
|
|
78
80
|
---
|
|
79
81
|
|
|
@@ -294,7 +296,7 @@ The **📊 statusline** step offers to install trailhead's Claude Code status ba
|
|
|
294
296
|
| Key | Values (default **bold**) | Effect |
|
|
295
297
|
|---|---|---|
|
|
296
298
|
| `ticket.language` | an ISO 639-1 code (**`en`**) | the language trailhead **writes** its GitHub prose & commit descriptions in, decoupled from the language it converses in |
|
|
297
|
-
| `models.{plan,execute,research,review,debug}` | a full **versioned** model id (**inherit session**) | which model runs each activity; `plan` and `execute` are always set separately |
|
|
299
|
+
| `models.{plan,execute,research,review,debug,verify,codebase-map}` | a full **versioned** model id (**inherit session**) | which model runs each activity; `plan` and `execute` are always set separately |
|
|
298
300
|
| `design` | **`disk`** \| `claude.ai/design` \| `stitch` | where UI mockups go: local throwaway HTML, claude.ai/design via Anthropic's official Claude Design MCP, or Google Stitch (cross-host, via the Stitch MCP) |
|
|
299
301
|
| `design.surface` | **`canvas`** \| `design-system` | under `claude.ai/design` or `stitch`, which surface: a canvas project (default) or a design-system project via `/design-sync` |
|
|
300
302
|
| `design.approval` | **`explicit`** \| `auto` | wait for mockup approval before UI code, or proceed without blocking |
|
|
@@ -304,11 +306,12 @@ The **📊 statusline** step offers to install trailhead's Claude Code status ba
|
|
|
304
306
|
| `plan_review` | **`off`** \| `on` \| CLI list | send `build` PLANs to external AI CLIs (Gemini, Codex, …) for a second opinion and converge on their concerns |
|
|
305
307
|
| `plan_review.rounds` | integer (**`2`**) | max converge-and-re-review rounds |
|
|
306
308
|
| `code_review.rounds` | integer (**`2`**) | max fix-and-re-review rounds in Code review before its boundary HITL checkpoint |
|
|
309
|
+
| `effort` | **`standard`** \| `lean` | how hard the `build`/`bug`/`quick` engine works a change: `standard` runs every step, `lean` size-triages small changes and offers to skip the heavy ones (plan pass, TDD, cross-AI review, full code review, goal-backward verify) |
|
|
307
310
|
|
|
308
311
|
**Models.** Each key runs its activity as a **subagent** on the model you name, so the whole per-activity split applies within a single work session, whatever model that session runs on:
|
|
309
312
|
|
|
310
313
|
- `plan` and `execute` are the build/bug engine's steps. Execute is a subagent too, and it commits to `main`.
|
|
311
|
-
- `research`, `review`, `debug`, and the codebase-map fan-out each run on their own key.
|
|
314
|
+
- `research`, `review`, `debug`, `verify` (the goal-backward Verify check), and the codebase-map fan-out each run on their own key.
|
|
312
315
|
|
|
313
316
|
Choose `plan` and `execute` separately, each by **full versioned id** (never a bare `opus` or `sonnet`). A key that is unset, or equal to the session model, simply runs inline.
|
|
314
317
|
|
|
@@ -101,7 +101,7 @@ Codex has a subagent toolkit: the multi_agent tools (\`spawn_agent\`, \`send_inp
|
|
|
101
101
|
|
|
102
102
|
**Never surrender the session to one open-ended \`wait_agent\`; poll instead.** \`wait_agent\` blocks until the subagent returns, so a single unconditional \`wait_agent\` on a long or stalling subagent freezes the whole session with nothing to check: this is the review / plan-review hang. Collect every delegated subagent through a **bounded wait in a poll loop**, not one open-ended blocking call. Give \`wait_agent\` a short timeout (30 to 60s); on each timeout, report a one-line "still running" progress note and loop again, so the session stays responsive and can be checked or interrupted between polls. The two **review** steps, **Code review** and **Cross-AI plan review**, always run this way: backgrounding and polling them (never a blind \`wait_agent\`) is the engine's hard cross-host default, not merely the case where it matters most. They are also the longest quiet activities in the cycle (a full-diff adversarial review, or an external CLI) and the ones most often seen to hang. If your Codex build's \`wait_agent\` exposes no timeout, poll the agent's status/output at intervals instead of blocking, or run the review as a **background job** and check back on it at intervals; the rule is the same either way. When several subagents run in parallel, poll them as a set, a bounded wait cycling across the outstanding handles, reporting which are still running.
|
|
103
103
|
|
|
104
|
-
**Per-technique
|
|
104
|
+
**Per-technique agent registry (\`agent_type\` dispatch).** The installer always projects all 8 committed trailhead agents as \`~/.codex/agents/trailhead-<technique>.toml\` (one per technique) and enables \`features.multi_agent_v2\`, so Codex always exposes a per-technique agent registry. A TOML whose technique has a \`models.codex.<key>\` pin set carries that OpenAI model (plus its reasoning effort); every other TOML — an unpinned keyed technique, or the 1 keyless agent — is pin-less and its subagent inherits the session model. Detect the registry at runtime by introspecting the visible \`spawn_agent\` tool schema (GSD-style, never a config read): if its parameters expose an \`agent_type\` field, spawn each delegated activity as \`spawn_agent(agent_type="trailhead-<technique>", reasoning_effort=…)\` (\`trailhead-plan\` for the Plan step, \`trailhead-execute\` for Execute, likewise research/review/debug/verify and \`trailhead-codebase-map\` for the Codebase-map step, plus the 1 keyless \`trailhead-fix\` for the Fix step), which routes it onto that technique's pinned model when one is set, or the session model otherwise. If the \`spawn_agent\` schema exposes NO \`agent_type\` field at all (a Codex build with only base multi_agent v1), spawn with base \`spawn_agent\` and the subagents inherit the one session model. Trust the tool surface you actually see and degrade gracefully: \`multi_agent_v2\` is still under development upstream, so v1 stays a supported runtime.
|
|
105
105
|
|
|
106
106
|
\`config.models.*\` (Claude model ids) still cannot take effect here regardless: Codex's subagent model registry is OpenAI-only, so a Claude id cannot run and those keys collapse to the session model. The honoured namespace on Codex is \`models.codex.*\` (above); the one-time models-collapse notice still applies while a stray \`models.*\` is set and \`models.codex.*\` is empty.
|
|
107
107
|
|
|
@@ -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 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 and install-guard as \`PreToolUse\` (matcher \`Bash\`) hooks that can veto the command before it runs, the injection-scanner as a \`PostToolUse\` (matcher \`Bash\`) advisory, and check-update as a \`SessionStart\` hook. 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.
|
|
@@ -165,47 +165,56 @@ policy:
|
|
|
165
165
|
`;
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
-
// ---
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}),
|
|
177
|
-
execute: Object.freeze({
|
|
178
|
-
description: 'trailhead execute subagent: implements a PLAN with atomic commits',
|
|
179
|
-
developerInstructions: "You are trailhead's execute subagent on Codex. Implement the approved PLAN for a trailhead build ticket with atomic conventional commits, each carrying a Refs: #<n> trailer for the ticket, following the repo's conventions. Follow the Execute step in the trailhead skill at ~/.codex/skills/trailhead.",
|
|
180
|
-
}),
|
|
181
|
-
research: Object.freeze({
|
|
182
|
-
description: 'trailhead research subagent: gathers a decision-ready fact from primary sources',
|
|
183
|
-
developerInstructions: "You are trailhead's research subagent on Codex. Gather the decision-ready fact a ticket waits on from primary sources, per the Research technique in the trailhead skill at ~/.codex/skills/trailhead. Record findings with citations; change no product code beyond the research artifact.",
|
|
184
|
-
}),
|
|
185
|
-
review: Object.freeze({
|
|
186
|
-
description: "trailhead code-review subagent: adversarially reviews a ticket's diff",
|
|
187
|
-
developerInstructions: "You are trailhead's code-review subagent on Codex. Review the ticket's diff adversarially on the trailhead Code review technique's axes, verifying each finding before reporting, per the trailhead skill at ~/.codex/skills/trailhead. Do not defer to a convention or a settled-decision memory.",
|
|
188
|
-
}),
|
|
189
|
-
debug: Object.freeze({
|
|
190
|
-
description: "trailhead debug subagent: finds a defect's root cause by the scientific method",
|
|
191
|
-
developerInstructions: "You are trailhead's debug subagent on Codex. Find the root cause of the ticket's defect by the scientific method (reproduce, localise, falsifiable hypotheses, confirm, verify), per the Debug technique in the trailhead skill at ~/.codex/skills/trailhead.",
|
|
192
|
-
}),
|
|
168
|
+
// --- CODEX_AGENT_NAME_ALIASES / codexTechniqueKey ---------------------------
|
|
169
|
+
// The 8 committed agent sources (plugins/trailhead/agents/trailhead-*.md) are
|
|
170
|
+
// keyed by their trailhead-<name>.md filename stem. Two of those stems don't
|
|
171
|
+
// match the models.codex.<key> pin namespace, so alias them; every other
|
|
172
|
+
// agent's key is just its name with the trailhead- prefix stripped.
|
|
173
|
+
const CODEX_AGENT_NAME_ALIASES = Object.freeze({
|
|
174
|
+
'trailhead-executor': 'execute',
|
|
175
|
+
'trailhead-code-review': 'review',
|
|
193
176
|
});
|
|
194
177
|
|
|
178
|
+
function codexTechniqueKey(name) {
|
|
179
|
+
return CODEX_AGENT_NAME_ALIASES[name] || String(name).replace(/^trailhead-/, '');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// --- MODEL_KEYS --------------------------------------------------------------
|
|
183
|
+
// The 7 pinnable technique keys, mirroring the models.codex.* namespace. The
|
|
184
|
+
// other committed agent (fix) has no pin key and is always
|
|
185
|
+
// projected pin-less (session model).
|
|
186
|
+
const MODEL_KEYS = new Set(['plan', 'execute', 'research', 'review', 'debug', 'verify', 'codebase-map']);
|
|
187
|
+
|
|
195
188
|
// --- codexAgentToml ---------------------------------------------------------
|
|
196
|
-
// Render one Codex custom-agent TOML.
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
//
|
|
189
|
+
// Render one Codex custom-agent TOML. When `model` is empty/absent, the TOML
|
|
190
|
+
// is pin-less: no `model =` line (and so no model_reasoning_effort either),
|
|
191
|
+
// so the subagent inherits the session model. When `model` is present,
|
|
192
|
+
// model_reasoning_effort is emitted only when effort is a non-empty string.
|
|
193
|
+
// developer_instructions is a TOML multi-line literal block ('''...'''); a
|
|
194
|
+
// stray ''' inside the text (none of ours has one, but be safe) is
|
|
195
|
+
// neutralised so it can't close the block early. The basic-string fields
|
|
196
|
+
// (name/description/model/effort) are escaped so a stray " or \ in a source
|
|
197
|
+
// value (description comes from the agent .md frontmatter) can't break the TOML.
|
|
198
|
+
function tomlBasicString(s) {
|
|
199
|
+
return String(s)
|
|
200
|
+
.replace(/\\/g, '\\\\')
|
|
201
|
+
.replace(/"/g, '\\"')
|
|
202
|
+
.replace(/\n/g, '\\n')
|
|
203
|
+
.replace(/\r/g, '\\r')
|
|
204
|
+
.replace(/\t/g, '\\t');
|
|
205
|
+
}
|
|
206
|
+
|
|
200
207
|
function codexAgentToml({ name, description, developerInstructions, model, effort }) {
|
|
201
208
|
const safeInstructions = String(developerInstructions).split("'''").join("''");
|
|
202
209
|
const lines = [
|
|
203
|
-
`name = "${name}"`,
|
|
204
|
-
`description = "${description}"`,
|
|
205
|
-
`model = "${model}"`,
|
|
210
|
+
`name = "${tomlBasicString(name)}"`,
|
|
211
|
+
`description = "${tomlBasicString(description)}"`,
|
|
206
212
|
];
|
|
207
|
-
if (typeof
|
|
208
|
-
lines.push(`
|
|
213
|
+
if (typeof model === 'string' && model.trim() !== '') {
|
|
214
|
+
lines.push(`model = "${tomlBasicString(model)}"`);
|
|
215
|
+
if (typeof effort === 'string' && effort.trim() !== '') {
|
|
216
|
+
lines.push(`model_reasoning_effort = "${tomlBasicString(effort)}"`);
|
|
217
|
+
}
|
|
209
218
|
}
|
|
210
219
|
lines.push(`developer_instructions = '''\n${safeInstructions}\n'''`);
|
|
211
220
|
return lines.join('\n') + '\n';
|
|
@@ -214,43 +223,54 @@ function codexAgentToml({ name, description, developerInstructions, model, effor
|
|
|
214
223
|
// --- codexAgentTomlPlan ------------------------------------------------------
|
|
215
224
|
// Given the resolved models.codex config (a plain object like
|
|
216
225
|
// { plan: "gpt-5.6-terra", execute: { model: "gpt-5.6-sol", effort: "high" } },
|
|
217
|
-
// or null/undefined/{})
|
|
218
|
-
//
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
|
|
226
|
+
// or null/undefined/{}) and `agentDefs` (the array from the installer's
|
|
227
|
+
// readAgentDefs(): every committed plugins/trailhead/agents/trailhead-*.md,
|
|
228
|
+
// each { name, description, tools, body }), return the set of
|
|
229
|
+
// trailhead-<technique>.toml writes the installer should make: ONE per agent,
|
|
230
|
+
// uniformly (all 8 project, always). A keyed agent (plan/execute/research/
|
|
231
|
+
// review/debug/verify/codebase-map) carries its models.codex.<key> pin when set, else is
|
|
232
|
+
// pin-less; the 1 keyless agent (fix) is always pin-less.
|
|
233
|
+
// Iterates agentDefs in the given order (readAgentDefs sorts by name) so the
|
|
234
|
+
// write order is deterministic.
|
|
235
|
+
function codexAgentTomlPlan(codexHome, codexModels, agentDefs) {
|
|
222
236
|
const models = codexModels && typeof codexModels === 'object' ? codexModels : {};
|
|
223
237
|
const dir = codexLayout(codexHome).codexAgentsDir;
|
|
238
|
+
const defs = Array.isArray(agentDefs) ? agentDefs : [];
|
|
224
239
|
const writes = [];
|
|
225
240
|
|
|
226
|
-
for (const
|
|
227
|
-
const
|
|
228
|
-
|
|
241
|
+
for (const def of defs) {
|
|
242
|
+
const key = codexTechniqueKey(def.name);
|
|
243
|
+
const name = `trailhead-${key}`;
|
|
244
|
+
const filePath = path.join(dir, `${name}.toml`);
|
|
229
245
|
|
|
230
246
|
let model = null;
|
|
231
247
|
let effort = null;
|
|
232
|
-
if (
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if (
|
|
237
|
-
|
|
238
|
-
|
|
248
|
+
if (MODEL_KEYS.has(key) && models[key] != null) {
|
|
249
|
+
const raw = models[key];
|
|
250
|
+
if (typeof raw === 'string') {
|
|
251
|
+
if (raw.trim() !== '') model = raw;
|
|
252
|
+
} else if (typeof raw === 'object') {
|
|
253
|
+
const m = typeof raw.model === 'string' ? raw.model : '';
|
|
254
|
+
if (m.trim() !== '') {
|
|
255
|
+
model = m;
|
|
256
|
+
effort = typeof raw.effort === 'string' ? raw.effort : null;
|
|
257
|
+
}
|
|
239
258
|
}
|
|
240
259
|
}
|
|
241
|
-
if (!model) continue;
|
|
242
260
|
|
|
243
|
-
const
|
|
244
|
-
const
|
|
245
|
-
|
|
261
|
+
const lead = 'You are running on Codex CLI; the trailhead skill lives at ~/.codex/skills/trailhead.\n\n';
|
|
262
|
+
const developerInstructions = lead + convertToCodex(String(def.body || '')).trim();
|
|
263
|
+
|
|
246
264
|
const content = codexAgentToml({
|
|
247
265
|
name,
|
|
248
|
-
description
|
|
249
|
-
|
|
266
|
+
// Route the description through convertToCodex too (like the body), and
|
|
267
|
+
// default a missing frontmatter description to '' rather than "undefined".
|
|
268
|
+
description: convertToCodex(String(def.description || '')),
|
|
269
|
+
developerInstructions,
|
|
250
270
|
model,
|
|
251
271
|
effort,
|
|
252
272
|
});
|
|
253
|
-
writes.push({ technique, name, path: filePath, content });
|
|
273
|
+
writes.push({ technique: key, name, path: filePath, content });
|
|
254
274
|
}
|
|
255
275
|
|
|
256
276
|
return { writes };
|
|
@@ -345,6 +365,7 @@ function codexHookEntries(hooksScriptsDir) {
|
|
|
345
365
|
return [
|
|
346
366
|
{ event: 'PreToolUse', matcher: 'Bash', command: cmd('trailhead-commit-guard.js') },
|
|
347
367
|
{ event: 'PreToolUse', matcher: 'Bash', command: cmd('trailhead-secret-guard.js') },
|
|
368
|
+
{ event: 'PreToolUse', matcher: 'Bash', command: cmd('trailhead-install-guard.js') },
|
|
348
369
|
{ event: 'PostToolUse', matcher: 'Bash', command: cmd('trailhead-issue-injection-scanner.js') },
|
|
349
370
|
{ event: 'SessionStart', matcher: '', command: cmd('trailhead-check-update.js') },
|
|
350
371
|
];
|
|
@@ -143,11 +143,13 @@ 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 5 entries', Array.isArray(entries) && entries.length === 5);
|
|
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
|
+
ok('codexHookEntries: install-guard is PreToolUse/Bash', entries.some((e) =>
|
|
152
|
+
e.event === 'PreToolUse' && e.matcher === 'Bash' && e.command.includes('trailhead-install-guard.js')));
|
|
151
153
|
ok('codexHookEntries: injection-scanner is PostToolUse/Bash', entries.some((e) =>
|
|
152
154
|
e.event === 'PostToolUse' && e.matcher === 'Bash' && e.command.includes('trailhead-issue-injection-scanner.js')));
|
|
153
155
|
ok('codexHookEntries: check-update is SessionStart', entries.some((e) =>
|
|
@@ -198,24 +200,110 @@ ok('codexAgentToml without effort omits model_reasoning_effort', (() => {
|
|
|
198
200
|
});
|
|
199
201
|
return out.includes('model = "gpt-5.6-terra"') && !out.includes('model_reasoning_effort');
|
|
200
202
|
})());
|
|
203
|
+
ok('codexAgentToml with no model emits a pin-less TOML (no model line, no effort line, keeps the rest)', (() => {
|
|
204
|
+
const out = codexAgentToml({
|
|
205
|
+
name: 'trailhead-fix',
|
|
206
|
+
description: 'desc',
|
|
207
|
+
developerInstructions: 'fix the thing',
|
|
208
|
+
model: null,
|
|
209
|
+
effort: null,
|
|
210
|
+
});
|
|
211
|
+
return out.includes('name = "trailhead-fix"') &&
|
|
212
|
+
out.includes('description = "desc"') &&
|
|
213
|
+
!/^model = /m.test(out) &&
|
|
214
|
+
!out.includes('model_reasoning_effort') &&
|
|
215
|
+
out.includes("developer_instructions = '''") &&
|
|
216
|
+
out.includes('fix the thing');
|
|
217
|
+
})());
|
|
218
|
+
|
|
219
|
+
ok('codexAgentToml escapes a " and \\ in name/description so the TOML stays valid', (() => {
|
|
220
|
+
const out = codexAgentToml({
|
|
221
|
+
name: 'trailhead-plan',
|
|
222
|
+
description: 'has a "quote" and a back\\slash',
|
|
223
|
+
developerInstructions: 'body',
|
|
224
|
+
model: null,
|
|
225
|
+
effort: null,
|
|
226
|
+
});
|
|
227
|
+
// The emitted basic string must carry escaped forms, never a raw " that
|
|
228
|
+
// would prematurely close the TOML string.
|
|
229
|
+
return out.includes('description = "has a \\"quote\\" and a back\\\\slash"');
|
|
230
|
+
})());
|
|
201
231
|
|
|
202
232
|
// --- codexAgentTomlPlan ---
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
233
|
+
// 7-entry stub agentDefs mirroring the real plugins/trailhead/agents/*.md
|
|
234
|
+
// sources: 6 keyed techniques (plan/execute/research/review/debug/codebase-map,
|
|
235
|
+
// whose filenames derive from trailhead-executor -> execute and
|
|
236
|
+
// trailhead-code-review -> review) + 1 always-keyless agent (fix).
|
|
237
|
+
const STUB_AGENT_DEFS = [
|
|
238
|
+
{ name: 'trailhead-plan', description: 'plan desc', tools: 'Read', body: 'Plan body.' },
|
|
239
|
+
{ name: 'trailhead-executor', description: 'execute desc', tools: 'Read, Write', body: 'Execute body.' },
|
|
240
|
+
{ name: 'trailhead-research', description: 'research desc', tools: 'Read', body: 'Research body.' },
|
|
241
|
+
{ name: 'trailhead-code-review', description: 'review desc', tools: 'Read', body: 'Review body.' },
|
|
242
|
+
{ name: 'trailhead-debug', description: 'debug desc', tools: 'Read', body: 'Debug body.' },
|
|
243
|
+
{ name: 'trailhead-fix', description: 'fix desc', tools: 'Read, Write', body: 'Fix body.' },
|
|
244
|
+
{ name: 'trailhead-codebase-map', description: 'map desc', tools: 'Read', body: 'Map body.' },
|
|
245
|
+
];
|
|
246
|
+
|
|
247
|
+
ok('codexAgentTomlPlan: 7 agentDefs -> 7 writes with only the pinned key carrying a model', (() => {
|
|
248
|
+
const plan = codexAgentTomlPlan('/c', { execute: 'gpt-5.6-terra' }, STUB_AGENT_DEFS);
|
|
249
|
+
if (plan.writes.length !== 7) return false;
|
|
250
|
+
const byName = Object.fromEntries(plan.writes.map((w) => [w.name, w]));
|
|
251
|
+
const execute = byName['trailhead-execute'];
|
|
252
|
+
if (!execute || execute.path !== '/c/agents/trailhead-execute.toml') return false;
|
|
253
|
+
if (!execute.content.includes('model = "gpt-5.6-terra"')) return false;
|
|
254
|
+
const others = plan.writes.filter((w) => w.name !== 'trailhead-execute');
|
|
255
|
+
return others.length === 6 && others.every((w) => !/^model = /m.test(w.content));
|
|
256
|
+
})());
|
|
257
|
+
|
|
258
|
+
ok('codexAgentTomlPlan: keyless trailhead-fix stays pin-less; trailhead-codebase-map now pins on codebase-map', (() => {
|
|
259
|
+
const plan = codexAgentTomlPlan('/c', { fix: 'gpt-5.6-terra', 'codebase-map': 'gpt-5.6-terra' }, STUB_AGENT_DEFS);
|
|
260
|
+
const byName = Object.fromEntries(plan.writes.map((w) => [w.name, w]));
|
|
261
|
+
const fix = byName['trailhead-fix'];
|
|
262
|
+
const map = byName['trailhead-codebase-map'];
|
|
263
|
+
return fix && map &&
|
|
264
|
+
fix.path === '/c/agents/trailhead-fix.toml' && !/^model = /m.test(fix.content) &&
|
|
265
|
+
map.path === '/c/agents/trailhead-codebase-map.toml' && map.content.includes('model = "gpt-5.6-terra"');
|
|
266
|
+
})());
|
|
267
|
+
|
|
268
|
+
ok('codexAgentTomlPlan: empty models object -> 7 pin-less writes (not zero)', (() => {
|
|
269
|
+
const plan = codexAgentTomlPlan('/c', {}, STUB_AGENT_DEFS);
|
|
270
|
+
return plan.writes.length === 7 && plan.writes.every((w) => !/^model = /m.test(w.content));
|
|
271
|
+
})());
|
|
272
|
+
|
|
273
|
+
ok('codexAgentTomlPlan: null models -> 7 pin-less writes (not zero)', (() => {
|
|
274
|
+
const plan = codexAgentTomlPlan('/c', null, STUB_AGENT_DEFS);
|
|
275
|
+
return plan.writes.length === 7 && plan.writes.every((w) => !/^model = /m.test(w.content));
|
|
276
|
+
})());
|
|
277
|
+
|
|
278
|
+
ok('codexAgentTomlPlan: object model value on the execute key carries model + model_reasoning_effort', (() => {
|
|
279
|
+
const plan = codexAgentTomlPlan('/c', { execute: { model: 'gpt-5.6-sol', effort: 'high' } }, STUB_AGENT_DEFS);
|
|
280
|
+
const execute = plan.writes.find((w) => w.name === 'trailhead-execute');
|
|
281
|
+
return execute.content.includes('model = "gpt-5.6-sol"') && execute.content.includes('model_reasoning_effort = "high"');
|
|
209
282
|
})());
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const
|
|
213
|
-
|
|
283
|
+
|
|
284
|
+
ok('codexAgentTomlPlan: a keyed key whose object value has no model stays pin-less', (() => {
|
|
285
|
+
const plan = codexAgentTomlPlan('/c', { execute: { effort: 'high' } }, STUB_AGENT_DEFS);
|
|
286
|
+
const execute = plan.writes.find((w) => w.name === 'trailhead-execute');
|
|
287
|
+
return plan.writes.length === 7 && !/^model = /m.test(execute.content) && !execute.content.includes('model_reasoning_effort');
|
|
288
|
+
})());
|
|
289
|
+
|
|
290
|
+
ok('codexAgentTomlPlan: description is routed through convertToCodex (host paths rewritten)', (() => {
|
|
291
|
+
const defs = [{ name: 'trailhead-plan', description: 'see ~/.claude/skills', tools: 'Read', body: 'b' }];
|
|
292
|
+
const plan = codexAgentTomlPlan('/c', {}, defs);
|
|
293
|
+
return plan.writes[0].content.includes('description = "see ~/.codex/skills"');
|
|
294
|
+
})());
|
|
295
|
+
|
|
296
|
+
ok('codexAgentTomlPlan: a missing description renders as an empty string, not "undefined"', (() => {
|
|
297
|
+
const defs = [{ name: 'trailhead-plan', tools: 'Read', body: 'b' }];
|
|
298
|
+
const plan = codexAgentTomlPlan('/c', {}, defs);
|
|
299
|
+
return plan.writes[0].content.includes('description = ""') && !plan.writes[0].content.includes('description = "undefined"');
|
|
300
|
+
})());
|
|
301
|
+
|
|
302
|
+
ok('codexAgentTomlPlan: empty/undefined agentDefs -> no writes (installer then leaves multi_agent_v2 off)', (() => {
|
|
303
|
+
return codexAgentTomlPlan('/c', {}, []).writes.length === 0 &&
|
|
304
|
+
codexAgentTomlPlan('/c', {}).writes.length === 0 &&
|
|
305
|
+
codexAgentTomlPlan('/c', {}, null).writes.length === 0;
|
|
214
306
|
})());
|
|
215
|
-
ok('codexAgentTomlPlan: empty object -> no writes', codexAgentTomlPlan('/c', {}).writes.length === 0);
|
|
216
|
-
ok('codexAgentTomlPlan: null -> no writes', codexAgentTomlPlan('/c', null).writes.length === 0);
|
|
217
|
-
ok('codexAgentTomlPlan: unknown key skipped', codexAgentTomlPlan('/c', { bogus: 'x' }).writes.length === 0);
|
|
218
|
-
ok('codexAgentTomlPlan: object value with no model is skipped', codexAgentTomlPlan('/c', { execute: { effort: 'high' } }).writes.length === 0);
|
|
219
307
|
|
|
220
308
|
// --- enableCodexMultiAgentV2Feature ---
|
|
221
309
|
ok('enableCodexMultiAgentV2Feature: dotted form already true -> null',
|