@marcomigozzi/trailhead 0.2.0 → 0.3.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.md +20 -4
- package/package.json +1 -1
- package/plugins/trailhead/.claude-plugin/plugin.json +1 -1
- package/plugins/trailhead/commands/adopt.md +1 -1
- package/plugins/trailhead/commands/dashboard.md +6 -0
- package/plugins/trailhead/commands/quick.md +6 -0
- package/plugins/trailhead/commands/seed.md +1 -1
- package/plugins/trailhead/commands/todo.md +1 -1
- package/plugins/trailhead/commands/whiteboard.md +6 -0
- package/plugins/trailhead/skills/trailhead/SKILL.md +97 -25
- package/plugins/trailhead/skills/trailhead/references/capture.md +4 -0
- package/plugins/trailhead/skills/trailhead/references/techniques/code-review.md +1 -1
- package/plugins/trailhead/skills/trailhead/references/techniques/codebase-map.md +1 -1
- package/plugins/trailhead/skills/trailhead/references/techniques/debug.md +2 -0
- package/plugins/trailhead/skills/trailhead/references/techniques/research.md +1 -1
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Two approaches to agent-driven project work each nail one half of the problem:
|
|
|
14
14
|
|
|
15
15
|
- **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.
|
|
16
16
|
|
|
17
|
-
Working across both, one pattern kept recurring: **use Wayfinder
|
|
17
|
+
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 single skill, its own self-contained take on both ideas rather than a wrapper around either.
|
|
18
18
|
|
|
19
19
|
> **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 Claude Code's built-in `Agent` tool. The only thing to install is trailhead itself. There is one opt-in exception: when enabled, `claude.ai/design` mockup mode pushes to a design-system project via DesignSync (the `claude_design` MCP plus the `/design-sync` skill). If that isn't available, it silently falls back to local-disk mockups.
|
|
20
20
|
|
|
@@ -75,12 +75,13 @@ Each **ticket** is a child issue with a type label and a one-question body. The
|
|
|
75
75
|
|
|
76
76
|
As tickets resolve, the fog clears: questions that were too vague to phrase become sharp enough to ticket, one at a time, until nothing is left to decide or build and the destination is reached.
|
|
77
77
|
|
|
78
|
-
A map is scoped to **one** effort, so knowledge that belongs to the *repo* (not to any single map) lives in
|
|
78
|
+
A map is scoped to **one** effort, so knowledge that belongs to the *repo* (not to any single map) lives in **repo-scoped anchor issues**, created once and shared by every map (each map's Notes just links them, so nothing is stranded when a map finishes):
|
|
79
79
|
|
|
80
80
|
- **`trailhead:codebase`**: the distilled codebase map (architecture, stack, conventions, risks, test/build), written once at adopt and refreshed only on major drift.
|
|
81
81
|
- **`trailhead:conventions`**: the project's **way of working**, readable by everyone: a small machine-read header the engine obeys (`git: main|pr`, `release: command|auto`, `isolation: none|worktree|clone`) over human prose. `/trailhead:adopt` and `:new` ask for it up front. `isolation: worktree` gives each executing ticket its own `git worktree` + branch (concurrent sessions on one clone never share a working tree); `isolation: clone` gives it a dedicated clone instead, for path-bound apps a worktree can't build.
|
|
82
|
+
- **`trailhead:dashboard`**: the pinned index of the whole surface, a link to every open map (with GitHub's native progress bars), the whiteboard, and live counts (inbox, whiteboard frontier). Refreshed when a map is charted or exhausted, and on demand via `/trailhead:dashboard`.
|
|
82
83
|
|
|
83
|
-
|
|
84
|
+
These three fill GitHub's **3 pinned-issue slots**, so a repo's trailhead anchors stay one click away; **maps themselves are never pinned** (they're indexed by the dashboard instead, since the "active" map is per-checkout local state, not a repo-global fact). Project *config* (models, TDD, design…) is separate again, a plain `.trailhead/config.json` file at the repo root, never in an issue.
|
|
84
85
|
|
|
85
86
|
---
|
|
86
87
|
|
|
@@ -157,6 +158,8 @@ Every verb is also a namespaced command (`/trailhead:new`, `/trailhead:work`, `/
|
|
|
157
158
|
| `/trailhead:new [idea]` | chart a new map from a loose idea |
|
|
158
159
|
| `/trailhead:adopt` | adopt an existing project (map the codebase once, then go lean) |
|
|
159
160
|
| `/trailhead:work [ticket]` | work the next frontier ticket, or the one you name |
|
|
161
|
+
| `/trailhead:quick [ticket \| "text"]` | work one ticket whole, off the map: opens a whiteboard ticket from `"text"` (or takes `<n>`), runs the full engine, grills only if needed, never splits |
|
|
162
|
+
| `/trailhead:whiteboard` | show the whiteboard: the loose (map-less) tickets and their frontier |
|
|
160
163
|
| `/trailhead:inbox [issue]` | triage issues opened by others and integrate the good ones into the map |
|
|
161
164
|
| `/trailhead:resume [ticket]` | resume a paused ticket from its `PAUSED` checkpoint |
|
|
162
165
|
| `/trailhead:pause [note]` | checkpoint the ticket in play so anyone can resume it |
|
|
@@ -164,6 +167,7 @@ Every verb is also a namespaced command (`/trailhead:new`, `/trailhead:work`, `/
|
|
|
164
167
|
| `/trailhead:split [ticket]` | split an oversized ticket into children, supersede the original |
|
|
165
168
|
| `/trailhead:grill [topic]` | run a standalone grilling session on a decision/topic |
|
|
166
169
|
| `/trailhead:map` | show the low-res map (destination, decisions, frontier, fog) |
|
|
170
|
+
| `/trailhead:dashboard` | show the repo dashboard: the pinned index of every open map, the whiteboard, and live counts |
|
|
167
171
|
|
|
168
172
|
### Capture: zero friction, one confirmation line, resolves nothing
|
|
169
173
|
|
|
@@ -177,6 +181,17 @@ Every verb is also a namespaced command (`/trailhead:new`, `/trailhead:work`, `/
|
|
|
177
181
|
|
|
178
182
|
The four fog/ticket captures form a spectrum of commitment and timing: **note < idea < seed < todo**.
|
|
179
183
|
|
|
184
|
+
When a map is open, a capture that produces a ticket (`todo`/`bug`/`seed`/a sharp `idea`) asks whether to file it on the **active map** or the **whiteboard**, the home for loose, map-less work that doesn't belong to any map (or isn't worth charting one). With no map open it lands on the whiteboard. Work a whiteboard ticket with **`/trailhead:quick`** (which also opens one from `"text"` and works it in the same sitting), and see them all with `/trailhead:whiteboard`.
|
|
185
|
+
|
|
186
|
+
### 🧯 Don't get trapped in a map: the whiteboard
|
|
187
|
+
|
|
188
|
+
Deep in a map, something unrelated surfaces: a bug in another area, a chore, a quick idea you want to act on now. Forcing it onto the map's frontier pollutes the map; charting a whole new map for it is overkill. That is what the **whiteboard** is for, loose map-less work, and two moves keep you from getting stuck:
|
|
189
|
+
|
|
190
|
+
- **Capture it aside.** A `todo`/`bug`/`seed`/sharp `idea` fired while a map is open asks *map or whiteboard?*. Send it to the whiteboard and it stays off the map: tracked, but out of the way, so the map's frontier keeps meaning "the way to this destination".
|
|
191
|
+
- **Do it on the fly.** `/trailhead:quick "<text>"` opens a whiteboard ticket and works it end to end in the same sitting, the full discuss → plan → execute → verify engine (atomic commits, code review, the lot), except it **grills only if needed and never splits**, and skips every map book-keeping step. `/trailhead:quick <n>` does the same for a ticket that already exists.
|
|
192
|
+
|
|
193
|
+
See the whole whiteboard with `/trailhead:whiteboard`. Nothing about the map changes: you just stepped off it, did the thing, and step back on when you're ready.
|
|
194
|
+
|
|
180
195
|
The three that trip people up are **idea, seed, todo**, so here they are spelled out:
|
|
181
196
|
|
|
182
197
|
- **`idea`** = *maybe, and not yet clear.* It lands in the **fog** (`Not yet specified`), **not** as a ticket, because you can't even phrase the question sharply yet, so there is nothing to work. It **graduates** into a ticket later, when the frontier reaches it or it simply gets clearer.
|
|
@@ -211,9 +226,10 @@ Two rules of thumb: build tickets **never auto-grill**: on blocking ambiguity th
|
|
|
211
226
|
Everything the map needs is expressed as GitHub labels, so state is queryable in the tracker UI:
|
|
212
227
|
|
|
213
228
|
- **Structural:** `trailhead:map`, `trailhead:ticket`
|
|
214
|
-
- **Repo-scoped anchors (one each per repo):** `trailhead:codebase` (the distilled codebase map), `trailhead:conventions` (the way of working)
|
|
229
|
+
- **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)
|
|
215
230
|
- **Type (one per ticket):** 🧭 `trailhead:decision` · 🔬 `research` · 🎨 `prototype` · 🔨 `build` · 🐛 `bug` · 🔧 `task`
|
|
216
231
|
- **State:** `trailhead:blocked` (has an open blocker) · `seed` (parked on a trigger) · `out-of-scope` (closed, beyond the destination) · `superseded` (closed, split into children)
|
|
232
|
+
- **Container:** `trailhead:whiteboard` (a loose, map-less ticket, off every map's frontier, on the whiteboard's own)
|
|
217
233
|
|
|
218
234
|
The **frontier** is then a single query (open, unassigned, not `trailhead:blocked`), no body-parsing needed.
|
|
219
235
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marcomigozzi/trailhead",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Start and drive large projects as a map of tickets on GitHub Issues. Map-based onboarding + a discuss→plan→execute→verify engine, all on the Issues. Installs the trailhead skill, commands, and hooks into your agent's config dir.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"trailhead": "bin/trailhead.js"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trailhead",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Start and drive large projects as a map of tickets on GitHub Issues, resolving one at a time. Map-based onboarding + a discuss→plan→execute→verify engine with atomic commits, all on the Issues. Self-contained: depends on nothing beyond an authenticated gh CLI.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Marco Migozzi",
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Show the repo dashboard: the pinned index of every open map, the whiteboard, and live counts"
|
|
3
|
+
argument-hint: ""
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Invoke the `trailhead` skill for the **dashboard** action: call the Skill tool with skill name `trailhead` and arguments `dashboard $ARGUMENTS`, then carry out the skill's instructions for that verb (regenerate the pinned `trailhead:dashboard` index issue from the live tracker, creating and pinning it if missing, and show it). Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Work one ticket whole, off the map (whiteboard); grills only if needed, never splits
|
|
3
|
+
argument-hint: "[ticket | \"text\"]"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Invoke the `trailhead` skill for the **quick** action: call the Skill tool with skill name `trailhead` and arguments `quick $ARGUMENTS`, then carry out the skill's instructions for that verb (open a `trailhead:whiteboard` ticket from the `"text"`, or take the existing ticket you name, then run the full engine on it without splitting and without map book-keeping). Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Show the whiteboard: loose (map-less) tickets, their frontier and who holds each"
|
|
3
|
+
argument-hint: ""
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Invoke the `trailhead` skill for the **whiteboard** action: call the Skill tool with skill name `trailhead` and arguments `whiteboard $ARGUMENTS`, then carry out the skill's instructions for that verb (render the read-only whiteboard dashboard: the frontier, in-progress, and blocked `trailhead:whiteboard` tickets). Do not re-implement the behaviour here. The skill is the single source of truth.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: trailhead
|
|
3
3
|
description: "Start and drive a large project (more than one agent session can hold) as a map of decision tickets on GitHub Issues, resolving one at a time until the way to the destination is clear. Self-contained: map-based onboarding and a discuss→plan→execute→verify engine with atomic commits, with everything (map, discussion, plan, verification) living on the Issues. No dependencies beyond an authenticated gh CLI. Invoke only when the user explicitly runs `/trailhead` (or a `/trailhead:<verb>` command) or asks to chart, adopt, or work a trailhead map, and do not auto-invoke for generic planning, project, or task-tracking requests."
|
|
4
|
-
argument-hint: "[new|adopt|work|ticket|inbox|map|config|grill|split|pause|resume|bug|todo|idea|seed|note] [text | ticket]"
|
|
4
|
+
argument-hint: "[new|adopt|work|quick|whiteboard|ticket|inbox|map|config|grill|split|pause|resume|bug|todo|idea|seed|note] [text | ticket]"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
A loose idea has arrived, too big for one session: the way from here to the **destination** isn't visible yet. `trailhead` is where the trail begins. It charts the way as a **shared map on GitHub Issues**, then works its **tickets**, one at a time, until the road is clear and the destination reached.
|
|
@@ -20,7 +20,7 @@ Only an **authenticated `gh` CLI**: the tracker is GitHub Issues. Nothing else t
|
|
|
20
20
|
|
|
21
21
|
## Commands
|
|
22
22
|
|
|
23
|
-
The **first word** of `$ARGUMENTS` is the verb; the rest is the text (or a ticket number). With no verb, `/trailhead` does **smart entry**: it detects the repo state and proposes (no map → offer *chart* or *adopt
|
|
23
|
+
The **first word** of `$ARGUMENTS` is the verb; the rest is the text (or a ticket number). With no verb, `/trailhead` does **smart entry**: it detects the repo state and proposes (no map → offer *chart* or *adopt*, or point at `/trailhead:whiteboard` if the repo has loose `trailhead:whiteboard` tickets but no map; map present with a workable frontier → *work* the next frontier ticket; **map present but its frontier is empty** (everything left is gated or still fog, or the map is complete-except-gated) → don't dead-end, lay out the sensible moves as a short list and **always include `/trailhead:quick "<text>"` right after `/trailhead:map`** as the way to do a discrete piece of work off the map now (`/trailhead:quick <n>` to work a loose ticket), alongside `/trailhead:map` for the full view, `/trailhead:whiteboard` when loose work waits, `/trailhead:new` to chart the next map, the captures (`todo`/`idea`/`bug`/`seed`) for new work, and graduating a fog line into a ticket when it's sharp enough). Smart entry also **self-heals the pinned dashboard**: it ensures the repo's `trailhead:dashboard` index exists and is pinned (create + pin only if missing, re-pin if the pin was dropped; never a body rewrite), so a repo that adopted trailhead before the dashboard existed gets one on the next bare `/trailhead`. See [The dashboard's self-heal note](#the-dashboard-a-pinned-repo-index).
|
|
24
24
|
|
|
25
25
|
**First-time config offer (once per project).** When a project has a map but **no `.trailhead/config.json` yet**, smart entry puts an **explicit two-way choice** to the user and waits for their pick: **(a) accept as-is** (continue on the current/proposed values, which writes an empty `{}`), or **(b) run `/trailhead:config`** to configure everything through the guided menu (models/design/TDD/…). **Always name both paths and let the user choose; never silently take one.** You may show the values you'd default to, but frame the close as this binary: **do not** pre-fill the config keys and ask for an open-ended inline edit ("ok tutto oppure le modifiche"), and **do not** interview the user key-by-key inline (that's exactly what `/trailhead:config`'s menu is for). Ask this **only once**: running config writes the file, and *accept as-is* writes an empty `{}`, so once `.trailhead/config.json` exists it's never offered again (a `{}` file means "reviewed, using defaults"). The same offer is made at the end of chart/adopt (Mode 1/1-bis). It fires on the bare `/trailhead` and when setting a project up, never in the middle of working a ticket.
|
|
26
26
|
|
|
@@ -33,6 +33,8 @@ Every verb is also a **namespaced command** (`/trailhead:new`, `/trailhead:work`
|
|
|
33
33
|
| `/trailhead:new [idea]` | chart a new map → Mode 1 |
|
|
34
34
|
| `/trailhead:adopt` | adopt an existing project → Mode 1-bis |
|
|
35
35
|
| `/trailhead:work [ticket]` | work the map: the next frontier ticket, or the one you name → Mode 2 |
|
|
36
|
+
| `/trailhead:quick [ticket \| "text"]` | work one ticket whole, off the map: `quick "text"` opens a whiteboard ticket and works it now, `quick <n>` works an existing one; full engine, grills only if needed, never splits |
|
|
37
|
+
| `/trailhead:whiteboard` | show the whiteboard: the loose (map-less) tickets, their frontier and who holds each |
|
|
36
38
|
| `/trailhead:inbox [issue]` | triage issues opened by others and integrate the worthwhile ones into the map |
|
|
37
39
|
| `/trailhead:resume [ticket]` | resume a paused ticket: read its latest `PAUSED` checkpoint and continue |
|
|
38
40
|
| `/trailhead:pause [note]` | checkpoint the ticket in play so it can be resumed later (by you or anyone) |
|
|
@@ -40,6 +42,7 @@ Every verb is also a **namespaced command** (`/trailhead:new`, `/trailhead:work`
|
|
|
40
42
|
| `/trailhead:ticket <type> <title>` | open one child ticket of the map on the fly (`type` = decision\|research\|prototype\|build\|bug\|task) |
|
|
41
43
|
| `/trailhead:grill [topic]` | run a standalone grilling session on a decision/topic (or a named ticket) |
|
|
42
44
|
| `/trailhead:map [map]` | show the low-res map (destination, decisions, frontier, fog); with several maps open, `[map]` picks one and makes it active, or lists them to choose (see [Multiple maps](#multiple-maps-on-one-repo-parking--concurrency)) |
|
|
45
|
+
| `/trailhead:dashboard` | show the repo dashboard: the pinned index of every open map, the whiteboard, and live counts; regenerates the pinned issue and shows it |
|
|
43
46
|
| `/trailhead:config [get\|set …]` | show the effective config (global + project), or set a key |
|
|
44
47
|
| `/trailhead:update` | check for a newer trailhead and install it where safe (see `references/updating.md`) |
|
|
45
48
|
|
|
@@ -76,10 +79,11 @@ Resolving a ticket **ends the session**: one ticket per session is a hard rule (
|
|
|
76
79
|
> - `/clear`: per liberare il contesto
|
|
77
80
|
> - poi `/trailhead:work <numero>` per lavorare **<nome del prossimo ticket>** (oppure `/trailhead:work` che prende **<numero>** in automatico)
|
|
78
81
|
> - in alternativa `/trailhead:map` per la vista completa e scegliere tu
|
|
82
|
+
> - oppure `/trailhead:quick` (o `/trailhead:quick "<il pezzo>"` se sai già cosa) per lavorare un pezzo fuori mappa, aperto e portato a termine subito
|
|
79
83
|
|
|
80
|
-
Name the next frontier ticket so the user knows what they're taking; put its **number** in the command (never the title). **
|
|
84
|
+
Name the next frontier ticket so the user knows what they're taking; put its **number** in the command (never the title). The block is the list of things to do **after the `/clear`** (it leads with it) and stays on screen in the terminal to read from, so each command must be either self-sufficient (a number already known) or safe to type by hand. The commands a handoff block may contain are `/clear`, `/trailhead:work` (bare or `<number>`), `/trailhead:map`, and `/trailhead:quick`; **in a whiteboard session** (the resolved ticket was `trailhead:whiteboard`), also `/trailhead:whiteboard`. **`/trailhead:quick` is the one command that may carry a free-text argument here**: `/trailhead:quick "<short seed>"` when a concrete off-map piece is actually at hand (its argument is forgiving freeform seed text, not an exact title to match, so pre-filling it is helpful, not error-prone), or **bare `/trailhead:quick`** as the generic "if you want to work a piece off the map" offer, which prompts for the piece itself; keep any seed short and quote-safe. **`/trailhead:work` always takes the number, never the title** of an existing ticket, and **never hand the user a `/trailhead:ticket <type> "<title>"`** (or any other title-bearing command): adding a map ticket needs the diverge-first framing (Mode 3), so it is never pre-filled.
|
|
81
85
|
|
|
82
|
-
**If the frontier is empty**, that usually means fog that this resolution made specifiable should have been **graduated into a ticket already** (Mode 2 step 5), so the frontier isn't actually empty. If it genuinely is (nothing ticketable yet, or the user must choose which fog item to take next), point the block to **`/trailhead:map`** and offer to open the next ticket *yourself* once they say which one, rather than pre-filling a `/trailhead:ticket` command for them. (The example is Italian; match whatever language you're conversing in.)
|
|
86
|
+
**If the frontier is empty**, that usually means fog that this resolution made specifiable should have been **graduated into a ticket already** (Mode 2 step 5), so the frontier isn't actually empty. If it genuinely is (nothing ticketable yet, or the user must choose which fog item to take next), point the block to **`/trailhead:map`** and offer to open the next ticket *yourself* once they say which one, rather than pre-filling a `/trailhead:ticket` command for them. **In a whiteboard session** the fog-graduation reasoning does not apply (the whiteboard has no fog): point the block at **`/trailhead:whiteboard`** (and `/trailhead:quick <n>` for the next loose ticket), not the map. (The example is Italian; match whatever language you're conversing in.)
|
|
83
87
|
|
|
84
88
|
**This block is required whenever you point the user at the next ticket**, including in a **later follow-up**, after answering their questions, not only in the instant you resolve the ticket. Never write "next: `/trailhead:work …`" without the `/clear` line above it.
|
|
85
89
|
|
|
@@ -91,11 +95,12 @@ The tracker is GitHub via the `gh` CLI (account already authenticated). **Never
|
|
|
91
95
|
|
|
92
96
|
| Element | How |
|
|
93
97
|
|----------|------|
|
|
94
|
-
| Map | an issue with label `trailhead:map`. **
|
|
98
|
+
| Map | an issue with label `trailhead:map`. **Not pinned** (the 3 pinned slots are the repo-scoped [dashboard](#the-dashboard-a-pinned-repo-index) + codebase + conventions): open maps are found via the `trailhead:map` label and listed in the pinned dashboard, so a repo's maps stay one click away without contending for a slot. **When a map is exhausted** (destination reached: no open tickets and no fog left), **ask the user whether to close the map issue**: on a yes, `gh issue close` it (a closed issue is still the durable record) and refresh the dashboard so it drops off; on a no, leave it open. Never close a map unprompted; there is no pin to free either way. Each map's **tickets** share a **`trailhead:map-<n>` label** (n = the map's issue number) that scopes its frontier; the map issue itself is found by its number and does not carry it. |
|
|
95
99
|
| Codebase | a **single per-repo** issue with label `trailhead:codebase`: the distilled codebase map (architecture, stack, conventions, decisions embodied, risks, test/build). It's repo-scoped: **shared by every map of this repo, owned by none**, so it survives when a map is finished. Each map's Notes *links* it (never re-inlines it). Not a `trailhead:ticket`, so it's off the frontier. Written at adopt; a **greenfield map** (`/trailhead:new`) has no code to map at chart, so it's created **later, once the repo has substantial code** (e.g. after the first build tickets land), via the Codebase map technique. **Kept fresh at Resolve**: when a resolved `build`/`bug` ticket **materially changes** the map (a new module or layer, a new dependency/integration, a new seam, a changed build/test command, a new convention), **patch the affected facet of this issue in place** as part of Resolve, one or two lines, not a re-run of the fan-out. Cosmetic or in-facet changes leave it alone; a full re-map is only for major drift. |
|
|
96
100
|
| Conventions | a **single per-repo** issue with label `trailhead:conventions`: the project's **way of working**, readable by everyone. Repo-scoped and linked from every map's Notes, like Codebase. It opens with a small machine-read header the engine obeys, then human prose. Header keys (defaults **bold**): `git:` **`main`** \| `pr` (commit straight to `main`, or feature branch + PR) · `release:` **`command`** \| `auto` (never release without an explicit command, or release automatically per the project's flow) · `isolation:` **`none`** \| `worktree` \| `clone` (work in the current checkout; or give each executing ticket its own `git worktree` + `trailhead/t<n>` branch; or a dedicated per-ticket **clone** at `../<repo>-t<n>` for **path-bound apps a worktree can't build**, e.g. React Native/Expo with a local `node_modules`; see [Working as a team](#working-as-a-team)). **`worktree`/`clone` imply a branch per ticket even under `git: main`** (git won't check the trunk out twice); the branch integrates to the trunk per `git:` at Resolve. Under **`none`** two in-progress tickets with overlapping `Scope:` **serialise** (the second holds at claim). Below the header: the standing "how we work here" notes. **Filled at chart/adopt by a brief conventions brainstorming** (see the Invocation modes), not a `trailhead:ticket`, off the frontier. |
|
|
97
|
-
|
|
|
98
|
-
|
|
|
101
|
+
| Dashboard | a **single per-repo** issue with label `trailhead:dashboard`: the pinned index of the whole trailhead surface. Repo-scoped and permanent, like Codebase and Conventions, and it occupies the **fixed 3rd pinned slot** alongside them (`gh api graphql` `pinIssue` mutation on its node id). It holds: a link to **every open `trailhead:map`** (GitHub renders each map's sub-issue **progress bar natively**, so the dashboard only links, it does not recompute per-ticket progress), the **whiteboard** as a section (or a link to the `/trailhead:whiteboard` view), and dynamic **counts** (untriaged inbox, whiteboard frontier size). **Created and pinned at the first chart/adopt**, then **rewritten on structural events** (a map is charted or exhausted, i.e. a map appears or disappears) **and on demand** via `/trailhead:dashboard`. **Not rewritten on every ticket resolve** (that would churn a pinned issue and its notifications; the native progress bars already track per-ticket progress). See [The dashboard](#the-dashboard-a-pinned-repo-index). |
|
|
102
|
+
| Ticket | a child issue with label `trailhead:ticket` + exactly one **type** label: `trailhead:decision` / `trailhead:research` / `trailhead:prototype` / `trailhead:build` / `trailhead:bug` / `trailhead:task`, plus its map's `trailhead:map-<n>` label and native sub-issue edge (see Child→map link). **A `trailhead:whiteboard` ticket carries none of the map wiring** (no `trailhead:map-<n>`, no sub-issue edge, no `Parent:`); see [The whiteboard](#the-whiteboard-map-less-tickets). |
|
|
103
|
+
| Child→map link | three views of one fact, wired in the same pass (like a blocker): a `Parent: <map name>(link)` line in the ticket body (human-readable); a **native sub-issue** edge to the map (`gh api --method POST repos/{owner}/{repo}/issues/<map>/sub_issues -F sub_issue_id=<ticket .id>`, the structure GitHub renders); and the map's **`trailhead:map-<n>` label** on the ticket (the queryable key that scopes its map's frontier). See [Multiple maps on one repo](#multiple-maps-on-one-repo-parking--concurrency). **A whiteboard ticket has none of the three** (it belongs to no map). |
|
|
99
104
|
| Path scope (monorepo) | an optional `Scope: <path>[, <path>…]` line in the ticket body naming the package/dir(s), or **submodule path(s)**, the ticket works in. It sharpens the [undeclared-coupling check](#working-as-a-team) (disjoint scopes parallelize safely), scopes build/test to the affected package, and scopes an `isolation: worktree` worktree's commits. **List every one it touches** (a ticket spanning two submodules names both). **Omit it in a single-package repo** (the whole tree is the scope); add it only where a repo has independent packages/subtrees or submodules. |
|
|
100
105
|
| Blocking | **Find every blocker first: from the ticket's own Question.** Each still-open ticket whose output this ticket *consumes as an input* is a blocker: a decision that needs another's answer, a build that needs a decision, a formula that needs the very thing another ticket defines. Read the Question and wire **all** of them: miss one and the ticket surfaces on the frontier prematurely and gets worked before its prerequisite. Then wiring a blocker is **three moves**: (1) list *which* tickets block in the `## Blocked by` body section (name+link); (2) create the **native GitHub dependency** so the frontier renders visually in GitHub's own UI: `gh api --method POST repos/{owner}/{repo}/issues/<blocked>/dependencies/blocked_by -F issue_id=<blocker's internal .id>`; (3) add the **`trailhead:blocked`** label. The **label is the frontier's source of truth**: native dependencies aren't cheaply queryable in one search, so the label is what keeps the frontier **one query**. On unblock, **remove the label the moment the last blocker closes** (that's what graduates the ticket onto the frontier); the native edge needs no cleanup: GitHub auto-reflects a closed blocker. **The three moves are one fact in three views: they must name the *same* blocker.** The `## Blocked by` line must link the **real blocker ticket** the native edge targets (its actual number), never a superseded parent, a split-origin, or a by-role placeholder like "child A"; wire the prose line and the native dependency in the same pass, from the same id, so they can't drift. |
|
|
101
106
|
| Claim | assign the ticket to yourself: `gh issue edit <n> --add-assignee @me`: the assignee *is* the claim; the claimer owns it end to end and closes it (no approver). Re-check before starting; on a collision, stop and ask the user. See [Working as a team](#working-as-a-team). |
|
|
@@ -108,8 +113,11 @@ The tracker is GitHub via the `gh` CLI (account already authenticated). **Never
|
|
|
108
113
|
- **`trailhead:superseded`**: closed because split into child tickets that replace it (see [Working as a team](#working-as-a-team)); *not* resolved, and out of `Decisions so far`.
|
|
109
114
|
- **`trailhead:unverified`**: a `trailhead:*` issue whose provenance isn't trusted (see [Trust & provenance](#working-as-a-team)); quarantined off the frontier until a maintainer adopts or rejects it.
|
|
110
115
|
- **`trailhead:fog`**: an in-scope-but-not-yet-sharp issue (usually an inbound suggestion) **kept open as a clarification space** (visible and tracked, off the frontier and out of the inbox) where the reporter and others discuss until it's sharp enough to graduate into a ticket. See [Inbox](#inbox-issues-opened-by-others).
|
|
116
|
+
- **`trailhead:whiteboard`**: a **map-less** ticket, parked on the **whiteboard** (the container for work not tied to any map). **Mutually exclusive with a `trailhead:map-<n>` label**: a ticket is on a map or on the whiteboard, never both, and a whiteboard ticket has no `Parent:` line and no native sub-issue edge. It carries its own frontier (below), off every map's. Captured there when a capture is routed to the whiteboard, or born there by `quick`; see [The whiteboard](#the-whiteboard-map-less-tickets).
|
|
111
117
|
|
|
112
|
-
**Frontier** = open, unassigned tickets that are **not** `trailhead:blocked` and **not** `trailhead:unverified`. That's the whole query: the labels are the pre-computed answers to "are all blockers closed?" and "is this a trusted trailhead ticket?", so no per-ticket body parsing. **With more than one live map on the repo**, add the active map's `trailhead:map-<n>` label to scope the frontier to that map (still one query); with a single map the repo-wide query
|
|
118
|
+
**Frontier** = open, unassigned tickets that are **not** `trailhead:blocked` and **not** `trailhead:unverified`. That's the whole query: the labels are the pre-computed answers to "are all blockers closed?" and "is this a trusted trailhead ticket?", so no per-ticket body parsing. **With more than one live map on the repo**, add the active map's `trailhead:map-<n>` label to scope the frontier to that map (still one query); with a single map the repo-wide query below is the frontier. See [Multiple maps on one repo](#multiple-maps-on-one-repo-parking--concurrency).
|
|
119
|
+
|
|
120
|
+
**Map frontier vs whiteboard frontier.** The whiteboard (tickets labelled `trailhead:whiteboard`, map-less) has its **own** frontier, queried by that label with the same open/unassigned/not-blocked/not-unverified filter, and it is **excluded from every map frontier**: the multi-map query already excludes it (whiteboard tickets carry no `trailhead:map-<n>`), and the single-map repo-wide query adds `-label:trailhead:whiteboard`. So a map's frontier and the whiteboard's never overlap. `/trailhead:whiteboard` renders the whiteboard frontier; `/trailhead:map` and bare `/trailhead:work` stay on the map. See [The whiteboard](#the-whiteboard-map-less-tickets).
|
|
113
121
|
|
|
114
122
|
**Blocked-by reconciliation (drift check).** The `## Blocked by` prose and the native dependency are two copies of one fact, so they can drift (it has happened: a body naming the split-*origin* while the native edge pointed at the sibling). When rendering `/trailhead:map` and before working a ticket, **reconcile them deterministically**: parse the issue numbers in the ticket's `## Blocked by`, read its native `blocked_by` (`gh api repos/{owner}/{repo}/issues/<n>/dependencies/blocked_by`), and if the two sets differ, **surface an advisory** naming both sides (the native edge is the structured reference); never auto-fix, never block. If the body has no parseable `## Blocked by`, report it **uncheckable**, never assume they agree. This is the one deterministic, false-positive-free consistency axis; higher-level "does a ticket contradict the map's decisions" drift is left to human judgement.
|
|
115
123
|
|
|
@@ -124,8 +132,10 @@ gh api --method POST repos/{owner}/{repo}/issues/<map>/sub_issues -F sub_issue_i
|
|
|
124
132
|
# wire a blocker: native dependency (visual frontier in the UI) + the label (the query)
|
|
125
133
|
gh api --method POST repos/{owner}/{repo}/issues/<blocked>/dependencies/blocked_by -F issue_id=$(gh api repos/{owner}/{repo}/issues/<blocker> --jq .id)
|
|
126
134
|
gh issue edit <blocked> --add-label "trailhead:blocked"
|
|
127
|
-
# the frontier: open, unassigned, not blocked, not unverified, one query (add --label trailhead:map-<map> to scope to one map when several are live)
|
|
128
|
-
gh issue list --label "trailhead:ticket" --state open --search "no:assignee -label:trailhead:blocked -label:trailhead:unverified"
|
|
135
|
+
# the frontier: open, unassigned, not blocked, not unverified, one query (add --label trailhead:map-<map> to scope to one map when several are live; add -label:trailhead:whiteboard to a single-map repo-wide query to keep loose tickets off it)
|
|
136
|
+
gh issue list --label "trailhead:ticket" --state open --search "no:assignee -label:trailhead:blocked -label:trailhead:unverified -label:trailhead:whiteboard"
|
|
137
|
+
# the whiteboard frontier: map-less loose tickets, its own query
|
|
138
|
+
gh issue list --label "trailhead:ticket" --label "trailhead:whiteboard" --state open --search "no:assignee -label:trailhead:blocked -label:trailhead:unverified"
|
|
129
139
|
# unblock a ticket once its last blocker closes (label only; native edge auto-reflects)
|
|
130
140
|
gh issue edit <n> --remove-label "trailhead:blocked"
|
|
131
141
|
# claim
|
|
@@ -134,22 +144,25 @@ gh issue edit <n> --add-assignee @me
|
|
|
134
144
|
gh issue comment <n> --body-file <resolution> && gh issue close <n>
|
|
135
145
|
```
|
|
136
146
|
**First-use repo setup (do BOTH, every chart or adopt, never skip either):**
|
|
137
|
-
1. Create any missing labels with `gh label create` (all
|
|
147
|
+
1. Create any missing labels with `gh label create` (all eighteen: `trailhead:map`, `trailhead:codebase`, `trailhead:conventions`, `trailhead:dashboard`, `trailhead:ticket`, the six type labels, `trailhead:blocked`, `trailhead:seed`, `trailhead:out-of-scope`, `trailhead:superseded`, `trailhead:unverified`, `trailhead:fog`, `trailhead:whiteboard`).
|
|
138
148
|
2. **Check the label guard is installed**: `gh api repos/{owner}/{repo}/contents/.github/workflows/trailhead-label-guard.yml`; if it's absent (404), install it: **read `references/teamwork.md` (Trust & provenance → Repo-side enforcement) for the exact steps**. This is part of standing up trailhead in a repo, not an optional extra: *check every time*, so a repo can never end up with the labels but no guard.
|
|
139
149
|
|
|
140
150
|
## The Map
|
|
141
151
|
|
|
142
152
|
A single `trailhead:map` issue, the canonical artifact. It's an **index**, not a store: it lists the decisions made and points at the tickets that hold their detail. A decision lives in exactly one place (its ticket); the map gists it and links.
|
|
143
153
|
|
|
144
|
-
**`/trailhead:map`** renders this at low resolution as a read-only dashboard (it changes nothing), the map body plus the live ticket state, by name:
|
|
154
|
+
**`/trailhead:map`** renders this at low resolution as a read-only dashboard (it changes nothing on the map itself; see the dashboard self-heal and exhaustion check below), the map body plus the live ticket state, by name:
|
|
145
155
|
- **Destination**: the one-line where-we're-headed.
|
|
146
|
-
- **Frontier**: takeable now (open, unassigned, not blocked/unverified), each with its type.
|
|
156
|
+
- **Frontier**: takeable now (open, unassigned, not blocked/unverified, and **not** `trailhead:whiteboard`, which has its own view), each with its type.
|
|
147
157
|
- **In progress**: claimed tickets, with who holds each.
|
|
148
158
|
- **Blocked**: with what each waits on; flag any **blocked-by drift** (prose `## Blocked by` ≠ native dependency, see [Blocked-by reconciliation](#substrate-github-issues)) as an advisory line.
|
|
149
159
|
- **Decisions so far**: the index of what's settled.
|
|
150
160
|
- **Not yet specified** + **parked fog**: the coarse fog, and a count/link of open `trailhead:fog` issues.
|
|
151
161
|
- **Out of scope**: what's been ruled out; **flag as an advisory** any line that's actually **deferred** rather than truly beyond the destination: it **names a gate/trigger** (*gated by X*, *quando X*, *dipende da X*), carries a **this-only qualifier** (*per questo slice*, *for now*, *in this milestone*), is a **feature wanted later** (another milestone/map), or **is the gate of an existing `trailhead:seed`** (a seed's trigger names it, so it cannot be "ruled out", and that cluster is mis-wired). **Flag only lines not yet properly parked**: a line **already annotated `→ future map`** whose dependent seeds are **wired to a live trigger** is settled, say nothing about it. When one or more **un-parked** such lines are present, **don't stay silent: name them and suggest `/trailhead:inbox` to re-route them** (seed/idea/todo). See [Out of scope](#out-of-scope).
|
|
152
162
|
- **Inbox**: a count of untriaged inbound issues, as a nudge.
|
|
163
|
+
- **Exhaustion check**: if the map is exhausted (no open tickets and no fog left: the destination is reached), **say so and ask whether to close the map issue**, the same ask as Mode 2's hand-off: on a yes, `gh issue close` it and refresh the dashboard so it drops off; on a no, leave it open. Maps aren't pinned, so there's no pin to free either way. Never close unprompted. Route any un-parked deferred *Out of scope* line first (above) so nothing wanted-later is lost. This user-confirmed close is the only change `/trailhead:map` makes to the map, and only on an explicit yes (the dashboard self-heal below touches only the pinned index, never the map).
|
|
164
|
+
|
|
165
|
+
**Dashboard self-heal.** Before rendering, ensure the repo's pinned `trailhead:dashboard` index exists and is pinned: create + pin it only if missing, re-pin it if the pin was dropped, and otherwise do nothing (never rewrite its body on a render). See [The dashboard's self-heal note](#the-dashboard-a-pinned-repo-index).
|
|
153
166
|
|
|
154
167
|
Map body (loaded once per session):
|
|
155
168
|
|
|
@@ -184,6 +197,8 @@ Load the effective config at the start of a work session, **from the map's proje
|
|
|
184
197
|
|
|
185
198
|
**Ticket language (standing engine rule).** Write **all human-authored trailhead prose on the Issues** (ticket titles and bodies, every engine comment (`DISCUSS`/`PLAN`/`VERIFY`/`OPTIONS`/`REPRO`/`DIAGNOSIS`/`PAUSED`/resolution), and the map body sections) **and commit message descriptions** in `config.ticket.language` (ISO 639-1, default `en`). The conventional-commit **type prefix stays English** (`feat:`/`fix:`/…). This is **independent of the language the agent converses in** and never changes it; likewise it never touches code, identifiers, the fixed `trailhead:*` label names, or config keys/values. The skill's own source docs stay English regardless.
|
|
186
199
|
|
|
200
|
+
**This is the single most-missed rule, and the trap is specific: when you are chatting in one language and go to write a ticket, the body reflexively comes out in the *chat's* language, which is wrong.** The chat language and the ticket language are decoupled on purpose. Before every `gh issue create`/`edit`/`comment` and every commit body, consciously write in `config.ticket.language` (default `en`), not the language of the conversation. This holds for the zero-friction captures too (`bug`/`todo`/`idea`/`seed`/`note`) **and for `quick`** (its `"<text>"` seed is input in the chat's language, but the whiteboard ticket it opens is written in the ticket language): a one-line Italian request still becomes an English ticket. If you catch a ticket, comment, or map section already written in the wrong language, **convert it in place before continuing** any other work.
|
|
201
|
+
|
|
187
202
|
## The Tickets
|
|
188
203
|
|
|
189
204
|
Each ticket is a child issue. Its **title carries the type icon** (see below); the body stays lean, one Question, sized to one ~100K-token session:
|
|
@@ -192,6 +207,7 @@ Each ticket is a child issue. Its **title carries the type icon** (see below); t
|
|
|
192
207
|
Title: 🐛 <clear, specific title>
|
|
193
208
|
|
|
194
209
|
**Parent:** [<map name>](link)
|
|
210
|
+
<!-- map tickets only; a trailhead:whiteboard ticket omits the Parent line entirely -->
|
|
195
211
|
|
|
196
212
|
## Question
|
|
197
213
|
<the decision, investigation, or build goal this ticket resolves>
|
|
@@ -219,6 +235,8 @@ The map issue title is prefixed 🗺. Keep the icons stable and don't add others
|
|
|
219
235
|
|
|
220
236
|
The ticket engines call these by name. Each technique's full protocol lives in its own file under `references/techniques/`: **read that file the first time a session needs the technique**, so only the ones in play load into context (don't preload them). They are `trailhead`'s own, self-contained; subagents are spawned with the built-in `Agent` tool.
|
|
221
237
|
|
|
238
|
+
**Pin the subagent type; never borrow a foreign agent.** Every technique subagent carries its full protocol in the prompt (from its `references/techniques/` file), so it needs no specialised agent, and must not use one: **always set `subagent_type` to a built-in agent, never a project- or plugin-provided type** (a specialised `*-reviewer`, `*-researcher`, `*-mapper`, or similarly named agent that another installed tool registered). Even when such an agent's name or description matches the task perfectly, it follows *its own* protocol and artifact conventions (a specialised code-review agent writes its own review file, not trailhead's `VERIFY` ticket comment), so selecting it silently swaps trailhead's engine for another tool's. The split by what the technique touches: **read-only techniques** (Code review, Codebase map, and the Plan step) spawn **`Explore`** (reads, searches, runs tests; no Edit/Write); **repo-modifying techniques** (Research, Execute, Fix, Debug) spawn **`general-purpose`**. On a `subagentToolkit == none` host (Codex) there is no fan-out and this is moot: every technique runs inline in the one session.
|
|
239
|
+
|
|
222
240
|
| Technique | File | In one line |
|
|
223
241
|
|---|---|---|
|
|
224
242
|
| **Grilling** | `references/techniques/grilling.md` | interrogate the human, one question at a time, to converge on a decision |
|
|
@@ -254,8 +272,8 @@ Raise the fidelity of the discussion with a rough, concrete artifact to react to
|
|
|
254
272
|
The ticket that *builds*. Lean cycle, all in **ticket comments**:
|
|
255
273
|
|
|
256
274
|
1. **Discuss**: **never auto-grill.** Start from Plan. If reading the ticket surfaces blocking ambiguity, **stop and ask the user** whether they want a **Grilling** round: don't launch it on your own initiative. Only on their assent run the short round and post a `DISCUSS` comment with the closed choices. The user may also ask for it themselves at any time.
|
|
257
|
-
2. **Plan**: a `PLAN` comment: the steps, the seams, the files touched, the verification criteria. Apply **TDD** per `config.tdd` (`seams`/`on`/`off`). Produce this plan via a *planner subagent* on `config.models.plan` (inline when
|
|
258
|
-
3. **Execute**: **if this ticket changes user-facing UI (a new screen, a redesign, a changed layout), run the [Prototype](#techniques) technique FIRST, before any real UI code**: surface a mockup to react to, routed by `config.design` (disk / claude.ai/design) and gated by `config.design.approval` (`explicit` waits for a go-ahead, `auto` proceeds after surfacing). For a redesign or new screen the default is to offer mockups; don't jump straight to code on a UI ticket. Then implement with **atomic commits** (conventional commits, each with a `Refs: #<n>` trailer for this ticket, see [Principles](#principles)), following the repo's `trailhead:conventions` `git:`, straight to `main` by default, or a feature branch + PR when `git: pr`. **Under `isolation: worktree`, do the work in this ticket's `git worktree`** (`trailhead/t<n>` branch), integrating to the trunk at Resolve (see [Working as a team](#working-as-a-team)). One commit = one verifiable step. Run this via an **executor subagent on `config.models.execute`** (inline when
|
|
275
|
+
2. **Plan**: a `PLAN` comment: the steps, the seams, the files touched, the verification criteria. Apply **TDD** per `config.tdd` (`seams`/`on`/`off`). Produce this plan via a *planner subagent* on `config.models.plan` (spawn it as the built-in **`Explore`** agent, read-only; inline when the model is unset or equals the session model; Execute then runs as its own subagent, see below). If `config.plan_review` is on, run **Cross-AI plan review** before Execute: send the plan to external AI CLIs and converge on their concerns. Now that the files/seam are known, run the [undeclared-coupling check](#working-as-a-team) against in-progress tickets.
|
|
276
|
+
3. **Execute**: **if this ticket changes user-facing UI (a new screen, a redesign, a changed layout), run the [Prototype](#techniques) technique FIRST, before any real UI code**: surface a mockup to react to, routed by `config.design` (disk / claude.ai/design) and gated by `config.design.approval` (`explicit` waits for a go-ahead, `auto` proceeds after surfacing). For a redesign or new screen the default is to offer mockups; don't jump straight to code on a UI ticket. Then implement with **atomic commits** (conventional commits, each with a `Refs: #<n>` trailer for this ticket, see [Principles](#principles)), following the repo's `trailhead:conventions` `git:`, straight to `main` by default, or a feature branch + PR when `git: pr`. **Under `isolation: worktree`, do the work in this ticket's `git worktree`** (`trailhead/t<n>` branch), integrating to the trunk at Resolve (see [Working as a team](#working-as-a-team)). One commit = one verifiable step. Run this via an **executor subagent on `config.models.execute`** (spawn it as the built-in **`general-purpose`** agent, which can Edit/Write/commit; inline when the model is unset or equals the session model); the commits land on `main`, visible. New scope surfacing mid-execute is captured/split, not worked here.
|
|
259
277
|
4. **Verify**: a `VERIFY` comment: run the tests / the plan's criterion; then the **Code review** technique (its subagent reviews *adversarially*: it doesn't defer to a `CLAUDE.md` convention or a "settled decision" memory); then **Acceptance testing** if the change is user-facing (browser-drive it, or **walk the user through a guided UAT conversationally, one step at a time**, never a checklist dumped in a comment). Report the outcome honestly (if a test fails, say so).
|
|
260
278
|
5. **Resolve**: a resolution comment with what was done → `gh issue close` → update `Decisions so far` on the map. **If this change materially altered the codebase** (a new module/layer, dependency, seam, or build/test command), **patch the affected facet of the [`trailhead:codebase`](#substrate-github-issues) issue** in place (a line or two, not a re-map). Then close the session with the **[Session handoff](#session-handoff)** ritual (`/clear` + next command).
|
|
261
279
|
|
|
@@ -272,7 +290,7 @@ Then run the cycle, all in ticket comments:
|
|
|
272
290
|
|
|
273
291
|
1. **Repro**: a `REPRO` comment: how to reproduce, expected vs observed behaviour.
|
|
274
292
|
2. **Diagnose**: run the **Debug** technique. A `DIAGNOSIS` comment with the root cause.
|
|
275
|
-
3. **Fix**: implement the correction with **atomic commits** (each with a `Refs: #<n>` trailer for this ticket, see [Principles](#principles); per the repo's `trailhead:conventions` `git:`, `main` by default, else a branch + PR; in this ticket's `git worktree` under `isolation: worktree`), via an **executor subagent on `config.models.execute`** (inline when unset/same as session). Where sensible, a test that fails before and passes after (**TDD**).
|
|
293
|
+
3. **Fix**: implement the correction with **atomic commits** (each with a `Refs: #<n>` trailer for this ticket, see [Principles](#principles); per the repo's `trailhead:conventions` `git:`, `main` by default, else a branch + PR; in this ticket's `git worktree` under `isolation: worktree`), via an **executor subagent on `config.models.execute`** (the built-in **`general-purpose`** agent; inline when unset/same as session). Where sensible, a test that fails before and passes after (**TDD**).
|
|
276
294
|
4. **Verify**: a `VERIFY` comment: the repro now passes, no regressions; **Code review** on the fix; **Acceptance testing** if the bug was user-facing (browser-drive the fixed flow, or **walk the user through a guided UAT conversationally, step by step**, not a checklist to self-serve).
|
|
277
295
|
5. **Resolve**: a resolution comment (cause + fix) → `gh issue close` → update `Decisions so far`. **If the fix materially altered the codebase**, patch the affected facet of the [`trailhead:codebase`](#substrate-github-issues) issue in place (a line or two). Then close the session with the **[Session handoff](#session-handoff)** ritual (`/clear` + next command).
|
|
278
296
|
|
|
@@ -281,6 +299,17 @@ A **blocking** bug that halts other work is worked immediately; an isolated one
|
|
|
281
299
|
### `task`: HITL or AFK
|
|
282
300
|
Manual work that must happen before a *decision* can be made: signing up for a service to judge its API, provisioning access, moving data to see its shape. The agent drives it alone where it can (AFK); otherwise it hands the human a precise checklist (HITL). Resolved when the work is done; the answer records what was done and the resulting facts (where credentials live, new URLs, row counts) later tickets depend on.
|
|
283
301
|
|
|
302
|
+
### `quick`: work one ticket whole, off the map
|
|
303
|
+
A streamlined way to just get one ticket done, without map ceremony. `quick "<text>"` opens a **whiteboard** ticket (a `build`, or a `bug` when the text is clearly a defect) and works it end to end now; `quick <n>` works an **existing** ticket (whiteboard or map) the same way. Claim it first; still one ticket per session. **Bare `/trailhead:quick`** (no argument, e.g. offered in a [Session handoff](#session-handoff)) asks for the piece to work, like a capture missing its text, then proceeds as `quick "<text>"`.
|
|
304
|
+
|
|
305
|
+
It runs the **full engine** (the `build`/`bug` cycle: Discuss → Plan → Execute → Verify → Resolve), differing from `work` on three points:
|
|
306
|
+
- **Grill only if needed** (Discuss): never auto-grill; start from Plan and stop to ask only if a blocking ambiguity surfaces, exactly as the `build` Discuss. The user may ask to grill at any time.
|
|
307
|
+
- **Never splits.** If the ticket turns out large, work it whole anyway: that is the point of `quick`. Do not `split` it and do not spin children. (New scope that surfaces mid-work is still captured out, never folded in, see [Scope that surfaces while working a ticket](#capture-zero-friction--tracker).)
|
|
308
|
+
- **No map narrative book-keeping.** A whiteboard ticket has no map, so there is no `Decisions so far` update, no fog to graduate, no frontier re-scan. Everything else holds: atomic commits with `Refs: #<n>`, TDD / Code review / Acceptance testing per the cycle, the resolution comment + `gh issue close`, and the [Session handoff](#session-handoff). **Exception, `quick <n>` on a *map* ticket: dependent-unblocking still runs** (remove `trailhead:blocked` from any dependent whose last blocker just closed, [Mode 2](#mode-2-work-the-map-trailheadwork) step 4); skipping it would strand those dependents off the frontier for good. Only the *narrative* book-keeping above and the split are skipped, so prefer `work <n>` for a map ticket that has dependents or belongs in the map record.
|
|
309
|
+
- **Same isolation setup as `work`.** Under `isolation: worktree`/`clone`, `quick` sets up the ticket's `trailhead/t<n>` workspace before any edit, re-checks the claim, and writes the `.trailhead/session-ticket` marker, exactly as [Mode 2](#mode-2-work-the-map-trailheadwork) step 2 (no active-map marker: a whiteboard ticket has no map). Under `isolation: none`, the scope-free check applies too.
|
|
310
|
+
|
|
311
|
+
Type follows the ticket: `quick "<text>"` defaults to `build` (`bug` if a defect); `quick <n>` uses the existing type and its matching engine.
|
|
312
|
+
|
|
284
313
|
## Fog of war
|
|
285
314
|
|
|
286
315
|
The map is *deliberately* incomplete: don't chart what you can't yet see. Beyond the live tickets lies the fog: decisions and investigations you can feel coming but can't yet pin down, because they hang on questions still open. Resolving a ticket clears the fog ahead of it, graduating into fresh tickets whatever has become specifiable, one at a time, until the way to the destination is clear and no tickets remain.
|
|
@@ -301,7 +330,7 @@ Only genuinely-beyond-the-destination work stays in *Out of scope*. The same tes
|
|
|
301
330
|
|
|
302
331
|
## Capture (zero friction → tracker)
|
|
303
332
|
|
|
304
|
-
Capture without breaking flow, to the **tracker** (not scattered files). One capture = one action + one confirmation line; it **resolves nothing**. The verbs are in the **Capture** table under [Commands](#commands); their per-verb detail and the **note · idea · seed · todo** commitment/timing spectrum (raw text → fog → gated ticket → frontier ticket) are in **`references/capture.md`**: read it when a capture verb needs more than the one-line gist. Any captured **ticket**
|
|
333
|
+
Capture without breaking flow, to the **tracker** (not scattered files). One capture = one action + one confirmation line; it **resolves nothing**. The verbs are in the **Capture** table under [Commands](#commands); their per-verb detail and the **note · idea · seed · todo** commitment/timing spectrum (raw text → fog → gated ticket → frontier ticket) are in **`references/capture.md`**: read it when a capture verb needs more than the one-line gist. Any captured **ticket** lands either on a **map** or on the **whiteboard** (map-less): **when a map is open, ask the user which** before creating it (active map vs whiteboard; if several maps are open, the same ask picks which map). On a map it gets that map's `trailhead:map-<n>` label + native sub-issue edge (like any ticket; see [Multiple maps on one repo](#multiple-maps-on-one-repo-parking--concurrency)); on the whiteboard it gets the `trailhead:whiteboard` label and **no** map label or `Parent:` (see [The whiteboard](#the-whiteboard-map-less-tickets)). **When no map is open at all, the ticket goes to the whiteboard without asking** (there is nowhere else). This ask applies to the ticket-producing captures (`todo`, `bug`, `seed`, and `idea` only when it is sharp enough to be a ticket); `note` and a non-sharp `idea` stay map-fog and are unaffected. With **no open map** they have no map fog to land in, so a `note` falls back to the global notes location (`$HOME/.claude/notes/`) and a non-sharp `idea` is either sharpened into a whiteboard ticket or recorded as a global note.
|
|
305
334
|
|
|
306
335
|
**Scope that surfaces while working a ticket.** Testing or building X often sparks new ideas: do **not** grow X in flight; a ticket is one answerable Question, sized to one session. Decide per idea:
|
|
307
336
|
- part of X's Question and small → just do it in X (that's completing X, not expanding it);
|
|
@@ -331,11 +360,54 @@ gh issue list --label "trailhead:ticket" --label "trailhead:map-<n>" --state ope
|
|
|
331
360
|
|
|
332
361
|
**Discovery.** `gh issue list --label "trailhead:map" --state open` enumerates the live maps. `/trailhead:map` with **no active map and more than one open** lists them and asks which; `/trailhead:map <n>` shows that one and makes it active.
|
|
333
362
|
|
|
334
|
-
**Pinning with several maps.**
|
|
363
|
+
**Pinning with several maps.** No map is ever pinned. The 3 pinned slots are the permanent repo anchors: **dashboard + codebase + conventions**. Every open map is found via the `trailhead:map` label and listed in the pinned [dashboard](#the-dashboard-a-pinned-repo-index), so all of them stay reachable regardless of how many are live, without contending for a slot.
|
|
335
364
|
|
|
336
365
|
**Team.** A teammate charts a new map → it gets its own `trailhead:map-<m>` + sub-issue parentage → their sessions scope to it. The two frontiers **never mix** (distinct labels). Per-ticket **claim/collision** rules are unchanged (the assignee is still the claim). Two maps can still touch the **same code**: the `Scope:` line + `isolation:` serialisation from [Working as a team](#working-as-a-team) now apply **across maps too**, not just within one.
|
|
337
366
|
|
|
338
|
-
**One-map compatibility.** With a single open map the frontier stays **repo-wide** (
|
|
367
|
+
**One-map compatibility.** With a single open map the frontier stays **repo-wide** (minus `trailhead:whiteboard` tickets, which carry their own frontier, see [The whiteboard](#the-whiteboard-map-less-tickets)); the map label + sub-issue are still applied at chart, so concurrency is ready the moment a second map appears. An older map charted before this (tickets lacking the label) needs a one-time **backfill** (add `trailhead:map-<n>` to its open tickets, set their sub-issue parent) only if you add a second map on that repo.
|
|
368
|
+
|
|
369
|
+
## The whiteboard (map-less tickets)
|
|
370
|
+
|
|
371
|
+
Not all work belongs to a map. A **bug**, a **todo**, or a one-off **task** may be about something else entirely, or simply not worth charting a map for. The **whiteboard** is where that loose work lives: tickets labelled **`trailhead:whiteboard`**, map-less (no `trailhead:map-<n>` label, no `Parent:` line, no native sub-issue edge). A ticket is on a map **or** on the whiteboard, never both.
|
|
372
|
+
|
|
373
|
+
**Ensure the label exists before applying it.** A repo that adopted trailhead before the whiteboard existed never created `trailhead:whiteboard` (first-use setup ran the old label set), so applying it would fail. Every path that puts a ticket on the whiteboard (a whiteboard-routed [capture](#capture-zero-friction--tracker), or [`quick`](#quick-work-one-ticket-whole-off-the-map) creating one) must first create the label if missing, idempotently: `gh label create trailhead:whiteboard --color C5DEF5 --description "Map-less ticket: lives on the whiteboard" 2>/dev/null || true`. This is the same ensure the first-use label step does, just reachable outside chart/adopt.
|
|
374
|
+
|
|
375
|
+
Work reaches the whiteboard two ways: a **capture routed there** (a ticket-producing `todo`/`bug`/`seed`/sharp-`idea` sent to the whiteboard instead of a map, see [Capture](#capture-zero-friction--tracker)), or a ticket **born there** by [`quick "<text>"`](#quick-work-one-ticket-whole-off-the-map).
|
|
376
|
+
|
|
377
|
+
It has its **own frontier**, off every map's (see [Substrate](#substrate-github-issues), Map frontier vs whiteboard frontier):
|
|
378
|
+
```bash
|
|
379
|
+
gh issue list --label "trailhead:ticket" --label "trailhead:whiteboard" --state open \
|
|
380
|
+
--search "no:assignee -label:trailhead:blocked -label:trailhead:unverified"
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
**`/trailhead:whiteboard`** renders this as a read-only dashboard, a mirror of [`/trailhead:map`](#the-map) minus destination, decisions, and fog (the whiteboard has no direction, only loose work), by name:
|
|
384
|
+
- **Frontier**: takeable now (open, unassigned, not blocked/unverified whiteboard tickets), each with its type.
|
|
385
|
+
- **In progress**: claimed whiteboard tickets, with who holds each.
|
|
386
|
+
- **Blocked**: any blocked whiteboard ticket, with what it waits on (a whiteboard ticket can still block on another).
|
|
387
|
+
|
|
388
|
+
**Dashboard self-heal.** Like the map render, ensure the repo's pinned `trailhead:dashboard` index exists and is pinned before rendering (create + pin only if missing, re-pin if the pin was dropped; never rewrite its body here). See [The dashboard's self-heal note](#the-dashboard-a-pinned-repo-index).
|
|
389
|
+
|
|
390
|
+
**Working the whiteboard.** Take a ticket with [`quick <n>`](#quick-work-one-ticket-whole-off-the-map), or `work <n>` by number; both work a single whiteboard ticket. There is no book-keeping to fold back (no `Decisions so far`, no fog to graduate), so a whiteboard resolution is just the resolution comment, `gh issue close`, and the [Session handoff](#session-handoff). Whiteboard tickets are never pinned and take no map slot.
|
|
391
|
+
|
|
392
|
+
## The dashboard (a pinned repo index)
|
|
393
|
+
|
|
394
|
+
A repo's trailhead surface is spread across issues: one or more maps, a whiteboard, an inbox. The **dashboard** is the single pinned entry point that indexes all of it: a permanent, repo-scoped issue labelled **`trailhead:dashboard`**, created once at the first chart/adopt and occupying the **fixed 3rd pinned slot** alongside `trailhead:codebase` and `trailhead:conventions` (see [Substrate](#substrate-github-issues)). Maps themselves are **never pinned**: a pin is server-side and global to the repo, but the active map is client-side per-checkout state (`.trailhead/active-map`, gitignored), so no single map is "the" map to pin. The dashboard removes that category error and gives one stable pinned index that scales to any number of maps and to the whiteboard.
|
|
395
|
+
|
|
396
|
+
It holds:
|
|
397
|
+
- a link to **every open `trailhead:map`** (found via the `trailhead:map` label); GitHub renders each map's sub-issue **progress bar natively**, so the dashboard only links, it never recomputes per-ticket progress;
|
|
398
|
+
- the **whiteboard** as its own section (frontier / in-progress / blocked, or a link to the `/trailhead:whiteboard` view);
|
|
399
|
+
- dynamic **counts**: untriaged inbox size, whiteboard frontier size.
|
|
400
|
+
|
|
401
|
+
**`/trailhead:dashboard`** regenerates the pinned issue body from the live tracker and shows it, both the render and the on-demand refresh, consistent with `/trailhead:map` and `/trailhead:whiteboard`. It creates and pins the issue if the repo doesn't have one yet (ensuring the `trailhead:dashboard` label first, idempotently, like the whiteboard label).
|
|
402
|
+
|
|
403
|
+
**Freshness: structural events + on demand.** The pinned issue is rewritten when the surface changes **structurally**, a map is **charted** (Mode 1/1-bis) or **exhausted** (a map appears or disappears), and **on demand** via `/trailhead:dashboard`. It is **not** rewritten on every ticket resolve: that would churn a pinned issue (and its notifications) constantly, and the native progress bars already track per-ticket progress. Links + counts stay good enough refreshed at structural events and on demand.
|
|
404
|
+
|
|
405
|
+
**Self-heal (read-only entry points).** A repo that adopted trailhead before the dashboard existed, or one whose pin was later dropped, has no pinned index until the next chart/adopt or an explicit `/trailhead:dashboard`. To close that gap, the frequent **read-only entry points self-heal the pin**: the bare `/trailhead` [smart entry](#commands), the [`/trailhead:map`](#the-map) render, and the [`/trailhead:whiteboard`](#the-whiteboard-map-less-tickets) render each ensure the dashboard exists and is pinned before rendering, idempotently and cheaply, mirroring the "ensure the label exists before applying it" pattern: ensure the `trailhead:dashboard` label, then
|
|
406
|
+
- **if no dashboard issue exists**, create it, write the current surface once, and pin it (the fixed 3rd slot);
|
|
407
|
+
- **if it exists but is unpinned** (a dropped pin is exactly the drift to self-heal), re-pin it;
|
|
408
|
+
- **if it exists and is already pinned**, do nothing.
|
|
409
|
+
|
|
410
|
+
**Create + pin only when missing: never rewrite the body on these renders.** They are frequent read-only views, not the on-demand refresh, so the freshness rule above holds unbroken (`/trailhead:dashboard` and structural events stay the only writers of the body). The one body write these paths ever make is the initial one at creation (the dashboard appearing is itself a structural event).
|
|
339
411
|
|
|
340
412
|
## Working as a team
|
|
341
413
|
|
|
@@ -354,10 +426,10 @@ The user invokes with a loose idea.
|
|
|
354
426
|
|
|
355
427
|
1. **Name the destination.** Run the **Grilling** + **Domain vocabulary** techniques to pin down what this map tends toward. The destination fixes the scope, so it's settled first. Default: a working artifact.
|
|
356
428
|
2. **Map the frontier.** Grill again, **breadth-first**: fan out across the whole space rather than deep on one thread, surfacing the open decisions and the first steps takeable now. **If no fog surfaces** (the way is already clear, the whole thing fits one session) you don't need a map: stop and ask how to proceed. **Harvest parked work:** from **earlier maps on this repo**, gather the artifacts their *Out of scope* `→ future map` breadcrumbs point at (**parked seeds/todos**, see [Out of scope](#out-of-scope)) **and the ideas** left in their *Not yet specified* fog, then **ask the user which belong in *this* map**; pull the chosen seeds/todos in as tickets and the chosen ideas in as fog (or as tickets, if now sharp enough), and leave (or update the destination on) the rest.
|
|
357
|
-
3. **Create the map** (`trailhead:map`): Destination and Notes filled in, Decisions-so-far empty, the fog sketched into *Not yet specified*. Run the [first-use repo setup](#substrate-github-issues) now: labels **and** the label-guard check/install. Once the map issue has its number `<n>`, **create its per-map label `trailhead:map-<n>`** (every ticket of this map carries it and is a native sub-issue of the map; the map issue itself is found by its number, so it does **not** carry the label; see [Multiple maps on one repo](#multiple-maps-on-one-repo-parking--concurrency)). **If this repo already has another open `trailhead:map`**, tell the user the new map runs concurrently (its own scoped frontier) and set it as the active map (`.trailhead/active-map`). Then set up **conventions**: if no `trailhead:conventions` issue exists, run a **brief conventions brainstorming** (breadth-first, in the spirit of grilling but short: a handful of questions, not a full session) to surface the project's way of working: `git:` `main`|`pr`, `release:` `command`|`auto`, and `isolation:` `none`|`worktree` (the header, the last asked as *"will more than one session run on a single clone at once?"* → `worktree`; a monorepo leans `worktree`), **plus** the narrative a new contributor would need: release/deploy specifics (environments, manual steps, OTA vs full build, versioning), tooling that must be driven a certain way (a specific MCP/CLI, e.g. a Lovable project piloted via its MCP), code & style rules, testing/UAT norms. Distil into the issue (header + prose); link it from the map's Notes. Don't invent: leave out or ask what you can't determine. If one already exists, just link it (reuse across maps).
|
|
429
|
+
3. **Create the map** (`trailhead:map`): Destination and Notes filled in, Decisions-so-far empty, the fog sketched into *Not yet specified*. Run the [first-use repo setup](#substrate-github-issues) now: labels **and** the label-guard check/install. Once the map issue has its number `<n>`, **create its per-map label `trailhead:map-<n>`** (every ticket of this map carries it and is a native sub-issue of the map; the map issue itself is found by its number, so it does **not** carry the label; see [Multiple maps on one repo](#multiple-maps-on-one-repo-parking--concurrency)). **If this repo already has another open `trailhead:map`**, tell the user the new map runs concurrently (its own scoped frontier) and set it as the active map (`.trailhead/active-map`). Then set up **conventions**: if no `trailhead:conventions` issue exists, run a **brief conventions brainstorming** (breadth-first, in the spirit of grilling but short: a handful of questions, not a full session) to surface the project's way of working: `git:` `main`|`pr`, `release:` `command`|`auto`, and `isolation:` `none`|`worktree` (the header, the last asked as *"will more than one session run on a single clone at once?"* → `worktree`; a monorepo leans `worktree`), **plus** the narrative a new contributor would need: release/deploy specifics (environments, manual steps, OTA vs full build, versioning), tooling that must be driven a certain way (a specific MCP/CLI, e.g. a Lovable project piloted via its MCP), code & style rules, testing/UAT norms. Distil into the issue (header + prose); link it from the map's Notes. Don't invent: leave out or ask what you can't determine. If one already exists, just link it (reuse across maps). Finally, **ensure the repo's [`trailhead:dashboard`](#the-dashboard-a-pinned-repo-index) index issue exists**: create and pin it if missing (the fixed 3rd pinned slot, alongside codebase + conventions), then **write it to list this map** (charting a map is a structural event); if it already exists, refresh it.
|
|
358
430
|
4. **Create the tickets you can specify now** as child issues (each with `trailhead:ticket` + its type + this map's `trailhead:map-<n>` label, and set as a **native sub-issue** of the map, see [Child→map link](#substrate-github-issues)), then wire the blocking in a **second pass** (issues need ids before they can reference each other): for each ticket **read its Question and wire a blocker for every still-open ticket whose output it consumes** (don't stop at the first/obvious one), then run the three-move wiring (`## Blocked by` line + native dependency + `trailhead:blocked` label, see [Substrate](#substrate-github-issues)). Wiring sorts them into frontier (unblocked) and blocked; the rest stays fog.
|
|
359
431
|
5. **Fire the research subagents.** For each `research` ticket created, run the **Research** technique in parallel, findings on a `research/<name>` branch with a pointer from the ticket.
|
|
360
|
-
6. **Offer config, then stop.** Charting is one session's work; it hand-resolves nothing. Before ending, make the **first-time config offer** (see [smart entry](#commands)): the project now has a fresh map and no `.trailhead/config.json`, so put the **two-way choice** to the user, **accept as-is** (writes `{}`) or **run `/trailhead:config`** for the guided menu, and let them pick. Name both paths; don't pre-fill the config keys and ask for an inline edit, and don't interview them key-by-key here (that's `/trailhead:config`'s job). Once the file exists it's never offered again. **No `trailhead:codebase` issue is created here**: greenfield has no code to map yet. It's created later, once the repo has substantial code (see Mode 2 step 1 and the [Codebase](#substrate-github-issues) row), so at chart
|
|
432
|
+
6. **Offer config, then stop.** Charting is one session's work; it hand-resolves nothing. Before ending, make the **first-time config offer** (see [smart entry](#commands)): the project now has a fresh map and no `.trailhead/config.json`, so put the **two-way choice** to the user, **accept as-is** (writes `{}`) or **run `/trailhead:config`** for the guided menu, and let them pick. Name both paths; don't pre-fill the config keys and ask for an inline edit, and don't interview them key-by-key here (that's `/trailhead:config`'s job). Once the file exists it's never offered again. **No `trailhead:codebase` issue is created here**: greenfield has no code to map yet. It's created later, once the repo has substantial code (see Mode 2 step 1 and the [Codebase](#substrate-github-issues) row), so at chart two of the three permanent pins (the dashboard + conventions) are filled and the codebase slot fills in later. Maps are never pinned.
|
|
361
433
|
|
|
362
434
|
### Mode 1-bis: Adopt an existing project, `/trailhead:adopt`
|
|
363
435
|
When the code already exists (project in progress). Like Mode 1, but start from **reality**, not a blank page. One heavy step at entry, then all lean.
|
|
@@ -367,13 +439,13 @@ When the code already exists (project in progress). Like Mode 1, but start from
|
|
|
367
439
|
3. **Backfill the decisions already made.** Choices already embodied in the code (now visible from the map) or stated by the user go into `Decisions so far` as **ticket-less** lines (they're already closed), so the map reflects reality and doesn't pretend greenfield. Link to code/commits where useful.
|
|
368
440
|
4. **Map the frontier of the remainder** → tickets specifiable now + fog in *Not yet specified*, then wire the blocking and fire the research. **Harvest parked work** (todos *and* ideas) from earlier maps on this repo (their `→ future map` *Out of scope* lines and their *Not yet specified* fog), asking the user which belong here, as in Mode 1 step 2. As Mode 1, steps 4–6.
|
|
369
441
|
|
|
370
|
-
The tracker is the existing repo's (`gh` in its directory); run the [first-use repo setup](#substrate-github-issues) on first use (missing `trailhead:*` labels **and** the label-guard check/install (adopting an existing repo is exactly when the guard tends to be missing)) and set up the `trailhead:conventions` issue via a **brief conventions brainstorming** (see Mode 1 step 3), but here, **seed the questions from what's discoverable** so you confirm rather than ask blind: an existing `CLAUDE.md`/`AGENTS.md`, CI workflows, the PR-vs-`main` git history, package scripts, deploy config. **Detect a monorepo** (a `pnpm-workspace.yaml`, a `workspaces` field, an `nx.json`/`turbo.json`, or several packages under `packages/`|`apps/`) and, if found, propose `isolation: worktree` and note that tickets should carry a `Scope:` line, **unless the buildable units are path-bound** (see below). **Detect git submodules** (a `.gitmodules` file) and, if found, add the prose rule that submodule work is committed **inside the submodule + a gitlink bump in the parent**, isolated at the submodule (not the superproject); a submodule path is a natural `Scope:` (see [Working as a team](#working-as-a-team)). **Before proposing `worktree`, check for path-bound tooling** (a React Native/Expo app, native toolchains, a package whose build depends on a local `node_modules`/bundler cache/absolute-path config): those don't build from a worktree, so for them propose **`isolation: clone`** (a per-ticket clone that installs its own deps and builds), or `isolation: none` + **serialize** if the user doesn't want per-ticket clones. Worktree is for units that build from any path. Propose the values you inferred and let the human correct/complete them; create the issue if absent, link it from the map's Notes.
|
|
442
|
+
The tracker is the existing repo's (`gh` in its directory); run the [first-use repo setup](#substrate-github-issues) on first use (missing `trailhead:*` labels **and** the label-guard check/install (adopting an existing repo is exactly when the guard tends to be missing)) and set up the `trailhead:conventions` issue via a **brief conventions brainstorming** (see Mode 1 step 3), but here, **seed the questions from what's discoverable** so you confirm rather than ask blind: an existing `CLAUDE.md`/`AGENTS.md`, CI workflows, the PR-vs-`main` git history, package scripts, deploy config. **Detect a monorepo** (a `pnpm-workspace.yaml`, a `workspaces` field, an `nx.json`/`turbo.json`, or several packages under `packages/`|`apps/`) and, if found, propose `isolation: worktree` and note that tickets should carry a `Scope:` line, **unless the buildable units are path-bound** (see below). **Detect git submodules** (a `.gitmodules` file) and, if found, add the prose rule that submodule work is committed **inside the submodule + a gitlink bump in the parent**, isolated at the submodule (not the superproject); a submodule path is a natural `Scope:` (see [Working as a team](#working-as-a-team)). **Before proposing `worktree`, check for path-bound tooling** (a React Native/Expo app, native toolchains, a package whose build depends on a local `node_modules`/bundler cache/absolute-path config): those don't build from a worktree, so for them propose **`isolation: clone`** (a per-ticket clone that installs its own deps and builds), or `isolation: none` + **serialize** if the user doesn't want per-ticket clones. Worktree is for units that build from any path. Propose the values you inferred and let the human correct/complete them; create the issue if absent, link it from the map's Notes. Also **ensure the repo's [`trailhead:dashboard`](#the-dashboard-a-pinned-repo-index) index issue exists**: create and pin it if missing (the fixed 3rd pinned slot, alongside codebase + conventions) and write it to list this map.
|
|
371
443
|
|
|
372
444
|
### Mode 2: Work the map, `/trailhead:work`
|
|
373
445
|
The user invokes with a map (URL or number). A ticket is optional: without one, you pick the next decision.
|
|
374
446
|
|
|
375
|
-
1. Load the **map** (the low-res view, not every ticket body). **Pick which map** if the repo has several open: use the one the user named, else the active map (`.trailhead/active-map`); if neither and more than one is open, list them and ask. Set/refresh the active-map marker to the one you're working. See [Multiple maps on one repo](#multiple-maps-on-one-repo-parking--concurrency). **Codebase issue check (greenfield, offer once):** if the repo has **no `trailhead:codebase` issue** (a greenfield map never got one) and now holds **substantial code**, offer once to generate it via the [Codebase map](#techniques) technique, then pin it (
|
|
376
|
-
2. Choose the ticket. If the user names one, use it. Otherwise the first frontier ticket in order, **scoped to this map** (add its `trailhead:map-<n>` label to the frontier query when several maps are live). **Claim** it: assign it to yourself before any work, then re-read the assignee before starting; on a collision, stop and ask the user (see [Working as a team](#working-as-a-team)). If the ticket proves too big once you're in it, **split** it rather than grind (same section).
|
|
447
|
+
1. Load the **map** (the low-res view, not every ticket body). **Pick which map** if the repo has several open: use the one the user named, else the active map (`.trailhead/active-map`); if neither and more than one is open, list them and ask. Set/refresh the active-map marker to the one you're working. See [Multiple maps on one repo](#multiple-maps-on-one-repo-parking--concurrency). **Codebase issue check (greenfield, offer once):** if the repo has **no `trailhead:codebase` issue** (a greenfield map never got one) and now holds **substantial code**, offer once to generate it via the [Codebase map](#techniques) technique, then pin it (one of the three permanent repo pins: dashboard, codebase, conventions) and link it from the map's Notes; if the user declines, don't nag again this session. A repo that already has the issue, or is still too thin to map, skips this.
|
|
448
|
+
2. Choose the ticket. If the user names one, use it (a **named** ticket may be a `trailhead:whiteboard` one: `work <n>` works it whole, off the map, with the normal cycle and split allowed; use [`quick <n>`](#quick-work-one-ticket-whole-off-the-map) for the no-split variant). Otherwise the first frontier ticket in order, **scoped to this map** (add its `trailhead:map-<n>` label to the frontier query when several maps are live) and **never a whiteboard ticket** (bare `work` stays on the map; the whiteboard frontier is reached via `/trailhead:whiteboard` and `quick`). **Claim** it: assign it to yourself before any work, then re-read the assignee before starting; on a collision, stop and ask the user (see [Working as a team](#working-as-a-team)). If the ticket proves too big once you're in it, **split** it rather than grind (same section).
|
|
377
449
|
- **First, if the conventions header has NO `isolation:` key at all** (never chosen, so silently defaulting to `none`), sanity-check it **once** before working: if there's a real collision risk, don't just work the shared checkout. The risk signals are **another in-progress ticket** (a concurrent session is plausible) or **path-bound tooling in this ticket's `Scope:`** (a React Native/Expo app, native toolchain, a local `node_modules`). On a signal, surface a one-line suggestion naming it and the fitting mode (`worktree` for builds that run from any path, `clone` for path-bound apps) and offer to set it now. Whatever the user picks (**including "keep `none`"**), **write the `isolation:` key into the conventions issue** so the choice is recorded and this never prompts again. If the key is already present (any value, `none` included), it was chosen: respect it and skip this check. This is the one convention worth surfacing (it changes where you work); don't nag beyond writing the key once.
|
|
378
450
|
- **Under `isolation: none`, check the scope is free before starting** (this is how a submodule/package gets serialised when worktrees don't fit): scan the other in-progress tickets' `Scope:` lines, and if one **overlaps** this ticket's scope, **stop and tell the user** the scope is busy (name the ticket + holder) and offer a disjoint-scope frontier ticket or to wait. See [Working as a team](#working-as-a-team).
|
|
379
451
|
- **Write the session-ticket marker** (a cheap hint for tooling like a statusline): at the **working root** (the checkout, worktree, or clone you'll edit in), write `.trailhead/session-ticket` as **one line** `#<n> <ticket title>`. **Keep it gitignored** (add `.trailhead/session-ticket` to that repo's `.gitignore` if absent): it's per-session local state, **never committed**. It just lets an external tool show "what am I on" offline; the tracker stays the source of truth. Remove it at Resolve/handoff (see [Session handoff](#session-handoff)); on **Pause** it may stay (you'll return) or be cleared if you release the claim. The **`.trailhead/active-map`** marker (which map this session works) follows the same rules: gitignored, per-session, never committed; see [Multiple maps on one repo](#multiple-maps-on-one-repo-parking--concurrency).
|
|
@@ -381,7 +453,7 @@ The user invokes with a map (URL or number). A ticket is optional: without one,
|
|
|
381
453
|
3. Resolve it with its type's engine, **zoom as needed**: fetch the full body of related/closed tickets on demand. If in doubt on a `decision` ticket, run **Grilling** + **Domain vocabulary**. If in doubt on a `build`, **stop and ask** (see the Discuss step): never auto-grill.
|
|
382
454
|
4. Record the resolution: a comment with the answer, `gh issue close`, add the pointer to *Decisions so far*. Then **unblock dependents**: for every ticket this one was blocking, if it was the last open blocker, remove its `trailhead:blocked` label so it graduates onto the frontier.
|
|
383
455
|
5. Add newly-surfaced tickets (create-then-wire, labelling blocked ones `trailhead:blocked`); graduate the fog that became specifiable, clearing the patch from *Not yet specified*. If the answer reveals a ticket sits beyond the destination, apply the **out-of-scope vs deferred** test (see [Out of scope](#out-of-scope)): if it's really deferred on something outside the map, ask the user to route it to a `seed`/`idea`/`todo`; only if it's truly beyond the destination rule it **out of scope** (label + close) instead of resolving it. If the decision invalidates other parts of the map, update or delete them.
|
|
384
|
-
6. **Hand off.** Close the session with the **[Session handoff](#session-handoff)** ritual: confirm the ticket is resolved by name, then the scannable **next-step block with `/clear` first** (never the next command without it), naming the next frontier ticket and giving its number, or `/trailhead:map` if the frontier is empty/ambiguous. This holds in any later follow-up too, not only at the moment of resolution. **If this resolution leaves the map exhausted** (no open tickets and no fog left: the destination is reached), say so; but **before closing it out, if *Out of scope* holds any deferred line** (apply the [out-of-scope-vs-deferred](#out-of-scope) tells), **stop and ask the user what to do with each** (a `seed` gated on its trigger, an `idea`, or a `todo`, in this or another map), so nothing wanted-later is lost when the map goes quiet. Only once the deferred items are routed (or the user says leave them), **
|
|
456
|
+
6. **Hand off.** Close the session with the **[Session handoff](#session-handoff)** ritual: confirm the ticket is resolved by name, then the scannable **next-step block with `/clear` first** (never the next command without it), naming the next frontier ticket and giving its number, or `/trailhead:map` if the frontier is empty/ambiguous. This holds in any later follow-up too, not only at the moment of resolution. **If this resolution leaves the map exhausted** (no open tickets and no fog left: the destination is reached), say so; but **before closing it out, if *Out of scope* holds any deferred line** (apply the [out-of-scope-vs-deferred](#out-of-scope) tells), **stop and ask the user what to do with each** (a `seed` gated on its trigger, an `idea`, or a `todo`, in this or another map), so nothing wanted-later is lost when the map goes quiet. Only once the deferred items are routed (or the user says leave them), **ask the user whether to close the map issue** now that the destination is reached: `gh issue close` it on a yes (it stays the record), leave it open on a no; never close a map unprompted. Either way **refresh the dashboard** so an exhausted/closed map drops off (a structural event). Maps aren't pinned, so there's no pin to free; the dashboard, codebase, and conventions stay pinned regardless.
|
|
385
457
|
|
|
386
458
|
### Mode 3: Capture, `/trailhead:bug|todo|idea|seed|note`
|
|
387
459
|
The user fires a capture on the fly → route it per the **Capture** section and confirm with one line.
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Capture without breaking flow, but with the **tracker** as destination, not scattered files. The verbs are in the **Capture** table in SKILL.md's Commands section; here is the detail of each. One capture = one action + one confirmation line; it resolves nothing.
|
|
4
4
|
|
|
5
|
+
**Language (do not skip):** the captured ticket (title + body) is written in `config.ticket.language` (default `en`), **regardless of the language you are chatting in**. A capture fired in Italian still lands as an English ticket. This is the easiest place to slip, because a capture echoes the user's one-line phrasing; translate it, do not mirror it. See SKILL.md's *Ticket language* rule.
|
|
6
|
+
|
|
7
|
+
**Map or whiteboard:** a capture that produces a **ticket** (`todo`, `bug`, `seed`, and `idea` when it is sharp enough) must land on a **map** or on the **whiteboard** (map-less). **When a map is open, ask the user which** before creating it (active map vs whiteboard; several maps open -> the ask also picks the map). Whiteboard tickets get the `trailhead:whiteboard` label and **no** map label or `Parent:`; map tickets get the map's `trailhead:map-<n>` label + native sub-issue edge. **No map open at all -> whiteboard, no ask** (nowhere else to go). `note` and a non-sharp `idea` stay map-fog and skip this; with no open map they fall back to the global notes location (`$HOME/.claude/notes/`), or a now-sharp `idea` becomes a whiteboard ticket. See SKILL.md's *Capture* and *The whiteboard*.
|
|
8
|
+
|
|
5
9
|
- **`note`** → a verbatim line in `## Not yet specified` (outside a project → `$HOME/.claude/notes/`). No questions, no rewriting.
|
|
6
10
|
- **`idea`** → fog by default in `## Not yet specified`; becomes a ticket only if already phrasable as a sharp question. An idea in the fog is **not executable**: to work it, it must first **graduate** into a ticket (which happens when the frontier reaches it, or when you can now phrase it sharply). A `todo`, by contrast, is born a `build` ticket and is workable immediately.
|
|
7
11
|
- **`todo`** → a small `trailhead:build` ticket on the frontier (defined work you will do: a todo is just a small build ticket). If it's a parking spot *beyond* the destination, it goes in `## Out of scope` instead.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Code review
|
|
2
|
-
Runs after execute, before closing a `build`/`bug` ticket: as a subagent when the diff is non-trivial, inline for a tiny change.
|
|
2
|
+
Runs after execute, before closing a `build`/`bug` ticket: as a subagent when the diff is non-trivial, inline for a tiny change. Spawn it as the built-in **`Explore`** agent (read-only: it reviews and reports, it must never Edit/Write or commit); **never** a specialised reviewer agent that another installed tool registered, which follows a different protocol and may write its own review file instead of trailhead's `VERIFY` comment.
|
|
3
3
|
|
|
4
4
|
**Review adversarially; don't defer to authority.** A review subagent still loads the project/global `CLAUDE.md` (Claude Code doesn't strip it per-spawn; auto-memory, though, is *not* inherited). Treat those conventions (and any "this decision is settled / final / don't revisit" note) as **non-authoritative for your verdict**: judge the diff against the ticket's spec and the code itself, never rubber-stamp because a memory or a CLAUDE.md says a choice is closed. Convention endorsement may suppress a *style* smell; it may **never** suppress a correctness or security finding. The point of an independent review is to disagree when the code warrants it.
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Codebase map (fan-out)
|
|
2
|
-
One-time at project adoption. Fan out 5 read-only reader subagents in parallel (single message), each owning ONE non-overlapping facet; no facet reads another's territory
|
|
2
|
+
One-time at project adoption. Fan out 5 read-only reader subagents in parallel (single message), each owning ONE non-overlapping facet; no facet reads another's territory. Spawn each reader as the built-in **`Explore`** agent (read-only by construction); **never** a specialised mapper agent that another installed tool registered.
|
|
3
3
|
- **Architecture**: pattern, layers, data flow, entry points, module seams; where the deep modules live.
|
|
4
4
|
- **Stack & dependencies**: languages, runtime, frameworks, manifests, build/config, external integrations (DBs, APIs, auth, webhooks).
|
|
5
5
|
- **Decisions already embodied / conventions**: style, naming, error handling, the repo's documented standards; patterns a new change must match.
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Debug (scientific method)
|
|
2
2
|
A bug is a science problem: observe, hypothesize, test, confirm, verify. Never patch on a hunch: an unconfirmed fix is a guess that recurs. Treat the issue as persistent debug state: log every hypothesis, test, and eliminated cause as comments, so a context reset never restarts the investigation.
|
|
3
3
|
|
|
4
|
+
When run as a subagent, spawn it as the built-in **`general-purpose`** agent (it adds instrumentation and regression tests, so it needs Write); **never** a specialised debugger agent that another installed tool registered.
|
|
5
|
+
|
|
4
6
|
**Redact as you go.** These notes are posted on public Issues. When you show a command, output, or artifact, replace every secret with `<REDACTED>` and run loops against env vars, never inlined credentials; quote only the signal-carrying lines, not whole dumps. If a redacted artifact isn't enough to move forward, say so and ask the user; never paste the raw one to compensate.
|
|
5
7
|
|
|
6
8
|
1. **Reproduce: build a feedback loop.** Get ONE command you've actually run that goes red on *this exact symptom* and green when fixed: a failing test at the nearest seam, a curl against a dev server, a CLI diff vs a known-good snapshot, a replayed trace. Tighten it: faster, sharper (assert the specific symptom), deterministic (pin time, seed RNG, freeze network). No red-capable command → do NOT hypothesize; say so, list what you tried, ask for env access or a redacted artifact. For flaky bugs, chase a *higher* repro rate (loop 100×, parallelize, widen timing windows). 50% is debuggable, 1% is not.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Research
|
|
2
|
-
Spawn a focused subagent (AFK, parallelisable) with exactly ONE clear question: sharp enough that the answer is a decision, not a survey. Vague scope breeds rabbit holes; a narrow question is its own leash.
|
|
2
|
+
Spawn a focused subagent (AFK, parallelisable) with exactly ONE clear question: sharp enough that the answer is a decision, not a survey. Vague scope breeds rabbit holes; a narrow question is its own leash. Spawn it as the built-in **`general-purpose`** agent (it writes findings to a throwaway `research/<name>` branch, so it needs Write); **never** a specialised researcher agent that another installed tool registered.
|
|
3
3
|
|
|
4
4
|
- **Treat your own prior knowledge as hypothesis, not fact**: training is stale. "As of my training" is a warning flag, not a citation.
|
|
5
5
|
- **Chase primary, high-trust sources**: official docs, source code, specs, first-party APIs; never a secondary write-up. Follow every claim back to the source that owns it; resolve *current* docs rather than reciting from memory.
|