@marcomigozzi/trailhead 0.4.0 → 0.5.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 +19 -14
- package/README.md +20 -15
- package/bin/lib/codex-projection.js +23 -2
- package/bin/lib/codex-projection.test.js +14 -0
- package/bin/trailhead.js +99 -36
- package/bin/trailhead.test.js +130 -5
- package/package.json +1 -1
- package/plugins/trailhead/.claude-plugin/plugin.json +7 -2
- package/plugins/trailhead/commands/adopt.md +1 -1
- package/plugins/trailhead/commands/bug.md +1 -1
- package/plugins/trailhead/commands/config.md +1 -1
- package/plugins/trailhead/commands/dashboard.md +1 -1
- package/plugins/trailhead/commands/grill.md +1 -1
- package/plugins/trailhead/commands/idea.md +1 -1
- package/plugins/trailhead/commands/inbox.md +1 -1
- package/plugins/trailhead/commands/map.md +1 -1
- package/plugins/trailhead/commands/new.md +1 -1
- package/plugins/trailhead/commands/note.md +1 -1
- package/plugins/trailhead/commands/pause.md +1 -1
- package/plugins/trailhead/commands/quick.md +3 -1
- package/plugins/trailhead/commands/resume.md +1 -1
- package/plugins/trailhead/commands/seed.md +1 -1
- package/plugins/trailhead/commands/split.md +1 -1
- package/plugins/trailhead/commands/ticket.md +1 -1
- package/plugins/trailhead/commands/todo.md +1 -1
- package/plugins/trailhead/commands/update.md +1 -1
- package/plugins/trailhead/commands/whiteboard.md +1 -1
- package/plugins/trailhead/commands/work.md +1 -1
- package/plugins/trailhead/hooks/trailhead-commit-guard.js +1 -1
- package/plugins/trailhead/hooks/trailhead-secret-guard.js +19 -4
- package/plugins/trailhead/hooks/trailhead-secret-guard.test.js +41 -1
- package/plugins/trailhead/skills/{trailhead/references/configuration.md → _shared/configuration-reference.md} +9 -3
- package/plugins/trailhead/skills/_shared/configuration.md +8 -0
- package/plugins/trailhead/skills/_shared/multi-map.md +27 -0
- package/plugins/trailhead/skills/_shared/out-of-scope.md +11 -0
- package/plugins/trailhead/skills/_shared/principles.md +8 -0
- package/plugins/trailhead/skills/_shared/session-handoff.md +22 -0
- package/plugins/trailhead/skills/_shared/substrate-commands.md +159 -0
- package/plugins/trailhead/skills/_shared/substrate.md +35 -0
- package/plugins/trailhead/skills/{trailhead/references → _shared}/teamwork.md +5 -4
- package/plugins/trailhead/skills/{trailhead/references → _shared}/techniques/acceptance-testing.md +3 -2
- package/plugins/trailhead/skills/{trailhead/references → _shared}/techniques/code-review.md +5 -1
- package/plugins/trailhead/skills/{trailhead/references → _shared}/techniques/plan-review.md +4 -7
- package/plugins/trailhead/skills/_shared/techniques/prototype.md +36 -0
- package/plugins/trailhead/skills/_shared/techniques.md +20 -0
- package/plugins/trailhead/skills/_shared/ticket-anatomy.md +32 -0
- package/plugins/trailhead/skills/_shared/ticket-language.md +3 -0
- package/plugins/trailhead/skills/trailhead/SKILL.md +25 -452
- package/plugins/trailhead/skills/trailhead-capture/SKILL.md +28 -0
- package/plugins/trailhead/skills/{trailhead → trailhead-capture}/references/capture.md +4 -4
- package/plugins/trailhead/skills/trailhead-chart/SKILL.md +51 -0
- package/plugins/trailhead/skills/trailhead-chart/references/charting.md +24 -0
- package/plugins/trailhead/skills/{trailhead → trailhead-chart}/references/inbox.md +1 -1
- package/plugins/trailhead/skills/trailhead-manage/SKILL.md +29 -0
- package/plugins/trailhead/skills/trailhead-view/SKILL.md +112 -0
- package/plugins/trailhead/skills/trailhead-work/SKILL.md +65 -0
- package/plugins/trailhead/skills/trailhead-work/references/ticket-engines.md +52 -0
- package/plugins/trailhead/skills/trailhead/references/techniques/prototype.md +0 -18
- /package/plugins/trailhead/skills/{trailhead/references → _shared}/techniques/codebase-map.md +0 -0
- /package/plugins/trailhead/skills/{trailhead/references → _shared}/techniques/debug.md +0 -0
- /package/plugins/trailhead/skills/{trailhead/references → _shared}/techniques/domain-vocabulary.md +0 -0
- /package/plugins/trailhead/skills/{trailhead/references → _shared}/techniques/grilling.md +0 -0
- /package/plugins/trailhead/skills/{trailhead/references → _shared}/techniques/research.md +0 -0
- /package/plugins/trailhead/skills/{trailhead/references → _shared}/techniques/tdd.md +0 -0
- /package/plugins/trailhead/skills/{trailhead → trailhead-manage}/references/updating.md +0 -0
package/README.it.md
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# Trailhead
|
|
2
4
|
|
|
3
5
|
🇬🇧 [English](README.md) · 🇮🇹 **Italiano**
|
|
4
6
|
|
|
7
|
+
[](https://www.npmjs.com/package/@marcomigozzi/trailhead) [](LICENSE)   
|
|
8
|
+
|
|
9
|
+
</div>
|
|
10
|
+
|
|
5
11
|
**Avvia e conduci grandi progetti come una mappa di ticket su GitHub Issues, risolvendone uno alla volta finché la via verso la destination non è chiara.**
|
|
6
12
|
|
|
7
|
-
`trailhead` è una skill orchestratrice per [Claude Code](https://docs.claude.com/en/docs/claude-code). Dà a un'idea grande e nebbiosa un punto da cui partire (un *trailhead*) e un modo disciplinato per portarla fino a un risultato funzionante, con l'intero piano che vive sul tuo issue tracker invece che in file locali sparsi.
|
|
13
|
+
`trailhead` è una skill orchestratrice per coding agent, che gira su [Claude Code](https://docs.claude.com/en/docs/claude-code) e [Codex CLI](https://developers.openai.com/codex/cli) da un'unica sorgente. Dà a un'idea grande e nebbiosa un punto da cui partire (un *trailhead*) e un modo disciplinato per portarla fino a un risultato funzionante, con l'intero piano che vive sul tuo issue tracker invece che in file locali sparsi.
|
|
8
14
|
|
|
9
15
|
---
|
|
10
16
|
|
|
@@ -18,7 +24,7 @@ Due approcci al lavoro di progetto guidato dagli agenti risolvono ciascuno una m
|
|
|
18
24
|
|
|
19
25
|
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.
|
|
20
26
|
|
|
21
|
-
> **trailhead è self-contained.** 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; i suoi subagent (research, codebase-map, review) usano i subagent nativi 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:
|
|
27
|
+
> **trailhead è self-contained.** 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; i suoi subagent (research, codebase-map, review) usano i subagent nativi 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.
|
|
22
28
|
|
|
23
29
|
Le scelte di design che ne derivano:
|
|
24
30
|
|
|
@@ -62,7 +68,7 @@ npx @marcomigozzi/trailhead --dir=<path> # punta a una config dir specifica
|
|
|
62
68
|
```
|
|
63
69
|
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.
|
|
64
70
|
|
|
65
|
-
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 quattro 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
|
|
71
|
+
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 quattro 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.
|
|
66
72
|
|
|
67
73
|
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).
|
|
68
74
|
|
|
@@ -234,7 +240,7 @@ Due regole pratiche: i build ticket **non fanno mai auto-grill**: su un'ambiguit
|
|
|
234
240
|
Tutto ciò di cui la mappa ha bisogno è espresso come label GitHub, così lo stato è interrogabile nella UI del tracker:
|
|
235
241
|
|
|
236
242
|
- **Strutturali:** `trailhead:map`, `trailhead:ticket`
|
|
237
|
-
- **Anchor con scope di repo (uno ciascuno per repo, pinnati):** `trailhead:codebase` 🧱 (la codebase map distillata), `trailhead:conventions` 📜 (il modo di lavorare), `trailhead:dashboard` 📊 (l'indice pinnato di mappe + whiteboard + conteggi)
|
|
243
|
+
- **Anchor con scope di repo (uno ciascuno per repo, pinnati):** `trailhead:codebase` 🧱 (la codebase map distillata), `trailhead:conventions` 📜 (il modo di lavorare), `trailhead:dashboard` 📊 (l'indice pinnato di mappe + whiteboard + conteggi); ogni titolo porta la sua icona (come il 🗺 della mappa) così gli anchor pinnati si distinguono a colpo d'occhio
|
|
238
244
|
- **Type (una per ticket):** 🧭 `trailhead:decision` · 🔬 `research` · 🎨 `prototype` · 🔨 `build` · 🐛 `bug` · 🔧 `task`
|
|
239
245
|
- **State:** `trailhead:blocked` (ha un blocker aperto) · `seed` (parcheggiato su un trigger) · `out-of-scope` (chiuso, oltre la destination) · `superseded` (chiuso, splittato in figli)
|
|
240
246
|
- **Container:** `trailhead:whiteboard` (un ticket sciolto, senza mappa, fuori dalla frontier di ogni mappa, sulla frontier propria della whiteboard)
|
|
@@ -289,13 +295,15 @@ Il passo **📊 statusline** offre di installare la status bar di trailhead per
|
|
|
289
295
|
|---|---|---|
|
|
290
296
|
| `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 |
|
|
291
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 |
|
|
292
|
-
| `design` | **`disk`** \| `claude.ai/design` | dove vanno i mockup UI: HTML usa-e-getta locale, o
|
|
298
|
+
| `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
|
+
| `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` |
|
|
293
300
|
| `design.approval` | **`explicit`** \| `auto` | attendi l'approvazione del mockup prima del codice UI, o procedi senza bloccare |
|
|
294
301
|
| `tdd` | **`seams`** \| `on` \| `off` | come il motore `build` testa |
|
|
295
302
|
| `acceptance.browser` | **`auto`** \| `on` \| `off` | guida il browser in Verify, o ti accompagna in una UAT conversazionale (passo-passo in chat, non una checklist da fare da solo) |
|
|
296
303
|
| `testing.webapp` / `testing.url` | bool / URL | è browser-drivabile, e dove |
|
|
297
304
|
| `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 |
|
|
298
305
|
| `plan_review.rounds` | integer (**`2`**) | numero massimo di round converge-and-re-review |
|
|
306
|
+
| `code_review.rounds` | integer (**`2`**) | numero massimo di round fix-and-re-review in Code review prima del checkpoint HITL di confine |
|
|
299
307
|
|
|
300
308
|
**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:
|
|
301
309
|
|
|
@@ -312,17 +320,14 @@ Esempio di file di config (la stessa forma funziona per il `.trailhead/config.js
|
|
|
312
320
|
{ "ticket": { "language": "en" }, "models": { "plan": "claude-opus-4-8", "execute": "claude-sonnet-5" }, "tdd": "seams", "acceptance": { "browser": "auto" } }
|
|
313
321
|
```
|
|
314
322
|
|
|
315
|
-
**Design mockups.** `design: disk` (il default) deposita un mockup HTML statico usa-e-getta accanto al codice e lo collega dal ticket. `design: claude.ai/design` invece
|
|
316
|
-
|
|
317
|
-
Alla prima schermata UI, trailhead chiede quale design system usare. Puoi:
|
|
323
|
+
**Design mockups.** `design: disk` (il default) deposita un mockup HTML statico usa-e-getta accanto al codice e lo collega dal ticket. `design: claude.ai/design` invece costruisce il mockup su claude.ai/design (canvas tramite la skill integrata `/design` o un progetto creato a mano nella web app; design-system tramite l'**MCP ufficiale Claude Design** di Anthropic, `claude mcp add --scope user --transport http claude-design https://api.anthropic.com/v1/design/mcp`, accedi con `/design-login`), dove lo affini visivamente, e `design: stitch` usa l'**MCP Google Stitch** (`claude mcp add --scope user --transport http stitch https://stitch.googleapis.com/mcp`), il generatore UI di Google basato su Gemini che gira sia su **Claude Code sia su Codex** (l'opzione hosted cross-host). Entrambe onorano `design.surface`:
|
|
318
324
|
|
|
319
|
-
-
|
|
320
|
-
-
|
|
321
|
-
- **crearne uno nuovo** (ti chiede il nome).
|
|
325
|
+
- **`canvas`** (default): una schermata singola a cui reagire, modificabile ed esportabile come prototipo live. Sotto `claude.ai/design` è la skill integrata `/design` (pubblicata come Artifact) o un progetto claude.ai/design creato a mano nella web app; sotto `stitch` è un progetto Stitch.
|
|
326
|
+
- **`design-system`**: un progetto design-system guidato da **DesignSync** (la skill `/design-sync` più l'MCP `claude-design`), per una libreria di componenti condivisa tenuta in sync.
|
|
322
327
|
|
|
323
|
-
Fa il caching dell'id scelto in `design.project
|
|
328
|
+
Alla prima schermata UI, trailhead si aggancia a una superficie: per un progetto **Stitch** o un **design-system claude.ai/design** può **sceglierne uno esistente**, **incollare un URL**, oppure **crearne uno nuovo** (ti chiede il nome); per una **canvas claude.ai/design** non esiste la creazione via MCP, quindi usa la skill `/design` (un **Artifact**) o un progetto web-app di cui incolli l'URL `/p/<id>`. Fa il caching dell'id/URL scelto in `design.project`; un progetto già in cache da prima mantiene la superficie `design-system` a meno che tu non imposti `design.surface`, così i setup esistenti non cambiano. Ogni schermata viene aggiunta lì, e la sua URL è collegata dal ticket. Una volta che approvi, trailhead **ri-fetcha** il design corrente (nel caso tu l'abbia modificato live) prima di scrivere qualsiasi codice UI.
|
|
324
329
|
|
|
325
|
-
|
|
330
|
+
Se l'MCP della modalità hosted non è connesso, trailhead **offre di installarlo una volta** (`claude mcp add ...`); se rifiuti, ripiega sul disco locale. (La superficie **canvas** di `claude.ai/design` non richiede MCP, quindi per lei non viene offerto nulla; solo la superficie **design-system** ha l'offerta di installazione.) Su Codex `claude.ai/design` non è disponibile, quindi trailhead offre di passare a `stitch` (cross-host) prima di ripiegare sul disco locale. `design.approval` decide se il build attende il tuo esplicito via libera (`explicit`), o procede subito dopo aver mostrato il mockup (`auto`); non tocca l'offerta di installazione una tantum, che compare in entrambi i casi.
|
|
326
331
|
|
|
327
332
|
---
|
|
328
333
|
|
package/README.md
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# Trailhead
|
|
2
4
|
|
|
3
5
|
🇬🇧 **English** · 🇮🇹 [Italiano](README.it.md)
|
|
4
6
|
|
|
7
|
+
[](https://www.npmjs.com/package/@marcomigozzi/trailhead) [](LICENSE)   
|
|
8
|
+
|
|
9
|
+
</div>
|
|
10
|
+
|
|
5
11
|
**Start and drive large projects as a map of tickets on GitHub Issues, resolving one at a time until the way to the destination is clear.**
|
|
6
12
|
|
|
7
|
-
`trailhead` is an orchestrator skill for [Claude Code](https://docs.claude.com/en/docs/claude-code). It gives a big, foggy idea a place to begin (a *trailhead*) and a disciplined way to walk it to a working result, with the whole plan living on your issue tracker instead of in scattered local files.
|
|
13
|
+
`trailhead` is an orchestrator skill for coding agents, running on [Claude Code](https://docs.claude.com/en/docs/claude-code) and [Codex CLI](https://developers.openai.com/codex/cli) from a single source. It gives a big, foggy idea a place to begin (a *trailhead*) and a disciplined way to walk it to a working result, with the whole plan living on your issue tracker instead of in scattered local files.
|
|
8
14
|
|
|
9
15
|
---
|
|
10
16
|
|
|
@@ -16,9 +22,9 @@ Two approaches to agent-driven project work each nail one half of the problem:
|
|
|
16
22
|
|
|
17
23
|
- **GSD** (the [open-gsd](https://github.com/open-gsd/gsd-core) workflow system) is brilliant at *doing*. Its `discuss → plan → execute → verify` cycle, atomic commits, and zero-friction capture make the actual construction reliable and reviewable. But its state lives in a local `.planning/` tree, separate from where the team can see it.
|
|
18
24
|
|
|
19
|
-
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
|
|
25
|
+
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.
|
|
20
26
|
|
|
21
|
-
> **trailhead is self-contained.** 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; its subagents (research, codebase-map, review) use the host's native subagents (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:
|
|
27
|
+
> **trailhead is self-contained.** 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; its subagents (research, codebase-map, review) use the host's native subagents (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.
|
|
22
28
|
|
|
23
29
|
The design choices that fall out of it:
|
|
24
30
|
|
|
@@ -62,7 +68,7 @@ npx @marcomigozzi/trailhead --dir=<path> # target a specific config dir
|
|
|
62
68
|
```
|
|
63
69
|
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.
|
|
64
70
|
|
|
65
|
-
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 four 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
|
|
71
|
+
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 four 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.
|
|
66
72
|
|
|
67
73
|
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).
|
|
68
74
|
|
|
@@ -234,7 +240,7 @@ Two rules of thumb: build tickets **never auto-grill**: on blocking ambiguity th
|
|
|
234
240
|
Everything the map needs is expressed as GitHub labels, so state is queryable in the tracker UI:
|
|
235
241
|
|
|
236
242
|
- **Structural:** `trailhead:map`, `trailhead:ticket`
|
|
237
|
-
- **Repo-scoped anchors (one each per repo, pinned):** `trailhead:codebase` 🧱 (the distilled codebase map), `trailhead:conventions` 📜 (the way of working), `trailhead:dashboard` 📊 (the pinned index of maps + whiteboard + counts)
|
|
243
|
+
- **Repo-scoped anchors (one each per repo, pinned):** `trailhead:codebase` 🧱 (the distilled codebase map), `trailhead:conventions` 📜 (the way of working), `trailhead:dashboard` 📊 (the pinned index of maps + whiteboard + counts); each title carries its icon (like the map's 🗺) so the pinned anchors stand apart at a glance
|
|
238
244
|
- **Type (one per ticket):** 🧭 `trailhead:decision` · 🔬 `research` · 🎨 `prototype` · 🔨 `build` · 🐛 `bug` · 🔧 `task`
|
|
239
245
|
- **State:** `trailhead:blocked` (has an open blocker) · `seed` (parked on a trigger) · `out-of-scope` (closed, beyond the destination) · `superseded` (closed, split into children)
|
|
240
246
|
- **Container:** `trailhead:whiteboard` (a loose, map-less ticket, off every map's frontier, on the whiteboard's own)
|
|
@@ -289,13 +295,15 @@ The **📊 statusline** step offers to install trailhead's Claude Code status ba
|
|
|
289
295
|
|---|---|---|
|
|
290
296
|
| `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 |
|
|
291
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 |
|
|
292
|
-
| `design` | **`disk`** \| `claude.ai/design` | where UI mockups go: local throwaway HTML, or
|
|
298
|
+
| `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
|
+
| `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` |
|
|
293
300
|
| `design.approval` | **`explicit`** \| `auto` | wait for mockup approval before UI code, or proceed without blocking |
|
|
294
301
|
| `tdd` | **`seams`** \| `on` \| `off` | how the `build` engine tests |
|
|
295
302
|
| `acceptance.browser` | **`auto`** \| `on` \| `off` | drive the browser in Verify, or walk you through a conversational UAT (step by step in chat, not a checklist to self-serve) |
|
|
296
303
|
| `testing.webapp` / `testing.url` | bool / URL | is it browser-drivable, and where |
|
|
297
304
|
| `plan_review` | **`off`** \| `on` \| CLI list | send `build` PLANs to external AI CLIs (Gemini, Codex, …) for a second opinion and converge on their concerns |
|
|
298
305
|
| `plan_review.rounds` | integer (**`2`**) | max converge-and-re-review rounds |
|
|
306
|
+
| `code_review.rounds` | integer (**`2`**) | max fix-and-re-review rounds in Code review before its boundary HITL checkpoint |
|
|
299
307
|
|
|
300
308
|
**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:
|
|
301
309
|
|
|
@@ -312,17 +320,14 @@ Example config file (the same shape works for the project `.trailhead/config.jso
|
|
|
312
320
|
{ "ticket": { "language": "en" }, "models": { "plan": "claude-opus-4-8", "execute": "claude-sonnet-5" }, "tdd": "seams", "acceptance": { "browser": "auto" } }
|
|
313
321
|
```
|
|
314
322
|
|
|
315
|
-
**Design mockups.** `design: disk` (the default) drops a throwaway static HTML mockup next to the code and links it from the ticket. `design: claude.ai/design` instead
|
|
316
|
-
|
|
317
|
-
On the first UI screen, trailhead asks which design system to use. You can:
|
|
323
|
+
**Design mockups.** `design: disk` (the default) drops a throwaway static HTML mockup next to the code and links it from the ticket. `design: claude.ai/design` instead builds the mockup on claude.ai/design (canvas via the built-in `/design` skill or a hand-made web-app project; design-system via Anthropic's **official Claude Design MCP**, `claude mcp add --scope user --transport http claude-design https://api.anthropic.com/v1/design/mcp`, sign in with `/design-login`), where you refine it visually, and `design: stitch` uses the **Google Stitch MCP** (`claude mcp add --scope user --transport http stitch https://stitch.googleapis.com/mcp`), Google's Gemini-powered UI generator that runs on **both Claude Code and Codex** (the cross-host hosted option). Both honour `design.surface`:
|
|
318
324
|
|
|
319
|
-
-
|
|
320
|
-
-
|
|
321
|
-
- **create a new one** (it asks you for the name).
|
|
325
|
+
- **`canvas`** (default): a one-off screen to react to, editable and exportable as a live prototype. Under `claude.ai/design` it's the built-in `/design` skill (published as an Artifact) or a claude.ai/design project made by hand in the web app; under `stitch` it's a Stitch project.
|
|
326
|
+
- **`design-system`**: a design-system project driven by **DesignSync** (the `/design-sync` skill plus the `claude-design` MCP), for a shared component library kept in sync.
|
|
322
327
|
|
|
323
|
-
It caches the chosen id in `design.project
|
|
328
|
+
On the first UI screen, trailhead attaches to a surface: for a **Stitch** project or a **claude.ai/design design-system**, it can **pick an existing one**, **paste a URL**, or **create a new one** (it asks you for the name); for a **claude.ai/design canvas** there is no create-via-MCP, so it uses the `/design` skill (an **Artifact**) or a web-app project whose `/p/<id>` URL you **paste**. It caches the chosen id/URL in `design.project`; a project already cached from before keeps the `design-system` surface unless you set `design.surface`, so existing setups don't change. Each screen is added there, and its URL is linked from the ticket. Once you approve, trailhead **re-fetches** the current design (in case you edited it live) before writing any UI code.
|
|
324
329
|
|
|
325
|
-
|
|
330
|
+
If a hosted mode's MCP isn't connected, trailhead **offers to install it once** (`claude mcp add ...`); decline and it falls back to local disk. (The `claude.ai/design` **canvas** surface needs no MCP, so nothing is offered for it; only the **design-system** surface has an install offer.) On Codex, `claude.ai/design` isn't available, so trailhead offers to switch to `stitch` (cross-host) before falling to local disk. `design.approval` decides whether the build waits for your explicit go-ahead (`explicit`), or proceeds right after surfacing the mockup (`auto`); it doesn't affect the one-time install offer, which prompts either way.
|
|
326
331
|
|
|
327
332
|
---
|
|
328
333
|
|
|
@@ -90,8 +90,8 @@ This trailhead engine is running on **Codex CLI**, not Claude Code. The installe
|
|
|
90
90
|
## A. Commands
|
|
91
91
|
trailhead is a native Codex skill. Invoke it as \`$trailhead <verb>\` (e.g. \`$trailhead work\`, \`$trailhead new "idea"\`); bare \`$trailhead\` is smart entry. Never \`/trailhead:<verb>\` (that is the Claude Code surface). Whatever you type after the verb is the arguments. For discoverability the installer also projects one thin skill per verb (\`$trailhead-work\`, \`$trailhead-bug\`, …) that just delegates to \`$trailhead <verb>\`, so the verbs surface in the \`$\`-menu; \`$trailhead <verb>\` stays the canonical form and this SKILL.md the single source of truth. (Codex custom prompts under \`~/.codex/prompts/\` are deliberately NOT used: they do not surface as slash commands.)
|
|
92
92
|
|
|
93
|
-
## B. This IS the skill
|
|
94
|
-
Codex loaded this SKILL.md because you invoked \`$trailhead
|
|
93
|
+
## B. This IS the skill; the engine is split across sibling skill dirs
|
|
94
|
+
Codex loaded this SKILL.md because you invoked \`$trailhead\` (or a \`$trailhead-<verb>\` / \`$trailhead-<cluster>\` entry); there is no separate Skill tool to call. The engine is split across sibling skill dirs under \`~/.codex/skills/\`: the dispatcher (\`trailhead/\`), five cluster skills (\`trailhead-chart/\`, \`trailhead-work/\`, \`trailhead-view/\`, \`trailhead-capture/\`, \`trailhead-manage/\`), and the shared core (\`_shared/\`, no SKILL.md). Read every referenced file **relative to the SKILL.md you are currently in**: a \`references/*.md\` from this skill's own dir, and the shared core (\`../_shared/*.md\`, or \`../../_shared/*.md\` from a \`references/\` file) from the sibling \`_shared/\` dir. Where the dispatcher's routing says "call the Skill tool with skill name \`trailhead-<cluster>\` and arguments \`<verb> <rest>\`", there is no Skill tool on Codex: **Read \`../trailhead-<cluster>/SKILL.md\` (the sibling dir) and follow it**, treating \`<verb> <rest>\` as its arguments.
|
|
95
95
|
|
|
96
96
|
## C. AskUserQuestion -> request_user_input
|
|
97
97
|
Where the engine uses \`AskUserQuestion\`, use Codex's \`request_user_input\`: map \`header\`->\`header\`, \`question\`->\`question\`, each option \`{label, description}\`; generate an \`id\` from the header (lowercase, spaces->underscores). Codex has no \`multiSelect\`: present sequential single-selects, or a numbered freeform list and ask for comma-separated numbers. If \`request_user_input\` is unavailable, present the choices as a plain-text numbered list and STOP for the user's reply; do not silently pick a default and proceed.
|
|
@@ -99,6 +99,8 @@ Where the engine uses \`AskUserQuestion\`, use Codex's \`request_user_input\`: m
|
|
|
99
99
|
## D. Subagents (native multi_agent)
|
|
100
100
|
Codex has a subagent toolkit: the multi_agent tools (\`spawn_agent\`, \`send_input\`, \`wait_agent\`, \`resume_agent\`, \`close_agent\`), stable and on by default on supported Codex (the installer gates on the floor that guarantees them). Where the engine delegates to a subagent (research, codebase-map, review, plan, execute, debug), spawn a real Codex subagent with these tools and the technique's own protocol (each technique carries its full protocol in-prompt, so no specialised agent is needed; pass the ticket's \`Scope:\` when relevant). The Claude-only Explore-vs-general-purpose split does not apply: Codex has one subagent kind. A subagent runs as a FRESH session that receives a task prompt, not this conversation; run independent ones in parallel (e.g. the codebase-map readers) and \`wait_agent\` on them.
|
|
101
101
|
|
|
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
|
+
|
|
102
104
|
**Per-technique model pins (\`agent_type\` dispatch).** When \`models.codex.*\` is set, the installer projects one \`~/.codex/agents/trailhead-<technique>.toml\` per key (that technique's OpenAI model, plus its reasoning effort) and enables \`features.multi_agent_v2\`, so Codex exposes a per-technique agent registry. Detect that 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, \`multi_agent_v2\` is live, so 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), which routes it onto its pinned model. If the \`spawn_agent\` schema exposes NO \`agent_type\` field (only base multi_agent v1 is present, or no \`models.codex.*\` pin was projected), 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.
|
|
103
105
|
|
|
104
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.
|
|
@@ -111,6 +113,9 @@ Codex has a hook bus, so trailhead's guardrails run as **real Codex hooks**, not
|
|
|
111
113
|
|
|
112
114
|
## G. Design / UI mockups -> local disk
|
|
113
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.
|
|
116
|
+
|
|
117
|
+
## H. Shell timeouts & background jobs (plan-review's external CLIs)
|
|
118
|
+
The **Cross-AI plan review** technique invokes each external reviewer CLI (\`codex exec\`, \`gemini -p\`, \`qwen -p\`, …) through Claude Code's **Bash tool**, and tells the engine to beat that tool's ~2-minute default cut by setting its \`timeout\` parameter to the 10-minute max, or launching the call with \`run_in_background: true\` and collecting it later. Those are Claude Bash-tool knobs with no Codex equivalent, so map the *intent* onto Codex's own shell/exec tool. Reviews never block the session (the engine's hard cross-host default), so **the default is to background the reviewers** and collect them by polling, never block on them: launch each with a trailing \`&\` (or your shell tool's background form), **redirect its stdin from \`/dev/null\` and its output to a file** (\`codex exec … "<prompt>" < /dev/null > out.txt 2>&1 &\`), and poll/collect when it finishes, so several reviewers run in parallel instead of serially eating the wall-clock; or wrap each reviewer invocation in a §D subagent so the long-running call sits behind the multi_agent poll loop. Where a reviewer must run synchronously instead, give that call its **own long timeout** (the \`timeout_ms\` / equivalent on Codex's shell/exec tool, set to several minutes, never the short default), so a reviewer that legitimately runs for minutes is not cut mid-review. Either way plan-review's own rule is unchanged: never rely on a short default timeout, retry once at the wide margin, then drop a still-slow reviewer rather than let it wedge the build. **Close the reviewer's stdin when backgrounding it** (\`< /dev/null\`): \`codex exec\` reads additional prompt input from stdin and blocks on EOF, so a backgrounded reviewer that inherits an open or attached stdin sits idle at "Reading additional input from stdin..." (0% CPU) forever instead of running (passing the prompt as an argument does not exempt it). Guard the poll loop too: a reviewer pinned at 0% CPU with no output past a threshold is stuck, so surface it and drop it after the one retry rather than reporting "still running" indefinitely. This is the external-CLI **shell** path; §D covers the separate **subagent** (\`wait_agent\`) path, and the two are distinct (an external reviewer CLI is a plain shell call, not a spawned Codex subagent).
|
|
114
119
|
</codex_skill_adapter>`;
|
|
115
120
|
}
|
|
116
121
|
|
|
@@ -145,6 +150,21 @@ policy:
|
|
|
145
150
|
`;
|
|
146
151
|
}
|
|
147
152
|
|
|
153
|
+
// --- codexClusterAgentsYaml ------------------------------------------------
|
|
154
|
+
// UI metadata for a projected cluster skill (trailhead-chart, trailhead-work,
|
|
155
|
+
// ...): a display name + explicit-only invocation, so a cluster never
|
|
156
|
+
// auto-loads into an unrelated turn. Mirrors codexAgentsYaml's shape.
|
|
157
|
+
function codexClusterAgentsYaml(cluster) {
|
|
158
|
+
const short = String(cluster).replace(/^trailhead-/, '');
|
|
159
|
+
return `interface:
|
|
160
|
+
display_name: ${yamlQuote(`Trailhead: ${short}`)}
|
|
161
|
+
short_description: ${yamlQuote(`trailhead ${short} cluster`)}
|
|
162
|
+
default_prompt: ${yamlQuote(`Run $${cluster}`)}
|
|
163
|
+
policy:
|
|
164
|
+
allow_implicit_invocation: false
|
|
165
|
+
`;
|
|
166
|
+
}
|
|
167
|
+
|
|
148
168
|
// --- CODEX_AGENT_TECHNIQUES ------------------------------------------------
|
|
149
169
|
// Metadata for the 5 trailhead activities that can get a per-technique Codex
|
|
150
170
|
// custom-agent TOML: a short UI description and the developer_instructions
|
|
@@ -388,6 +408,7 @@ module.exports = {
|
|
|
388
408
|
codexSkillAdapterHeader,
|
|
389
409
|
injectCodexAdapterHeader,
|
|
390
410
|
codexAgentsYaml,
|
|
411
|
+
codexClusterAgentsYaml,
|
|
391
412
|
codexAgentToml,
|
|
392
413
|
codexAgentTomlPlan,
|
|
393
414
|
codexVerbSkillContent,
|
|
@@ -81,8 +81,22 @@ ok('codexSkillAdapterHeader §D pins the exact agent_type dispatch call shape',
|
|
|
81
81
|
header.includes('spawn_agent(agent_type="trailhead-<technique>"'));
|
|
82
82
|
ok('codexSkillAdapterHeader §D keeps the base spawn_agent v1 fallback path',
|
|
83
83
|
header.includes('base multi_agent v1') && header.includes('inherit the one session model'));
|
|
84
|
+
ok('codexSkillAdapterHeader §D warns against a single blocking wait_agent with a poll loop',
|
|
85
|
+
header.includes('bounded wait') && header.includes('poll'));
|
|
86
|
+
ok('codexSkillAdapterHeader §D offers a background-job fallback for a stalling review',
|
|
87
|
+
header.includes('background job'));
|
|
88
|
+
ok('codexSkillAdapterHeader §D makes background+poll the hard cross-host default for reviews',
|
|
89
|
+
header.includes('always run this way') && header.includes('hard cross-host default'));
|
|
84
90
|
ok('codexSkillAdapterHeader §F mentions real Codex hooks', header.includes('real Codex hooks'));
|
|
85
91
|
ok('codexSkillAdapterHeader §F no longer says Codex has no hook bus', !header.includes('Codex has no hook bus'));
|
|
92
|
+
ok('codexSkillAdapterHeader §H maps plan-review external-CLI Bash timeout/background onto Codex shell',
|
|
93
|
+
header.includes('## H.') && header.includes('Cross-AI plan review') && header.includes('run_in_background') && header.includes('shell/exec'));
|
|
94
|
+
ok('codexSkillAdapterHeader §H distinguishes the external-CLI shell path from the §D subagent path',
|
|
95
|
+
header.includes('external-CLI') && header.includes('not a spawned Codex subagent'));
|
|
96
|
+
ok('codexSkillAdapterHeader §H makes backgrounding reviewers the default',
|
|
97
|
+
header.includes('the default is to background the reviewers'));
|
|
98
|
+
ok('codexSkillAdapterHeader §H closes reviewer stdin to avoid the EOF hang',
|
|
99
|
+
header.includes('/dev/null') && header.toLowerCase().includes('stdin'));
|
|
86
100
|
|
|
87
101
|
// --- codexVerbSkillContent (#46) ---
|
|
88
102
|
ok('codexVerbSkillContent: SKILL.md frontmatter is at byte 0 with name+description', (() => {
|
package/bin/trailhead.js
CHANGED
|
@@ -23,10 +23,41 @@ const readline = require('readline');
|
|
|
23
23
|
const { spawnSync } = require('child_process');
|
|
24
24
|
|
|
25
25
|
const { getHost, configDirFor, codexVersionGate } = require('./lib/host-descriptor.js');
|
|
26
|
-
const { codexLayout, convertToCodex, injectCodexAdapterHeader, codexAgentsYaml, codexHookEntries, enableCodexHooksFeature, codexAgentTomlPlan, codexVerbSkillPlan, enableCodexMultiAgentV2Feature } = require('./lib/codex-projection.js');
|
|
26
|
+
const { codexLayout, convertToCodex, injectCodexAdapterHeader, codexAgentsYaml, codexClusterAgentsYaml, codexHookEntries, enableCodexHooksFeature, codexAgentTomlPlan, codexVerbSkillPlan, enableCodexMultiAgentV2Feature } = require('./lib/codex-projection.js');
|
|
27
27
|
|
|
28
28
|
const PKG = path.resolve(__dirname, '..');
|
|
29
29
|
const SRC = path.join(PKG, 'plugins', 'trailhead');
|
|
30
|
+
|
|
31
|
+
// Enumerate the engine skill dirs shipped under plugins/trailhead/skills: the
|
|
32
|
+
// dispatcher, the cluster skills, and the shared core. A subdir qualifies when
|
|
33
|
+
// it is `_shared` (the shared core, no SKILL.md) or contains a SKILL.md, so a
|
|
34
|
+
// stray empty dir is skipped and a new cluster is picked up automatically.
|
|
35
|
+
function engineSkillDirs() {
|
|
36
|
+
const skillsRoot = path.join(SRC, 'skills');
|
|
37
|
+
if (!fs.existsSync(skillsRoot)) return [];
|
|
38
|
+
return fs.readdirSync(skillsRoot, { withFileTypes: true })
|
|
39
|
+
.filter((e) => e.isDirectory())
|
|
40
|
+
.map((e) => e.name)
|
|
41
|
+
.filter((name) => name === '_shared' || fs.existsSync(path.join(skillsRoot, name, 'SKILL.md')))
|
|
42
|
+
.sort();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Sweep every trailhead-owned skill dir under a skills root, by name: the
|
|
46
|
+
// dispatcher (`trailhead`), the shared core (`_shared`), and every
|
|
47
|
+
// `trailhead-<cluster|verb>` sibling. Clears a prior layout's stale dirs (an
|
|
48
|
+
// old monolith fallback, a removed cluster, a renamed verb skill) the current
|
|
49
|
+
// install no longer ships, so a re-install or /trailhead:update never leaves a
|
|
50
|
+
// dangling skill. The skills root is shared with other plugins, so only
|
|
51
|
+
// trailhead's own names are touched, never the whole dir.
|
|
52
|
+
function sweepTrailheadSkills(skillsRoot) {
|
|
53
|
+
rmrf(path.join(skillsRoot, 'trailhead'));
|
|
54
|
+
rmrf(path.join(skillsRoot, '_shared'));
|
|
55
|
+
if (!fs.existsSync(skillsRoot)) return;
|
|
56
|
+
for (const f of fs.readdirSync(skillsRoot)) {
|
|
57
|
+
if (f.startsWith('trailhead-')) rmrf(path.join(skillsRoot, f));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
30
61
|
const args = process.argv.slice(2);
|
|
31
62
|
const has = (f) => args.includes(f);
|
|
32
63
|
const useSymlink = has('--symlink');
|
|
@@ -138,7 +169,6 @@ async function resolveHostName() {
|
|
|
138
169
|
// --- Claude adapter (unchanged behaviour) -----------------------------------
|
|
139
170
|
function claudePaths(configDir) {
|
|
140
171
|
return {
|
|
141
|
-
skill: path.join(configDir, 'skills', 'trailhead'),
|
|
142
172
|
commands: path.join(configDir, 'commands', 'trailhead'),
|
|
143
173
|
hooksDir: path.join(configDir, 'hooks'),
|
|
144
174
|
templates: path.join(configDir, 'trailhead', 'templates'),
|
|
@@ -155,18 +185,28 @@ const HOOK_LIB_FILES = ['commit-message-check.js'];
|
|
|
155
185
|
// Copy the hook scripts and their runtime libs into a destination hooks dir
|
|
156
186
|
// (Claude's ~/.claude/hooks or Codex's skills/trailhead/hooks). Shared by both
|
|
157
187
|
// install paths so the lib copy can never drift from the script copy again.
|
|
158
|
-
|
|
188
|
+
// Place the allow-listed hook scripts into destHooksDir. Normal install copies
|
|
189
|
+
// (+chmod so the exec bit is set regardless of the source mode); a --symlink dev
|
|
190
|
+
// install links each file per-file into the package source so hook edits are
|
|
191
|
+
// live too, matching skills/commands. Never symlink the whole hooks/ dir: it is
|
|
192
|
+
// shared with other plugins, so only the curated allowlist is touched. rmrf the
|
|
193
|
+
// dest first so a reinstall over an existing file or symlink never hits EEXIST.
|
|
194
|
+
function copyHookScripts(destHooksDir, { useSymlink = false } = {}) {
|
|
159
195
|
ensure(destHooksDir);
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
196
|
+
const put = (src, dest) => {
|
|
197
|
+
rmrf(dest);
|
|
198
|
+
if (useSymlink) {
|
|
199
|
+
fs.symlinkSync(src, dest, 'file');
|
|
200
|
+
} else {
|
|
201
|
+
fs.copyFileSync(src, dest);
|
|
202
|
+
fs.chmodSync(dest, 0o755);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
for (const f of HOOK_FILES) put(path.join(SRC, 'hooks', f), path.join(destHooksDir, f));
|
|
164
206
|
if (HOOK_LIB_FILES.length) {
|
|
165
207
|
const libDest = path.join(destHooksDir, 'lib');
|
|
166
208
|
ensure(libDest);
|
|
167
|
-
for (const f of HOOK_LIB_FILES)
|
|
168
|
-
fs.copyFileSync(path.join(SRC, 'hooks', 'lib', f), path.join(libDest, f));
|
|
169
|
-
}
|
|
209
|
+
for (const f of HOOK_LIB_FILES) put(path.join(SRC, 'hooks', 'lib', f), path.join(libDest, f));
|
|
170
210
|
}
|
|
171
211
|
}
|
|
172
212
|
|
|
@@ -195,7 +235,10 @@ function stripHook(s, event, substr) {
|
|
|
195
235
|
|
|
196
236
|
function uninstallClaude(configDir) {
|
|
197
237
|
const P = claudePaths(configDir);
|
|
198
|
-
|
|
238
|
+
// Sweep every trailhead-owned skill dir (current set + any stale dir a prior
|
|
239
|
+
// layout left that this package no longer ships), never the shared dir itself.
|
|
240
|
+
sweepTrailheadSkills(path.join(configDir, 'skills'));
|
|
241
|
+
[P.commands, path.join(configDir, 'trailhead')].forEach(rmrf);
|
|
199
242
|
HOOK_FILES.forEach((f) => rmrf(path.join(P.hooksDir, f)));
|
|
200
243
|
// Remove only trailhead's own lib files by name: the hooks/lib dir is shared
|
|
201
244
|
// with other plugins, so never rmrf the whole dir.
|
|
@@ -213,10 +256,21 @@ function installClaude(configDir, { useSymlink }) {
|
|
|
213
256
|
const P = claudePaths(configDir);
|
|
214
257
|
const hookCmd = (name) => `node "${path.join(P.hooksDir, name)}"`;
|
|
215
258
|
|
|
216
|
-
|
|
259
|
+
// Place each engine skill dir as a sibling under configDir/skills so the
|
|
260
|
+
// ../_shared/ and ../trailhead-<cluster>/ relative refs resolve. The skills
|
|
261
|
+
// dir is shared with other plugins: place by name, never touch the whole dir.
|
|
262
|
+
// Migration: sweep any stale trailhead-owned skill dirs from a prior layout
|
|
263
|
+
// (an old monolith, a removed cluster) before laying the current set down, so
|
|
264
|
+
// an update over an old install leaves no dangling skill. The skills dir is
|
|
265
|
+
// shared with other plugins: place by name, never touch the whole dir.
|
|
266
|
+
const skillDirs = engineSkillDirs();
|
|
267
|
+
sweepTrailheadSkills(path.join(configDir, 'skills'));
|
|
268
|
+
for (const name of skillDirs) {
|
|
269
|
+
place(path.join(SRC, 'skills', name), path.join(configDir, 'skills', name), useSymlink);
|
|
270
|
+
}
|
|
217
271
|
place(path.join(SRC, 'commands'), P.commands, useSymlink);
|
|
218
272
|
place(path.join(SRC, 'templates'), P.templates, useSymlink);
|
|
219
|
-
copyHookScripts(P.hooksDir);
|
|
273
|
+
copyHookScripts(P.hooksDir, { useSymlink });
|
|
220
274
|
// version marker: serve al check-update hook per la versione installata (canale npm)
|
|
221
275
|
const pkgVersion = (readJSON(path.join(SRC, '.claude-plugin', 'plugin.json')).version || '').trim();
|
|
222
276
|
if (pkgVersion) {
|
|
@@ -234,7 +288,7 @@ function installClaude(configDir, { useSymlink }) {
|
|
|
234
288
|
writeJSON(P.settings, s);
|
|
235
289
|
|
|
236
290
|
console.log(`✓ trailhead installed for Claude Code → ${configDir}`);
|
|
237
|
-
console.log(`
|
|
291
|
+
console.log(` skills → ${path.join(configDir, 'skills')}/ (${skillDirs.join(', ')})${useSymlink ? ' (symlink)' : ''}`);
|
|
238
292
|
console.log(` commands → ${P.commands} (/trailhead:*)`);
|
|
239
293
|
console.log(` hooks → ${P.hooksDir}/ (${added ? 'registered' : 'already present'} in settings.json)`);
|
|
240
294
|
console.log(` templates → ${P.templates}`);
|
|
@@ -333,7 +387,7 @@ function installCodex(configDir, { useSymlink }) {
|
|
|
333
387
|
const L = codexLayout(configDir);
|
|
334
388
|
|
|
335
389
|
if (useSymlink) {
|
|
336
|
-
console.log('Note: symlink
|
|
390
|
+
console.log('Note: on Codex, --symlink links the verbatim artifacts (hooks, templates) into the package source for live edits; the skill files are text-converted at install, so they are written (projected), not linked.');
|
|
337
391
|
}
|
|
338
392
|
|
|
339
393
|
// Migration: sweep the pre-#25 layout AND the #42 prompt shims that never
|
|
@@ -346,31 +400,44 @@ function installCodex(configDir, { useSymlink }) {
|
|
|
346
400
|
}
|
|
347
401
|
rmrf(L.legacyEngineDir);
|
|
348
402
|
|
|
349
|
-
//
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
403
|
+
// Sweep the whole engine + any prior per-verb skills, then project fresh.
|
|
404
|
+
sweepTrailheadSkills(L.skillsRoot);
|
|
405
|
+
|
|
406
|
+
// Project each engine skill dir (dispatcher + 5 clusters + _shared) as a
|
|
407
|
+
// sibling under skills/, converting text and injecting the adapter header into
|
|
408
|
+
// each top-level SKILL.md. The ../_shared/ relative refs survive conversion and
|
|
409
|
+
// resolve as siblings (decision #54). _shared has no SKILL.md, so no header.
|
|
410
|
+
const engineDirs = engineSkillDirs();
|
|
411
|
+
for (const name of engineDirs) {
|
|
412
|
+
copySkillConverted(path.join(SRC, 'skills', name), path.join(L.skillsRoot, name), true);
|
|
413
|
+
}
|
|
353
414
|
|
|
354
415
|
// Templates (verbatim, never converted), bundled inside the skill dir so
|
|
355
416
|
// ${CLAUDE_PLUGIN_ROOT}/templates resolves to ~/.codex/skills/trailhead/templates.
|
|
356
|
-
|
|
357
|
-
fs.cpSync(path.join(SRC, 'templates'), L.templatesDir, { recursive: true });
|
|
417
|
+
place(path.join(SRC, 'templates'), L.templatesDir, useSymlink);
|
|
358
418
|
|
|
359
419
|
// agents/openai.yaml: UI metadata + explicit-only invocation (no auto-trigger).
|
|
360
420
|
ensure(L.agentsDir);
|
|
361
421
|
fs.writeFileSync(L.agentsYaml, codexAgentsYaml());
|
|
422
|
+
// Each cluster skill also gets explicit-only UI metadata (never auto-invoked).
|
|
423
|
+
for (const name of engineDirs) {
|
|
424
|
+
if (name === 'trailhead' || name === '_shared') continue;
|
|
425
|
+
const dir = path.join(L.skillsRoot, name, 'agents');
|
|
426
|
+
ensure(dir);
|
|
427
|
+
fs.writeFileSync(path.join(dir, 'openai.yaml'), codexClusterAgentsYaml(name));
|
|
428
|
+
}
|
|
362
429
|
|
|
363
430
|
// Per-verb discoverability (#46): one thin Codex skill per verb
|
|
364
431
|
// (skills/trailhead-<verb>/, invocable as $trailhead-<verb>), each delegating
|
|
365
432
|
// to $trailhead <verb>. Codex custom prompts (~/.codex/prompts/) never surface
|
|
366
433
|
// as slash commands, so per-verb skills carry the surface, exactly as GSD does.
|
|
367
|
-
//
|
|
368
|
-
//
|
|
369
|
-
//
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
}
|
|
373
|
-
const verbSkillPlan = codexVerbSkillPlan(configDir,
|
|
434
|
+
// Verbs come from the canonical commands/*.md so the surfaces never drift.
|
|
435
|
+
// Skip any verb whose skill dir would collide with a projected cluster skill
|
|
436
|
+
// (e.g. verb `work` vs the trailhead-work cluster): the cluster skill is its
|
|
437
|
+
// own $trailhead-<name> entry. Compute the skip-set from the projected dirs.
|
|
438
|
+
const clusterDirNames = new Set(engineDirs);
|
|
439
|
+
const verbList = readCommandVerbs().filter((c) => !clusterDirNames.has(`trailhead-${c.verb}`));
|
|
440
|
+
const verbSkillPlan = codexVerbSkillPlan(configDir, verbList);
|
|
374
441
|
for (const d of verbSkillPlan.dirs) ensure(path.join(d, 'agents'));
|
|
375
442
|
for (const w of verbSkillPlan.writes) fs.writeFileSync(w.path, w.content);
|
|
376
443
|
|
|
@@ -379,7 +446,7 @@ function installCodex(configDir, { useSymlink }) {
|
|
|
379
446
|
|
|
380
447
|
// Hooks: copy the 4 guard scripts into the skill dir and register them in
|
|
381
448
|
// ~/.codex/hooks.json (same shape as Claude's settings.json hooks block).
|
|
382
|
-
copyHookScripts(L.hooksScriptsDir);
|
|
449
|
+
copyHookScripts(L.hooksScriptsDir, { useSymlink });
|
|
383
450
|
const h = readJSON(L.hooksJson);
|
|
384
451
|
for (const e of codexHookEntries(L.hooksScriptsDir)) addHook(h, e.event, e.matcher, e.command);
|
|
385
452
|
writeJSON(L.hooksJson, h);
|
|
@@ -443,7 +510,7 @@ function installCodex(configDir, { useSymlink }) {
|
|
|
443
510
|
// edit config.toml, the hooks stay inert until the user sets it by hand, so
|
|
444
511
|
// don't reassure them about a trust review that won't happen yet.
|
|
445
512
|
if (featureManual) {
|
|
446
|
-
console.log(` ⚠ could not edit ${L.configToml} automatically
|
|
513
|
+
console.log(` ⚠ could not edit ${L.configToml} automatically; add \`features.hooks = true\` under [features] by hand, then Codex will ask you to trust trailhead's hooks on next start.`);
|
|
447
514
|
} else {
|
|
448
515
|
console.log('Note: Codex will ask you to trust trailhead\'s hooks on next start (feature `features.hooks`).');
|
|
449
516
|
}
|
|
@@ -454,12 +521,8 @@ function installCodex(configDir, { useSymlink }) {
|
|
|
454
521
|
|
|
455
522
|
function uninstallCodex(configDir) {
|
|
456
523
|
const L = codexLayout(configDir);
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
// never the main skills/trailhead (already removed above; does not match).
|
|
460
|
-
for (const f of (fs.existsSync(L.skillsRoot) ? fs.readdirSync(L.skillsRoot) : [])) {
|
|
461
|
-
if (f.startsWith('trailhead-')) rmrf(path.join(L.skillsRoot, f));
|
|
462
|
-
}
|
|
524
|
+
// Sweep the dispatcher, _shared, the clusters, and the per-verb skills.
|
|
525
|
+
sweepTrailheadSkills(L.skillsRoot);
|
|
463
526
|
// sweep any pre-#25 layout AND the #42 prompt shims from prompts/
|
|
464
527
|
rmrf(path.join(L.promptsDir, 'trailhead.md'));
|
|
465
528
|
for (const f of (fs.existsSync(L.promptsDir) ? fs.readdirSync(L.promptsDir) : [])) {
|