@marcomigozzi/trailhead 0.1.0 → 0.2.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 +159 -98
- package/bin/trailhead.js +14 -3
- package/package.json +2 -4
- package/plugins/trailhead/.claude-plugin/plugin.json +2 -4
- package/plugins/trailhead/commands/adopt.md +2 -2
- package/plugins/trailhead/commands/bug.md +1 -1
- package/plugins/trailhead/commands/config.md +1 -1
- package/plugins/trailhead/commands/grill.md +1 -1
- package/plugins/trailhead/commands/idea.md +1 -1
- package/plugins/trailhead/commands/inbox.md +1 -1
- package/plugins/trailhead/commands/map.md +1 -1
- package/plugins/trailhead/commands/new.md +1 -1
- package/plugins/trailhead/commands/note.md +1 -1
- package/plugins/trailhead/commands/pause.md +1 -1
- package/plugins/trailhead/commands/resume.md +1 -1
- package/plugins/trailhead/commands/seed.md +2 -2
- package/plugins/trailhead/commands/split.md +1 -1
- package/plugins/trailhead/commands/ticket.md +1 -1
- package/plugins/trailhead/commands/todo.md +2 -2
- package/plugins/trailhead/commands/update.md +5 -0
- package/plugins/trailhead/commands/work.md +1 -1
- package/plugins/trailhead/hooks/hooks.json +14 -0
- package/plugins/trailhead/hooks/trailhead-check-update.js +125 -0
- package/plugins/trailhead/hooks/trailhead-commit-guard.js +3 -3
- package/plugins/trailhead/hooks/trailhead-issue-injection-scanner.js +5 -5
- package/plugins/trailhead/hooks/trailhead-secret-guard.js +131 -0
- package/plugins/trailhead/hooks/trailhead-secret-guard.test.js +71 -0
- package/plugins/trailhead/skills/trailhead/SKILL.md +176 -208
- package/plugins/trailhead/skills/trailhead/references/capture.md +20 -0
- package/plugins/trailhead/skills/trailhead/references/configuration.md +44 -16
- package/plugins/trailhead/skills/trailhead/references/inbox.md +29 -0
- package/plugins/trailhead/skills/trailhead/references/teamwork.md +87 -0
- package/plugins/trailhead/skills/trailhead/references/techniques/acceptance-testing.md +6 -2
- package/plugins/trailhead/skills/trailhead/references/techniques/code-review.md +10 -6
- package/plugins/trailhead/skills/trailhead/references/techniques/codebase-map.md +9 -7
- package/plugins/trailhead/skills/trailhead/references/techniques/debug.md +11 -9
- package/plugins/trailhead/skills/trailhead/references/techniques/domain-vocabulary.md +6 -5
- package/plugins/trailhead/skills/trailhead/references/techniques/grilling.md +8 -8
- package/plugins/trailhead/skills/trailhead/references/techniques/plan-review.md +14 -5
- package/plugins/trailhead/skills/trailhead/references/techniques/prototype.md +10 -8
- package/plugins/trailhead/skills/trailhead/references/techniques/research.md +7 -7
- package/plugins/trailhead/skills/trailhead/references/techniques/tdd.md +6 -6
- package/plugins/trailhead/skills/trailhead/references/updating.md +42 -0
- package/plugins/trailhead/templates/statusline-trailhead.sh +344 -0
- package/plugins/trailhead/templates/trailhead-label-guard.yml +2 -2
|
@@ -1,33 +1,35 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: trailhead
|
|
3
|
-
description: Start and drive a large project
|
|
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
4
|
argument-hint: "[new|adopt|work|ticket|inbox|map|config|grill|split|pause|resume|bug|todo|idea|seed|note] [text | ticket]"
|
|
5
5
|
---
|
|
6
6
|
|
|
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
|
|
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.
|
|
8
8
|
|
|
9
|
-
The method fuses two
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
9
|
+
The method fuses two halves; `trailhead`'s core invokes no other skill and depends on nothing but an authenticated `gh` CLI (the one opt-in exception: `claude.ai/design` mockup mode uses **DesignSync** (the `claude_design` MCP + `/design-sync` skill) to push to a design-system project, and falls back to disk if it's unavailable):
|
|
10
|
+
- **The map.** Question-driven onboarding, the destination named first, the frontier, the fog of war, reference *by name*, one ticket per session.
|
|
11
|
+
- **The engine.** Zero-friction capture and the `discuss → plan → execute → verify` cycle with atomic commits that resolves each build ticket *inside* the map.
|
|
12
12
|
|
|
13
|
-
The techniques
|
|
13
|
+
The techniques commonly packaged as separate skills (grilling, TDD, systematic debugging, codebase mapping, code review) are **built in here as inline protocols** (see [Techniques](#techniques)). `trailhead` is self-contained.
|
|
14
14
|
|
|
15
15
|
**Everything lives on the Issues.** No `.planning/`: the map is the parent issue, each ticket is a child issue, and discussion / plan / verification are **comments** on the ticket. The repo holds code only.
|
|
16
16
|
|
|
17
17
|
## Requirements
|
|
18
18
|
|
|
19
|
-
Only an **authenticated `gh` CLI
|
|
19
|
+
Only an **authenticated `gh` CLI**: the tracker is GitHub Issues. Nothing else to install. The subagents `trailhead` spawns (research, codebase-map, review) use the built-in `Agent` tool; every technique is defined inline below.
|
|
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
|
|
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*; map present → *work* the next frontier ticket).
|
|
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
|
+
|
|
27
|
+
Every verb is also a **namespaced command** (`/trailhead:new`, `/trailhead:work`, `/trailhead:bug`, …) so typing `/trailhead` lists them all in the picker. They're thin wrappers that delegate here; `/trailhead <verb>` and `/trailhead:<verb>` are equivalent.
|
|
26
28
|
|
|
27
29
|
**Flow**
|
|
28
30
|
| Command | What it does |
|
|
29
31
|
|---|---|
|
|
30
|
-
| `/trailhead` | smart entry
|
|
32
|
+
| `/trailhead` | smart entry: detect and propose |
|
|
31
33
|
| `/trailhead:new [idea]` | chart a new map → Mode 1 |
|
|
32
34
|
| `/trailhead:adopt` | adopt an existing project → Mode 1-bis |
|
|
33
35
|
| `/trailhead:work [ticket]` | work the map: the next frontier ticket, or the one you name → Mode 2 |
|
|
@@ -37,117 +39,130 @@ Every verb is also a **namespaced command** — `/trailhead:new`, `/trailhead:wo
|
|
|
37
39
|
| `/trailhead:split [ticket]` | split an oversized ticket into children and supersede the original |
|
|
38
40
|
| `/trailhead:ticket <type> <title>` | open one child ticket of the map on the fly (`type` = decision\|research\|prototype\|build\|bug\|task) |
|
|
39
41
|
| `/trailhead:grill [topic]` | run a standalone grilling session on a decision/topic (or a named ticket) |
|
|
40
|
-
| `/trailhead:map` | show the low-res map (destination, decisions, frontier, fog) |
|
|
42
|
+
| `/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)) |
|
|
41
43
|
| `/trailhead:config [get\|set …]` | show the effective config (global + project), or set a key |
|
|
44
|
+
| `/trailhead:update` | check for a newer trailhead and install it where safe (see `references/updating.md`) |
|
|
42
45
|
|
|
43
|
-
**Capture**
|
|
46
|
+
**Capture** (zero friction, one confirmation line, resolves nothing → Mode 3):
|
|
44
47
|
| Command | Destination |
|
|
45
48
|
|---|---|
|
|
46
49
|
| `/trailhead:bug [--of <ticket>] <text>` | a `trailhead:bug` ticket on the frontier; `--of` records it as a `Regression of: <ticket>` |
|
|
47
|
-
| `/trailhead:todo <text>` | a small ticket on the frontier
|
|
50
|
+
| `/trailhead:todo <text>` | a small ticket on the frontier: work you *will* do |
|
|
48
51
|
| `/trailhead:idea <text>` | a line in *Not yet specified* (the fog); a ticket only if already razor-sharp |
|
|
49
52
|
| `/trailhead:seed <text>` | a blocked ticket with its trigger noted in `## Blocked by` |
|
|
50
53
|
| `/trailhead:note <text>` | a verbatim note (fog; outside a project → `$HOME/.claude/notes/`) |
|
|
51
54
|
|
|
52
55
|
Missing text in a capture → ask for the line. Unrecognised verb → treat the whole string as `idea`.
|
|
53
56
|
|
|
54
|
-
**Idea vs todo:** `idea` is a maybe-later thought (fog by default, graduates when the frontier reaches it); `todo` is defined work you will do (born a ticket). The test
|
|
57
|
+
**Idea vs todo:** `idea` is a maybe-later thought (fog by default, graduates when the frontier reaches it); `todo` is defined work you will do (born a ticket). The test: can you *phrase* the question precisely now? Yes → ticket, no → fog.
|
|
55
58
|
|
|
56
59
|
## Principles
|
|
57
60
|
|
|
58
|
-
- **Execution inside the map (default).** Unlike
|
|
59
|
-
- **Refer by name.** Every map and ticket is an issue, so it has a **title**. In everything the human reads, refer by name, never by a bare `#number`. A wall of `#42, #43, #44` is illegible; names read at a glance. The id and URL don't vanish
|
|
60
|
-
- **
|
|
61
|
-
- **
|
|
61
|
+
- **Execution inside the map (default).** Unlike a plan-only approach ("plan, don't do"), here the map carries **construction** within it: after the decision tickets, build tickets graduate from the fog and are **executed** as children of the map. The destination is the **working** artifact (a deployed app), not a spec document; the spec is a waypoint. *Override:* a project that must stop at the spec declares it in the map's `## Notes` (that wins over this default).
|
|
62
|
+
- **Refer by name.** Every map and ticket is an issue, so it has a **title**. In everything the human reads, refer by name, never by a bare `#number`. A wall of `#42, #43, #44` is illegible; names read at a glance. The id and URL don't vanish (a name wraps its link) but they ride *inside* the name. **One exception: a command the user must run.** In `/trailhead:work <ticket>` the argument is machine input, not prose; give the **bare number** there (a long title, with its punctuation and jargon, is awkward and error-prone to type). Name the ticket in the surrounding sentence, put the number in the command.
|
|
63
|
+
- **Result-oriented output.** In the chat, report **what was done and the next step**: refer to tickets by name and give the concrete next command. Do **not** expose trailhead's internal machinery: Mode labels (`Mode 1`, …), protocol/step names (`DISCUSS`/`PLAN`/`VERIFY`, "the build engine", "Session handoff", "graduate the fog", "the frontier"), or wrapper/SKILL.md mechanics. **Nor narrate the conventions and config you are silently obeying**: the git mode (`main`/`pr`), the isolation/worktree decision, submodule commit mechanics (commit-inside + gitlink bump), claim/assignment plumbing, which model ran a step. These are housekeeping you just *do*; reciting them ("the conventions say `git: main`, no worktree isolation, submodule work commits inside + a gitlink bump, and the ticket is already assigned to you") is noise, not a status update. Just act, then report the outcome. Surface a convention only when it **changes what the user must do** (a UAT they run, a release awaiting their command, a genuine claim collision with *another* login) or when they explicitly ask. (A map may reinforce this in its `## Notes`, but it holds regardless.)
|
|
64
|
+
- **No em-dashes.** Write with ordinary punctuation: commas, colons, semicolons, parentheses, periods. Never use an em-dash (U+2014), and never a hyphen standing in for a comma. This holds for **everything trailhead produces**: ticket titles and bodies, engine comments, the map body, commit descriptions, chat output, and the skill's own source docs. En-dashes in numeric ranges (`1–2`) and the arrow `→` in trailhead's notation stay; only the em-dash is banned.
|
|
65
|
+
- **One ticket per session: hard rule.** Resolve **at most one** ticket per session; once one is resolved, do **not** start a second; end the session instead. The only exceptions: `research` tickets (AFK, run in parallel) and `capture` operations (they resolve nothing). On resolving a ticket, close the session with the **[Session handoff](#session-handoff)** ritual.
|
|
66
|
+
- **Git per the repo's conventions.** The default is commit straight to `main`, conventional commits, no feature branch/PR, but the repo's `trailhead:conventions` issue governs: honour its `git:` (`main` | `pr`), `release:` (`command` | `auto`), and `isolation:` (`none` | `worktree`) header. Never `Co-Authored-By`. **Every commit made while resolving a ticket carries a `Refs: #<n>` trailer** (the ticket's issue number) as its last line, so GitHub threads the commit into that ticket's timeline (the commits show up *in* the ticket). Use a **non-closing** reference (`Refs:`): never a closing keyword (`fixes`/`closes`/`resolves #n`), because the ticket is closed **explicitly at Resolve** after Verify passes, not by a commit landing on the trunk. (Charting/config commits not tied to a ticket carry no `Refs:`.)
|
|
62
67
|
|
|
63
68
|
## Session handoff
|
|
64
69
|
|
|
65
|
-
Resolving a ticket **ends the session
|
|
70
|
+
Resolving a ticket **ends the session**: one ticket per session is a hard rule (see [Principles](#principles)). So every resolution closes the same way. After the resolution comment + `gh issue close` + the `Decisions so far` update, **remove the session-ticket marker** (`.trailhead/session-ticket` at the working root, if you wrote one, see Mode 2), then **always** sign off with, in order:
|
|
66
71
|
|
|
67
72
|
1. **A one-line confirmation** the ticket is resolved, by name (not a bare `#number`).
|
|
68
|
-
2. **
|
|
69
|
-
|
|
73
|
+
2. **The next step: a scannable block, `/clear` FIRST, always.** Never bury it in prose, and **never point at the next ticket without leading with `/clear`**. State the `/clear` **every time** (not only when context is heavy): it must come *before* the next command, so the next ticket starts on clean context. Render it as a short labelled list in the user's conversation language, along these lines:
|
|
74
|
+
|
|
75
|
+
> **Prossimo passo:**
|
|
76
|
+
> - `/clear`: per liberare il contesto
|
|
77
|
+
> - poi `/trailhead:work <numero>` per lavorare **<nome del prossimo ticket>** (oppure `/trailhead:work` che prende **<numero>** in automatico)
|
|
78
|
+
> - in alternativa `/trailhead:map` per la vista completa e scegliere tu
|
|
79
|
+
|
|
80
|
+
Name the next frontier ticket so the user knows what they're taking; put its **number** in the command (never the title). **No handoff command ever takes a free-text argument.** The only commands a handoff block may contain are `/clear`, `/trailhead:work` (bare or `<number>`), and `/trailhead:map`. Never hand the user a `/trailhead:ticket <type> "<title>"` (or any title-bearing command) to type.
|
|
81
|
+
|
|
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.)
|
|
83
|
+
|
|
84
|
+
**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.
|
|
70
85
|
|
|
71
86
|
This ritual fires from every **Resolve** step (`build`, `bug`) and from **Mode 2**'s close (which resolves `decision` / `prototype` / `task` too). It does **not** fire after a `capture` (resolves nothing) or a parallel `research` batch (AFK, not session-ending).
|
|
72
87
|
|
|
73
88
|
## Substrate: GitHub Issues
|
|
74
89
|
|
|
75
|
-
The tracker is GitHub via the `gh` CLI (account already authenticated). Conventions:
|
|
90
|
+
The tracker is GitHub via the `gh` CLI (account already authenticated). **Never post a secret to an issue**: everything trailhead writes (ticket bodies, engine comments, the codebase/conventions issues, resolutions) is public tracker text, so redact credentials proactively with `<REDACTED>` or an env-var reference (the Debug **Redact** protocol applies to *all* engine writes, not just debugging). A shipped **secret-guard hook** hard-blocks any `gh` issue/PR write that still contains one. If it fires, treat the block as a **clean-and-retry cue, not a dead end**: redact the flagged value in place (`<REDACTED>` or an env-var reference like `$SUPABASE_KEY`), then re-post, silently, without surfacing the block to the user as a failure. (The hook only *blocks*; it can't rewrite the write, which is deliberate: a fail-safe block never leaks, whereas a silent auto-redact that didn't apply would. The redaction is yours to do.) If the block looks like a false positive, reword so it no longer matches a credential pattern. Conventions:
|
|
76
91
|
|
|
77
92
|
| Element | How |
|
|
78
93
|
|----------|------|
|
|
79
|
-
| Map | an issue with label `trailhead:map` |
|
|
80
|
-
|
|
|
81
|
-
|
|
|
82
|
-
|
|
|
83
|
-
|
|
|
94
|
+
| Map | an issue with label `trailhead:map`. **Pin it** while it's live, along with the codebase and conventions issues: GitHub's **3 pinned-issue slots** are exactly map + codebase + conventions, so the repo's trailhead anchors stay one click away (`gh api graphql` `pinIssue` mutation on each issue's node id). **When a map is exhausted** (destination reached: no open tickets and no fog left), **unpin it** (`unpinIssue` mutation) to free its slot; the map issue stays open as the record, just unpinned. The **codebase and conventions stay pinned permanently** (repo-scoped); the next map charted on the repo takes the freed slot. **With several live maps at once** the 3rd slot holds the active/primary one and the others stay unpinned but listed, see [Multiple maps on one repo](#multiple-maps-on-one-repo-parking--concurrency). 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
|
+
| 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
|
+
| 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
|
+
| 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) |
|
|
98
|
+
| 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). |
|
|
99
|
+
| 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
|
+
| 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
|
+
| 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). |
|
|
84
102
|
| Resolution | comment with the answer → `gh issue close` → update `Decisions so far` on the map |
|
|
85
103
|
|
|
86
104
|
**State labels** (orthogonal to the type label, on top of `trailhead:ticket`):
|
|
87
|
-
- **`trailhead:blocked
|
|
88
|
-
- **`trailhead:seed
|
|
89
|
-
- **`trailhead:out-of-scope
|
|
90
|
-
- **`trailhead:superseded
|
|
91
|
-
- **`trailhead:unverified
|
|
92
|
-
- **`trailhead:fog
|
|
105
|
+
- **`trailhead:blocked`**: has an open blocker; excluded from the frontier. Removed when unblocked.
|
|
106
|
+
- **`trailhead:seed`**: a forward-looking capture parked until a trigger fires (kept blocked); makes seeds auditable.
|
|
107
|
+
- **`trailhead:out-of-scope`**: closed because ruled beyond the destination, *not* resolved; keeps it distinct from resolved tickets and out of `Decisions so far`.
|
|
108
|
+
- **`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
|
+
- **`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
|
+
- **`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).
|
|
111
|
+
|
|
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 above is the frontier. See [Multiple maps on one repo](#multiple-maps-on-one-repo-parking--concurrency).
|
|
93
113
|
|
|
94
|
-
**
|
|
114
|
+
**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.
|
|
95
115
|
|
|
96
116
|
Base commands:
|
|
97
117
|
```bash
|
|
98
118
|
# create the map
|
|
99
119
|
gh issue create --label "trailhead:map" --title "<destination>" --body-file <body>
|
|
100
|
-
# create a child ticket (add trailhead:blocked too if it has an open blocker)
|
|
101
|
-
gh issue create --label "trailhead:ticket,trailhead:build" --title "<question/goal>" --body-file <body>
|
|
102
|
-
# the
|
|
120
|
+
# create a child ticket (add trailhead:blocked too if it has an open blocker; add trailhead:map-<map> to scope it to its map)
|
|
121
|
+
gh issue create --label "trailhead:ticket,trailhead:build,trailhead:map-<map>" --title "<question/goal>" --body-file <body>
|
|
122
|
+
# link the ticket to its map: native sub-issue (structure/UI) + the map label above (the query)
|
|
123
|
+
gh api --method POST repos/{owner}/{repo}/issues/<map>/sub_issues -F sub_issue_id=$(gh api repos/{owner}/{repo}/issues/<ticket> --jq .id)
|
|
124
|
+
# wire a blocker: native dependency (visual frontier in the UI) + the label (the query)
|
|
125
|
+
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
|
+
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)
|
|
103
128
|
gh issue list --label "trailhead:ticket" --state open --search "no:assignee -label:trailhead:blocked -label:trailhead:unverified"
|
|
104
|
-
# unblock a ticket once its last blocker closes
|
|
129
|
+
# unblock a ticket once its last blocker closes (label only; native edge auto-reflects)
|
|
105
130
|
gh issue edit <n> --remove-label "trailhead:blocked"
|
|
106
131
|
# claim
|
|
107
132
|
gh issue edit <n> --add-assignee @me
|
|
108
133
|
# resolve
|
|
109
134
|
gh issue comment <n> --body-file <resolution> && gh issue close <n>
|
|
110
135
|
```
|
|
111
|
-
|
|
136
|
+
**First-use repo setup (do BOTH, every chart or adopt, never skip either):**
|
|
137
|
+
1. Create any missing labels with `gh label create` (all sixteen: `trailhead:map`, `trailhead:codebase`, `trailhead:conventions`, `trailhead:ticket`, the six type labels, `trailhead:blocked`, `trailhead:seed`, `trailhead:out-of-scope`, `trailhead:superseded`, `trailhead:unverified`, `trailhead:fog`).
|
|
138
|
+
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.
|
|
112
139
|
|
|
113
140
|
## The Map
|
|
114
141
|
|
|
115
|
-
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
|
|
142
|
+
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.
|
|
116
143
|
|
|
117
|
-
**`/trailhead:map`** renders this at low resolution as a read-only dashboard (it changes nothing)
|
|
118
|
-
- **Destination
|
|
119
|
-
- **Frontier
|
|
120
|
-
- **In progress
|
|
121
|
-
- **Blocked
|
|
122
|
-
- **Decisions so far
|
|
123
|
-
- **Not yet specified** + **parked fog
|
|
124
|
-
- **Out of scope
|
|
125
|
-
- **Inbox
|
|
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:
|
|
145
|
+
- **Destination**: the one-line where-we're-headed.
|
|
146
|
+
- **Frontier**: takeable now (open, unassigned, not blocked/unverified), each with its type.
|
|
147
|
+
- **In progress**: claimed tickets, with who holds each.
|
|
148
|
+
- **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
|
+
- **Decisions so far**: the index of what's settled.
|
|
150
|
+
- **Not yet specified** + **parked fog**: the coarse fog, and a count/link of open `trailhead:fog` issues.
|
|
151
|
+
- **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
|
+
- **Inbox**: a count of untriaged inbound issues, as a nudge.
|
|
126
153
|
|
|
127
154
|
Map body (loaded once per session):
|
|
128
155
|
|
|
129
156
|
```markdown
|
|
130
157
|
## 🎯 Destination
|
|
131
|
-
<what reaching the end of this map looks like
|
|
158
|
+
<what reaching the end of this map looks like: the spec, decision, or change this effort tends toward. The working artifact, unless overridden. One or two lines; every session orients to it before choosing a ticket.>
|
|
132
159
|
|
|
133
160
|
## 🗒️ Notes
|
|
134
|
-
<domain; vocabulary (see Domain vocabulary); standing preferences for this effort; any "stop at the spec" override.>
|
|
135
|
-
|
|
136
|
-
## ⚙️ Config
|
|
137
|
-
<!-- overrides the global ~/.claude/trailhead/config.json; omitted keys inherit global, then defaults. See Configuration. -->
|
|
138
|
-
models: { plan: opus, execute: sonnet, research: sonnet, review: sonnet, debug: opus }
|
|
139
|
-
design: disk # disk | claude.ai/design (mode; project URL auto-cached below)
|
|
140
|
-
design.approval: explicit # explicit | auto (require explicit mockup approval before UI code?)
|
|
141
|
-
# design.project: # auto-filled at execution when a claude.ai/design project is created
|
|
142
|
-
tdd: seams # seams | on | off
|
|
143
|
-
acceptance.browser: auto # auto | on | off
|
|
144
|
-
testing: { webapp: true, url: http://localhost:3000 }
|
|
145
|
-
plan_review: off # off | on | gemini,codex (external AI review of build PLANs)
|
|
146
|
-
plan_review.rounds: 2
|
|
161
|
+
<domain; vocabulary (see Domain vocabulary); standing preferences for this effort; any "stop at the spec" override; a **link to the repo's `trailhead:codebase` issue** (the codebase map lives there, repo-scoped, never re-inline it here) and a **link to the repo's `trailhead:conventions` issue** (the way of working: git/release + notes). Config is NOT here either: it lives in `.trailhead/config.json` at the repo root; see Configuration.>
|
|
147
162
|
|
|
148
163
|
## ✅ Decisions so far
|
|
149
|
-
<!-- the index
|
|
150
|
-
- [<closed ticket title>](link)
|
|
164
|
+
<!-- the index: one line per closed ticket -->
|
|
165
|
+
- [<closed ticket title>](link): <one-line gist of the answer>
|
|
151
166
|
|
|
152
167
|
## 🌫️ Not yet specified
|
|
153
168
|
<!-- fog of war: in-scope fog not yet ticketable; graduates as the frontier advances -->
|
|
@@ -156,15 +171,18 @@ plan_review.rounds: 2
|
|
|
156
171
|
<!-- work ruled beyond the destination; closed, never graduates -->
|
|
157
172
|
```
|
|
158
173
|
|
|
159
|
-
Section names are matched by their **text
|
|
174
|
+
Section names are matched by their **text**: the emoji are cosmetic anchors, so `Decisions so far` / `Not yet specified` / `Out of scope` still refer to these blocks throughout.
|
|
160
175
|
|
|
161
176
|
## Configuration
|
|
162
177
|
|
|
163
|
-
|
|
164
|
-
- **
|
|
165
|
-
- **
|
|
178
|
+
Three layers, **nearest wins**, key by key; a key unset at one layer inherits the next:
|
|
179
|
+
- **Project**: a `.trailhead/config.json` file at the repo root: overrides for this project, committed so a team shares one config. **Config never lives in the map issue**: the map is an index of decisions, while config is the user's to change at any time, so it stays in a plain file (and never asserted into the map by the agent).
|
|
180
|
+
- **Global**: `~/.claude/trailhead/config.json` (honour `$CLAUDE_CONFIG_DIR`): your standing defaults across every project.
|
|
181
|
+
- **Defaults**: the built-in values documented in `references/configuration.md`.
|
|
182
|
+
|
|
183
|
+
Load the effective config at the start of a work session, **from the map's project root** (`.trailhead/config.json` at the superproject repo root) plus the global file, **before** any isolation workspace is set up. **Config is a property of the project/map, not the working directory**: an `isolation: worktree`/`clone` workspace **inherits** the config loaded here and must **never re-resolve it from the isolated copy's own root**, because a per-ticket clone (a submodule clone especially) has no `.trailhead/` of its own and would silently fall back to defaults (this is exactly how a `design: claude.ai/design` project ends up wrongly using `disk` in a clone). Config **writes** too (e.g. caching `design.project`) target the project's `.trailhead/config.json` in the original checkout, never the clone. **The main session orchestrates and holds the HITL moments** (the `build`/`bug` Discuss's "stop and ask", Verify's acceptance/UAT); **every model key runs as a subagent** (`plan`, `execute` (an *executor subagent* implementing with atomic commits on `models.execute`), `research`, `review`, `debug`) so the whole per-activity model split applies in one session whatever model it runs on. Run Execute inline only when `models.execute` is unset or equals the session model. The **keys, their values, the model/tdd/acceptance semantics, and the guided menu setup live in `references/configuration.md`**: read it when running `/trailhead:config` or when you need a key's exact meaning. `/trailhead:config` with no args runs the guided setup; `config get` prints the merged config; `config set <key> <value>` writes one key.
|
|
166
184
|
|
|
167
|
-
|
|
185
|
+
**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.
|
|
168
186
|
|
|
169
187
|
## The Tickets
|
|
170
188
|
|
|
@@ -179,7 +197,7 @@ Title: 🐛 <clear, specific title>
|
|
|
179
197
|
<the decision, investigation, or build goal this ticket resolves>
|
|
180
198
|
|
|
181
199
|
## Blocked by
|
|
182
|
-
<!-- names+links of tickets that must close first; empty → write "
|
|
200
|
+
<!-- names+links of tickets that must close first; empty → write "nothing (on the frontier)" -->
|
|
183
201
|
```
|
|
184
202
|
|
|
185
203
|
The answer isn't in the body: it's recorded on resolution as a comment. Assets created while resolving a ticket are linked from the issue, not pasted in.
|
|
@@ -190,16 +208,16 @@ The answer isn't in the body: it's recorded on resolution as a comment. Assets c
|
|
|
190
208
|
|---|---|---|---|---|
|
|
191
209
|
| `decision` | 🧭 | | Destination | 🎯 |
|
|
192
210
|
| `research` | 🔬 | | Notes | 🗒️ |
|
|
193
|
-
| `prototype` | 🎨 | |
|
|
194
|
-
| `build` | 🔨 | |
|
|
195
|
-
| `bug` | 🐛 | |
|
|
196
|
-
| `task` | 🔧 | |
|
|
211
|
+
| `prototype` | 🎨 | | Decisions so far | ✅ |
|
|
212
|
+
| `build` | 🔨 | | Not yet specified | 🌫️ |
|
|
213
|
+
| `bug` | 🐛 | | Out of scope | 🚫 |
|
|
214
|
+
| `task` | 🔧 | | | |
|
|
197
215
|
|
|
198
|
-
The map issue title is prefixed 🗺. Keep the icons stable and don't add others
|
|
216
|
+
The map issue title is prefixed 🗺. Keep the icons stable and don't add others: they're anchors, not decoration.
|
|
199
217
|
|
|
200
218
|
## Techniques
|
|
201
219
|
|
|
202
|
-
The ticket engines call these by name. Each technique's full protocol lives in its own file under `references/techniques
|
|
220
|
+
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.
|
|
203
221
|
|
|
204
222
|
| Technique | File | In one line |
|
|
205
223
|
|---|---|---|
|
|
@@ -208,7 +226,7 @@ The ticket engines call these by name. Each technique's full protocol lives in i
|
|
|
208
226
|
| **Prototype** | `references/techniques/prototype.md` | throwaway artifact for "how should it look/behave"; routes UI mockups (disk / claude.ai/design) |
|
|
209
227
|
| **Research** | `references/techniques/research.md` | a focused subagent gathers a decision-ready fact from primary sources |
|
|
210
228
|
| **TDD** | `references/techniques/tdd.md` | RED → GREEN → REFACTOR at the seams; no implementation before a failing test |
|
|
211
|
-
| **Codebase map** | `references/techniques/codebase-map.md` | one-time fan-out of 5 read-only readers, distilled into the
|
|
229
|
+
| **Codebase map** | `references/techniques/codebase-map.md` | one-time fan-out of 5 read-only readers, distilled into the repo's `trailhead:codebase` issue |
|
|
212
230
|
| **Debug** | `references/techniques/debug.md` | scientific method: reproduce → localise → falsifiable hypotheses → confirm cause → verify |
|
|
213
231
|
| **Code review** | `references/techniques/code-review.md` | review the diff on 4 axes, adversarially verify each finding before reporting |
|
|
214
232
|
| **Acceptance testing** | `references/techniques/acceptance-testing.md` | prove it does what the *user* wanted: automated → browser-drive → guided UAT |
|
|
@@ -218,212 +236,162 @@ The ticket engines call these by name. Each technique's full protocol lives in i
|
|
|
218
236
|
|
|
219
237
|
Every ticket is **HITL** (human in the loop, speaking for themselves) or **AFK** (driven by the agent alone). A HITL ticket resolves only through the live exchange: the agent never stands in for the human's side.
|
|
220
238
|
|
|
221
|
-
### `decision
|
|
222
|
-
Close a choice
|
|
239
|
+
### `decision`: HITL, *default for decision tickets*
|
|
240
|
+
Close a choice, but widen before you narrow:
|
|
223
241
|
|
|
224
|
-
1. **Diverge (only if the option space isn't already clear).** Mapping the frontier usually framed the options already; skip straight to grilling when it did. When it didn't
|
|
242
|
+
1. **Diverge (only if the option space isn't already clear).** Mapping the frontier usually framed the options already; skip straight to grilling when it did. When it didn't (the ticket names a question but the candidate answers aren't laid out) open the space *first*: brainstorm the plausible options, name each concretely, and post them as an `OPTIONS` comment. Divergence has no judgement yet: surface possibilities, don't rank them. If a missing **fact** is what's blocking clarity, spin a `research` ticket and block on it; if the question is "how could it look/behave", spin a `prototype`. Don't grill a space you haven't opened.
|
|
225
243
|
2. **Converge.** Run the **Grilling** + **Domain vocabulary** techniques over the options to pick one. Record the decision and the why.
|
|
226
244
|
|
|
227
|
-
The two phases are distinct on purpose: divergence generates, grilling chooses. Never let the agent invent the options *and* pick for the human
|
|
245
|
+
The two phases are distinct on purpose: divergence generates, grilling chooses. Never let the agent invent the options *and* pick for the human: the choice stays theirs.
|
|
228
246
|
|
|
229
|
-
### `research
|
|
247
|
+
### `research`: AFK
|
|
230
248
|
Reading docs, third-party APIs, or local resources to surface a fact a decision waits on. Resolved by the **Research** technique. The only type that runs in parallel and more than one per session.
|
|
231
249
|
|
|
232
|
-
### `prototype
|
|
233
|
-
Raise the fidelity of the discussion with a rough, concrete artifact to react to
|
|
250
|
+
### `prototype`: HITL
|
|
251
|
+
Raise the fidelity of the discussion with a rough, concrete artifact to react to: the **Prototype** technique. Link the prototype as an asset.
|
|
234
252
|
|
|
235
|
-
### `build
|
|
253
|
+
### `build`: the engine inside the map
|
|
236
254
|
The ticket that *builds*. Lean cycle, all in **ticket comments**:
|
|
237
255
|
|
|
238
|
-
1. **Discuss
|
|
239
|
-
2. **Plan
|
|
240
|
-
3. **Execute**
|
|
241
|
-
4. **Verify
|
|
242
|
-
5. **Resolve
|
|
256
|
+
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 it's 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.
|
|
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 it's unset or equals the session model); the commits land on `main`, visible. New scope surfacing mid-execute is captured/split, not worked here.
|
|
259
|
+
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
|
+
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).
|
|
243
261
|
|
|
244
262
|
New scope that surfaces mid-build (while planning, executing, or testing) → **capture or split, never expand this ticket in flight** (see [Scope that surfaces while working a ticket](#capture-zero-friction--tracker)).
|
|
245
263
|
|
|
246
|
-
### `bug
|
|
264
|
+
### `bug`: the fix with diagnosis first
|
|
247
265
|
A defect to correct. Unlike `build`, you don't start from implementation: first you **understand**.
|
|
248
266
|
|
|
249
|
-
**New bug ticket vs reopen
|
|
267
|
+
**New bug ticket vs reopen: decide first.** A bug in the work of an already-closed ticket almost always means a **new** `bug` ticket, *not* reopening the old one, because `Decisions so far` is append-only history: that ticket really did deliver, and the defect surfaced later is a new event. The one exception is a **premature close**: a ticket closed before its Verify ever passed, i.e. it never actually delivered. The discriminator is sharp: **did that ticket genuinely pass Verify?**
|
|
250
268
|
- **Yes** → new bug ticket carrying `Regression of: <closed ticket name+link>` in its body. Leave the original closed. Open it with `/trailhead:bug --of <ticket> <text>` (below), which fills the pointer for you.
|
|
251
269
|
- **No** (closed by mistake, never delivered) → this isn't a new bug, it's a bad close: `gh issue reopen <n>` and finish it.
|
|
252
270
|
|
|
253
271
|
Then run the cycle, all in ticket comments:
|
|
254
272
|
|
|
255
|
-
1. **Repro
|
|
256
|
-
2. **Diagnose
|
|
257
|
-
3. **Fix
|
|
258
|
-
4. **Verify
|
|
259
|
-
5. **Resolve
|
|
273
|
+
1. **Repro**: a `REPRO` comment: how to reproduce, expected vs observed behaviour.
|
|
274
|
+
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**).
|
|
276
|
+
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
|
+
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).
|
|
260
278
|
|
|
261
279
|
A **blocking** bug that halts other work is worked immediately; an isolated one is a normal frontier ticket. New scope surfacing while fixing → capture or split, never expand this ticket in flight (see [Scope that surfaces while working a ticket](#capture-zero-friction--tracker)).
|
|
262
280
|
|
|
263
|
-
### `task
|
|
281
|
+
### `task`: HITL or AFK
|
|
264
282
|
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.
|
|
265
283
|
|
|
266
284
|
## Fog of war
|
|
267
285
|
|
|
268
|
-
The map is *deliberately* incomplete: don't chart what you can't yet see. Beyond the live tickets lies the fog
|
|
286
|
+
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.
|
|
269
287
|
|
|
270
288
|
**Not yet specified** is where that dim view is written: the suspected question, the area to revisit. Everything here is in scope, just not sharp enough for a ticket.
|
|
271
289
|
|
|
272
|
-
**Fog or ticket?** The test is whether you can *phrase* the question precisely now
|
|
290
|
+
**Fog or ticket?** The test is whether you can *phrase* the question precisely now, not whether you can *answer* it now.
|
|
273
291
|
- **Ticket** when the question is already sharp, even if blocked.
|
|
274
292
|
- **Not yet specified** when you can't yet phrase it that sharply. Don't pre-slice the fog into ticket-sized pieces.
|
|
275
293
|
|
|
276
294
|
## Out of scope
|
|
277
295
|
|
|
278
|
-
Fog only ever gathers *toward* the destination. Work beyond the destination is **out of scope**: it isn't fog, it doesn't belong in *Not yet specified*. It gets its own section on the map. It never graduates
|
|
296
|
+
Fog only ever gathers *toward* the destination. Work beyond the destination is **out of scope**: it isn't fog, it doesn't belong in *Not yet specified*. It gets its own section on the map. It never graduates: the frontier stops at the destination. When an existing ticket turns out to sit beyond the destination, label it **`trailhead:out-of-scope`**, **close it**, and leave one line in *Out of scope* with the why, linking the closed ticket. The label keeps it distinct from a resolved ticket; it stays out of *Decisions so far*, which records the way actually walked.
|
|
279
297
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
Capture without breaking flow, but with the **tracker** as destination, not scattered files. The verbs are in the **Capture** table under [Commands](#commands); here is the detail of each:
|
|
283
|
-
|
|
284
|
-
- **`note`** → a verbatim line in `## Not yet specified` (outside a project → `$HOME/.claude/notes/`). No questions, no rewriting.
|
|
285
|
-
- **`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.
|
|
286
|
-
- **`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.
|
|
287
|
-
- **`bug`** → a `trailhead:bug` ticket on the frontier; the repro in the body if you know it. If it blocks the ticket you're working, add it to that ticket's `## Blocked by` and label that ticket `trailhead:blocked`. Pass **`--of <ticket>`** (a number, `#n`, name, or URL) to record it as a `Regression of: <ticket>` pointer in the body — use it for a bug in already-closed work, which is always a *new* ticket, never a reopen (see the `bug` engine for the reopen exception). **Don't derail**: capture and continue — you resolve it in a dedicated session.
|
|
288
|
-
- **`seed`** → a ticket labelled `trailhead:seed` + `trailhead:blocked`, kept in its own `## Blocked by` until the trigger fires; note the trigger in the body. When the trigger fires, drop both labels to graduate it onto the frontier.
|
|
298
|
+
**Out of scope vs deferred (ask, never bury).** *Out of scope* is for work ruled beyond the destination **for good**: this effort never does it. It is **not** a parking lot for work that *will* happen but hangs on something **outside this map**, a feature, function, or milestone not ticketed here (e.g. a billing / *bolletta* module that lives elsewhere or comes later, and the items that wait on it). Before filing anything under *Out of scope*, apply the test: **will this actually get done once X exists?** **The tells of a deferred line (any one is enough):** it **names a gate or trigger** (*gated by X*, *quando X*, *once X exists*, *dipende da X / waits on X*); it carries a **this-only scope qualifier** that rules it out *here* but not for good (*per questo slice*, *for now*, *in this milestone*, *not in this map*), which means wanted-later, not rejected; it's a **feature the product will plausibly still want** (a later milestone, another map); or **an existing `trailhead:seed` is gated on it** (some seed's trigger / `## Blocked by` names this line), which makes it a **live gate by definition**. Any one of these is deferred: never wave it through as "genuinely beyond the destination". Only work **decided against for the product** (won't ever be built, wrong approach, explicitly cut, with **no** "for this slice / for now" qualifier) is truly out of scope. **A line that is the gate of a live seed can never be "ruled out"**: finding one means the cluster is mis-wired (the gate was left behind when its dependents became seeds), so re-route the gate too (see below), or those seeds' triggers dangle. If yes, it isn't out of scope, it's **deferred**, so don't close-and-forget it. **Ask the user how to capture it, and which map it belongs to** (this map, or a future / other map); **never decide silently, and every move out of *Out of scope* is user-confirmed** (you propose, the user picks the tier and the map). Route along the [capture spectrum](#capture-zero-friction--tracker): a **`seed`** gated on its trigger (tie it to what it waits on, e.g. *when the bolletta exists*), an **`idea`** to revisit, or a **`todo`** if it's already defined work to do now. **If it belongs to a future map** (defined work wanted for the product, but beyond this effort's arc), don't fabricate a map that isn't charted yet, but **do capture it as a real artifact, asking the user which**: a **`seed`** (a gated todo, a tracked issue that survives the map, is queryable by label, and can be the native `blocked_by` of its dependents) or an **`idea`** (a fog line, lighter, for a maybe-later). **Never leave it as a bare *Out of scope* text line**: that's the fragile case (a finished map's *Out of scope* is easy to lose, and a dependent wired to a text line has no real target). Leave only a one-line *Out of scope* **breadcrumb pointing at the artifact** (`→ future map: <name>, see <seed/idea link>`). It then **graduates into the current map** the moment this effort actually needs it (it "comes up on its own"), or **the next map charted on this repo harvests it**, its chart step asking which parked seeds/todos/ideas to pull in (see [Mode 1](#mode-1-chart-the-map-new-project-trailheadnew) / [Mode 1-bis](#mode-1-bis-adopt-an-existing-project-trailheadadopt)). Whichever way, **the gate and its dependents move together** (a dependent seed's trigger points at the gate's artifact, a live target), never split across "ruled out" and re-routed. **Re-route the gate, not just its dependents.** When the thing a deferred item waits on (its gate X) is itself an *Out of scope* / deferred line, X is deferred by the same test: **re-route X first** (its own `seed`/`idea`/`todo`, or a named future-milestone / another-map pointer), then **tie the dependents to X's new form**, never to a line that stays in *Out of scope*. A seed's trigger must point at a **live** target: a ticket/seed here, a named milestone/map, or a real external condition; a seed gated on a dead "ruled out" line has a trigger that can never fire. **Move a gate and its dependents together**, so the whole cluster leaves *Out of scope* at once (e.g. the *bolletta* becomes its own seed, and the "gated by the bolletta" items become seeds wired to it, none left behind as "ruled out").
|
|
289
299
|
|
|
290
|
-
|
|
300
|
+
Only genuinely-beyond-the-destination work stays in *Out of scope*. The same test applies to **stale *Out of scope* lines** noticed later: `/trailhead:map` (read-only) **flags** any **un-parked** deferred line as an advisory (like blocked-by drift, it changes nothing) **and suggests running `/trailhead:inbox` to re-route them**, while `inbox` or an explicit user go-ahead does the **actual re-route**, a line that's really deferred becoming a seed/idea/todo instead of lingering as "ruled out". A line **already parked** (annotated `→ future map` with its dependent seeds wired to a live trigger) is settled: neither surface flags it again.
|
|
291
301
|
|
|
292
|
-
|
|
293
|
-
|---|---|---|---|
|
|
294
|
-
| **note** | verbatim text (fog, or global notes) | maybe never — just recorded | *remember this* (not necessarily work) |
|
|
295
|
-
| **idea** | fog (`Not yet specified`) | later, **if** it graduates to a ticket | *maybe I'll do it* |
|
|
296
|
-
| **seed** | a blocked ticket | when a **trigger** fires | *I'll do it when X happens* |
|
|
297
|
-
| **todo** | a frontier ticket | **now** | *I'm doing it* |
|
|
298
|
-
|
|
299
|
-
Discriminator: not even work → `note`; maybe, once it's clear → `idea`; yes, when X happens → `seed`; yes, now → `todo`. The close pairs: `note` vs `idea` — raw text vs a work-candidate meant to graduate; `idea` vs `seed` — waiting on *clarity* vs waiting on a *condition*; `seed` vs `todo` — committed *later* vs committed *now*.
|
|
302
|
+
## Capture (zero friction → tracker)
|
|
300
303
|
|
|
301
|
-
One capture = one action + one confirmation line
|
|
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** joins the **active 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)); if several maps are open and it's ambiguous which the capture belongs to, ask.
|
|
302
305
|
|
|
303
|
-
**Scope that surfaces while working a ticket.** Testing or building X often sparks new ideas
|
|
306
|
+
**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:
|
|
304
307
|
- part of X's Question and small → just do it in X (that's completing X, not expanding it);
|
|
305
308
|
- it balloons X past one session → **`split`** X (children carry the new pieces, supersede the original);
|
|
306
309
|
- separate work or a follow-up beyond X's goal → **capture and continue** (`idea`/`todo`/`ticket`/`bug --of X`), don't derail.
|
|
307
310
|
|
|
308
|
-
Tickets born this way carry a **`Surfaced from: <ticket name+link>`** line in their body
|
|
309
|
-
|
|
310
|
-
## Working as a team
|
|
311
|
-
|
|
312
|
-
Many people (and their agent sessions) share one map and work it concurrently. The tracker is the single source of truth; these conventions keep concurrent work from colliding.
|
|
313
|
-
|
|
314
|
-
### Claiming
|
|
315
|
-
Whoever claims a ticket **owns it end to end** — they work it and close it themselves. "Owner" means only "the current claimer of *this* ticket," never a project-wide approver; there is no gate and no one to wait on to close your own ticket. GitHub assignment isn't an atomic lock, so keep the protocol simple and human-decided:
|
|
316
|
-
|
|
317
|
-
1. **Before claiming, check it's not already being worked.** Read the assignee. If it's already assigned to someone else, it's in progress — don't take it; pick another frontier ticket. (If the user explicitly named this taken ticket, stop and tell them it's assigned to `<login>`.)
|
|
318
|
-
2. **Claim it** — assign it to yourself: `gh issue edit <n> --add-assignee @me`.
|
|
319
|
-
3. **Re-check right before starting work.** Read the assignee again. If someone else got assigned in the meantime, **do not do any work — stop and flag it to the user with a clear message**, e.g.:
|
|
311
|
+
Tickets born this way carry a **`Surfaced from: <ticket name+link>`** line in their body (the same lineage convention as `Split from:` and `Regression of:`) so where an idea came from stays visible. Never let extras become silent lines inside X: the map stays honest by spinning them into their own tickets or fog.
|
|
320
312
|
|
|
321
|
-
|
|
322
|
-
> 1. **Leave it to them** — I'll unassign myself and take the next frontier ticket.
|
|
323
|
-
> 2. **Take it over** — I'll keep it and post a comment notifying @<other-login> that you're taking it.
|
|
313
|
+
## Multiple maps on one repo (parking & concurrency)
|
|
324
314
|
|
|
325
|
-
|
|
326
|
-
4. **Unclaim if you stop.** Pausing or abandoning a ticket → remove your assignment so it returns to the frontier. A claimed-but-idle ticket is invisible work.
|
|
327
|
-
5. **Never touch a ticket someone else holds.** Don't edit its body, labels, or state; contribute via a comment and let its owner incorporate it.
|
|
315
|
+
By default a repo has **one live map** and the frontier is the whole repo (as everywhere above). But you can **park a map to work another**, and a teammate can chart a second map on the same repo and work it concurrently. When more than one `trailhead:map` is open, each map carries **its own frontier**, kept separate by two mechanisms wired together (the same "one fact, two views" pattern as a blocker's native edge + label):
|
|
328
316
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
- **Pause** (`/trailhead:pause [note]`) — first commit any work-in-progress (atomic) so nothing is stranded, then post a **`PAUSED`** checkpoint comment: what's done, the exact next step, and any local state (branch, how to run it). Keep the claim if you'll return soon; **release it** (unassign) if it's open-ended, so someone else can resume — the checkpoint is what makes that safe.
|
|
332
|
-
- **Resume** (`/trailhead:resume [ticket]`) — read the ticket's latest `PAUSED` checkpoint (and the `DISCUSS`/`PLAN` comments above it), then pick up at the recorded next step. Anyone may resume a *released* ticket; a still-claimed one, only its owner.
|
|
333
|
-
- One ticket per session still holds: pausing A to work B ideally means B in a fresh session. If you must switch within one session, checkpoint A first — never leave it half-done and unrecorded.
|
|
317
|
+
- **Native sub-issue**: every ticket is a **sub-issue of its map** (`gh api --method POST repos/{owner}/{repo}/issues/<map>/sub_issues -F sub_issue_id=<ticket .id>`). This is the structure GitHub renders (the map shows its children + a progress bar); it carries the `Parent:` line's meaning as a real edge.
|
|
318
|
+
- **Per-map label `trailhead:map-<n>`** (n = the map issue's own number): on **every one of its tickets** (not on the map issue itself, which is identified by its number, and would only be redundantly "a member of itself"). This is the **queryable key** that scopes the frontier to one query, because native sub-issues aren't cheaply filterable in a single search (exactly as with `blocked_by`), so the label carries the query.
|
|
334
319
|
|
|
335
|
-
|
|
336
|
-
A ticket is sized to one ~100K-token session. When a claimed ticket turns out too big, or fans into distinct pieces, **split it** — don't grind through an oversized ticket:
|
|
337
|
-
1. **Create the child tickets** (create-then-wire): each carries `Parent: <map>`, a `Split from: <original name+link>` line, its type label, and `trailhead:blocked` if it has an open blocker.
|
|
338
|
-
2. **Supersede the original**: post a comment listing the children (`split into <names+links>`), label it **`trailhead:superseded`**, and **close it**. The children fully replace it.
|
|
339
|
-
3. The superseded ticket stays **out of `Decisions so far`** (it wasn't a decision walked — its children are). If other tickets were blocked by the original, re-point their `## Blocked by` at the relevant child and re-evaluate their `trailhead:blocked`.
|
|
320
|
+
Both are set at chart and on every ticket **in the same pass** (create the ticket → set its map parent → add its map label), so they can't drift.
|
|
340
321
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
- **Re-read the map body immediately before editing it**, append your one line, write back — keep the window tiny. Never edit the map from stale content read earlier in the session.
|
|
347
|
-
- **Keep map-body writes small and append-only** where possible; heavy restructuring of the map is a single-session job, done when no one else is mid-write.
|
|
348
|
-
- Ticket-level state (claim, labels, comments, close) is naturally conflict-free — prefer expressing state there over in the map body.
|
|
349
|
-
|
|
350
|
-
### Trust & provenance
|
|
351
|
-
`trailhead:*` labels drive the map, so they must not be forgeable by outsiders. GitHub already blocks users without write access from applying labels at all; this layer defends against the rest (triage-level collaborators, automations, or a repo that later loosens permissions).
|
|
322
|
+
**Scoped frontier** (one query, per map):
|
|
323
|
+
```bash
|
|
324
|
+
gh issue list --label "trailhead:ticket" --label "trailhead:map-<n>" --state open \
|
|
325
|
+
--search "no:assignee -label:trailhead:blocked -label:trailhead:unverified"
|
|
326
|
+
```
|
|
352
327
|
|
|
353
|
-
**
|
|
328
|
+
**Active map.** Which map a session works is the **active map**: a `.trailhead/active-map` marker at the working root (one line `#<n>`, **gitignored**, per-session local state like `session-ticket`, never committed). `/trailhead:work` and `/trailhead:map` default to it; naming a map/ticket explicitly always overrides. Each checkout/clone (so each teammate) has its own.
|
|
354
329
|
|
|
355
|
-
**
|
|
330
|
+
**Park & switch** = **pause** the in-flight ticket (`/trailhead:pause`, a `PAUSED` checkpoint), then point the active map at the other's number. Nothing else to save: every map lives on the Issues, durable. Resume later with `/trailhead:resume`, or just `/trailhead:work` once that map is active again.
|
|
356
331
|
|
|
357
|
-
**
|
|
332
|
+
**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.
|
|
358
333
|
|
|
359
|
-
|
|
334
|
+
**Pinning with several maps.** Codebase + conventions stay pinned always; the **3rd slot goes to the active/primary map**; other live maps stay **unpinned but listed** (the `trailhead:map` query above). You can't pin them all (3 slots); the label + the list keep them reachable.
|
|
360
335
|
|
|
361
|
-
|
|
336
|
+
**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.
|
|
362
337
|
|
|
363
|
-
|
|
338
|
+
**One-map compatibility.** With a single open map the frontier stays **repo-wide** (as today); 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.
|
|
364
339
|
|
|
365
|
-
|
|
366
|
-
```bash
|
|
367
|
-
gh issue list --state open --json number,title,author,labels \
|
|
368
|
-
| jq '[.[] | select([.labels[].name] | any(startswith("trailhead:")) | not)]'
|
|
369
|
-
```
|
|
340
|
+
## Working as a team
|
|
370
341
|
|
|
371
|
-
|
|
372
|
-
```bash
|
|
373
|
-
gh issue list --label trailhead:fog --state open --search "sort:updated-desc"
|
|
374
|
-
```
|
|
375
|
-
This is **how you notice a fog has cleared**: you don't watch each thread — you run the inbox (as you would to triage new issues), and section B surfaces the parked items that moved. For each, decide: **graduate** (adopt in place — see Fog), **keep parked** (still vague), or **drop** (turned out out-of-scope/dead → `trailhead:out-of-scope` + close). Optionally, a participant can nudge readiness by commenting on the thread; you confirm at graduation.
|
|
342
|
+
Many people (and their sessions) share one map concurrently; the tracker is the single source of truth. **Claiming (needed every time you work a ticket):** assign it to yourself before any work (`gh issue edit <n> --add-assignee @me`), re-read the assignee right before starting, and on a **collision stop and ask the user**: never resolve it silently; unclaim if you stop; never touch a ticket someone else holds. Whoever claims a ticket owns it end to end and closes it (no approver).
|
|
376
343
|
|
|
377
|
-
|
|
344
|
+
The rest of the concurrency protocol lives in **`references/teamwork.md`**. Read it when you do that thing: **pausing & resuming** (`PAUSED` checkpoints, release vs keep the claim), **parking & switching between concurrent maps** on one repo (see also [Multiple maps on one repo](#multiple-maps-on-one-repo-parking--concurrency)), **splitting** an oversized ticket (children + supersede + re-point blockers), the **undeclared-coupling** check between parallel tickets, safe **concurrent map-body edits**, and **trust & provenance** (the trust rule, the `trailhead:unverified` quarantine, and installing the repo-side **label guard** at first-use).
|
|
378
345
|
|
|
379
|
-
|
|
380
|
-
- **Fog.** In scope but not sharp enough to ticket → **keep the issue open and label it `trailhead:fog`** (parked: still visible and tracked, but the `trailhead:*` label drops it out of the inbound query, so it won't re-surface at every triage). Then use the open issue as a **clarification space**: post a comment with the questions that would make it concrete — in the spirit of **Grilling**, but async ("what would *done* look like? which users? where does it show?") — and invite the reporter and anyone interested to refine it in the thread. The fog dissipates through that conversation as much as through other work landing; others can carry the discussion with the reporter without you in the loop. **You don't lose it** — `gh issue list --label trailhead:fog` is the durable list, and the reporter follows their own open issue. When the thread (or the wider work) has made the question sharp, **adopt it in place**: swap `trailhead:fog` for `trailhead:ticket` + the type label, add `Parent:` and the now-answerable `## Question`, and it lands on the frontier — same issue, same reporter, the clarifying discussion preserved above.
|
|
381
|
-
- **Out of scope.** Beyond the destination → label `trailhead:out-of-scope`, close with the why.
|
|
382
|
-
- **Duplicate.** Link the existing ticket and close.
|
|
383
|
-
- **Needs info.** Ask the reporter for a repro or specifics; leave it open.
|
|
346
|
+
## Inbox: issues opened by others
|
|
384
347
|
|
|
385
|
-
|
|
348
|
+
`/trailhead:inbox [issue]` triages issues opened by others (bug reports, requests, questions that aren't trailhead tickets) and integrates the worthwhile ones **in place**: reframing them as tickets, keeping the reporter's authorship, applying the labels on adoption; the rest routes to fog, out-of-scope, duplicate, or needs-info. It also surfaces **parked `trailhead:fog`** whose thread went quiet-then-active (that's how you notice a fog has cleared), **and sweeps the map's own *Out of scope* for lines that are actually [deferred](#out-of-scope)** (a gate/trigger or a wanted-later feature), asking the user to re-route each to a seed/idea/todo (which map) and wiring it gate-first with a live trigger. This is the surface where the map's read-only advisory turns into an actual re-route. **Full protocol (the three sections, the adopt/fog/out-of-scope/duplicate/needs-info routing, the deferred-out-of-scope sweep, the `trailhead:unverified` handling) in `references/inbox.md`**: read it when running inbox.
|
|
386
349
|
|
|
387
350
|
## Invocation
|
|
388
351
|
|
|
389
|
-
### Mode 1
|
|
352
|
+
### Mode 1: Chart the map (new project), `/trailhead:new`
|
|
390
353
|
The user invokes with a loose idea.
|
|
391
354
|
|
|
392
355
|
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.
|
|
393
|
-
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**
|
|
394
|
-
3. **Create the map** (`trailhead:map`): Destination and Notes filled in, Decisions-so-far empty, the fog sketched into *Not yet specified*.
|
|
395
|
-
4. **Create the tickets you can specify now** as child issues
|
|
356
|
+
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).
|
|
358
|
+
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.
|
|
396
359
|
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.
|
|
397
|
-
6.
|
|
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 only two of the three pin slots (map + conventions) are filled.
|
|
398
361
|
|
|
399
|
-
### Mode 1-bis
|
|
362
|
+
### Mode 1-bis: Adopt an existing project, `/trailhead:adopt`
|
|
400
363
|
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.
|
|
401
364
|
|
|
402
|
-
1. **Map the codebase (once).** Heavy step, *only* at adoption
|
|
365
|
+
1. **Map the codebase (once per repo).** Heavy step, *only* at adoption: it does not repeat per ticket, and **not per map**. First check for an existing **`trailhead:codebase`** issue: if one exists (an earlier map on this repo created it) and the code hasn't materially drifted, just **link it** from this map's Notes and skip the fan-out. Otherwise run the **Codebase map** technique (a parallel fan-out of reader subagents) for a structured understanding (architecture, stack, decisions already embodied, risk areas) and distil the essence into the repo's **`trailhead:codebase` issue** (create it, label `trailhead:codebase`), then link that issue from the map's Notes. Discard the raw. Because it's a standalone repo-scoped issue, the next map reuses it instead of re-deriving or losing it.
|
|
403
366
|
2. **Name the destination of the remaining stretch.** **Grilling** + **Domain vocabulary**, **seeded by the map you just built**: not "what is the project" but "what's left to reach working / the next milestone".
|
|
404
367
|
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.
|
|
405
|
-
4. **Map the frontier of the remainder** → tickets specifiable now + fog in *Not yet specified*, then wire the blocking and fire the research. As Mode 1, steps 4–6.
|
|
368
|
+
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.
|
|
406
369
|
|
|
407
|
-
The tracker is the existing repo's (`gh` in its directory);
|
|
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.
|
|
408
371
|
|
|
409
|
-
### Mode 2
|
|
410
|
-
The user invokes with a map (URL or number). A ticket is optional
|
|
372
|
+
### Mode 2: Work the map, `/trailhead:work`
|
|
373
|
+
The user invokes with a map (URL or number). A ticket is optional: without one, you pick the next decision.
|
|
411
374
|
|
|
412
|
-
1. Load the **map** (the low-res view, not every ticket body).
|
|
413
|
-
2. Choose the ticket. If the user names one, use it. Otherwise the first frontier ticket in order. **Claim** it: assign it to yourself before any work, then re-read the assignee before starting
|
|
414
|
-
|
|
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 (3rd slot) 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.
|
|
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).
|
|
377
|
+
- **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
|
+
- **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
|
+
- **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).
|
|
380
|
+
- **Under `isolation: worktree` or `clone`, set up the isolated workspace NOW, before any file edit or diagnostic probe** (not later at Execute/Fix): for `worktree`, create/enter this ticket's `git worktree` on a `trailhead/t<n>` branch for the repo the ticket's `Scope:` points at (**the submodule itself for submodule-scoped work**, e.g. `git -C app worktree add ../<repo>-t<n> -b trailhead/t<n>`); for `clone`, make an independent working copy at `../<repo>-t<n>` (branch `trailhead/t<n>`) by whichever is faster: `git clone` + the install step, or a **folder copy that brings `node_modules`** to skip the reinstall (then verify the copy's git is independent, `git -C <copy> rev-parse --absolute-git-dir` resolves inside it, else commits leak back to the original). **Because `clone` is heavy (a full clone + an install), gate it, every time, before cloning: ask the user whether they expect to work more than one ticket at once on this machine this session.** If **no**, **skip the clone** and work this ticket in the current checkout (isolation only exists to stop concurrent workspaces from colliding; a lone ticket has nothing to collide with, so the shared-checkout caveats apply and that is fine, and `git:` still decides the branch as usual); if **yes**, clone as above. The gate is **`clone`-only**: `worktree` is cheap, so set it up without asking. Do every subsequent step in whatever workspace you ended up in. This is the whole point of isolation: if you diagnose and edit in the shared checkout "just to start" while a concurrent workspace exists, you are already colliding. Set the workspace up silently (the `clone` gate-question is the one exception you voice); it's undone at Resolve when the branch integrates and the worktree/clone is removed. See [Working as a team](#working-as-a-team).
|
|
381
|
+
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.
|
|
415
382
|
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.
|
|
416
|
-
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, **out of scope** (label + close) instead of resolving it. If the decision invalidates other parts of the map, update or delete them.
|
|
417
|
-
6. **Hand off.** Close the session with the **[Session handoff](#session-handoff)** ritual: confirm the ticket is resolved by name,
|
|
383
|
+
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), **unpin the map** (see [Substrate](#substrate-github-issues), the pin lifecycle); codebase and conventions stay pinned.
|
|
418
385
|
|
|
419
|
-
### Mode 3
|
|
386
|
+
### Mode 3: Capture, `/trailhead:bug|todo|idea|seed|note`
|
|
420
387
|
The user fires a capture on the fly → route it per the **Capture** section and confirm with one line.
|
|
421
388
|
|
|
422
|
-
### Auxiliary verbs
|
|
423
|
-
- **`ticket <type> <title
|
|
424
|
-
- **`grill [topic|ticket]
|
|
425
|
-
- **`config
|
|
426
|
-
- **`split [ticket]
|
|
427
|
-
- **`pause [note]`** / **`resume [ticket]
|
|
389
|
+
### Auxiliary verbs: `/trailhead:ticket|config|grill|split|pause|resume|update`
|
|
390
|
+
- **`ticket <type> <title>`**: open ticket(s) of the map on the fly, for any of the six types (`decision`, `research`, `prototype`, `build`, `bug`, `task`), the escape hatch the capture verbs don't cover. **Adding a ticket is a micro-charting act, so diverge briefly first, don't blind-commit to a single piece:** run a short breadth-first pass around the request: is this really *one* session-sized ticket, or a small **cluster** (a `decision` that needs a `research` before it, a UI `build` that needs a `prototype`, obvious siblings)? Does it imply a blocker? Surface the neighbours, *then* create the ticket(s): each gets `trailhead:ticket` + its `trailhead:<type>`, a `Parent:` line, a `## Question`; put each on the frontier, or wire `## Blocked by` + `trailhead:blocked`. This is a framing brainstorm (is this the right work?), distinct from the `decision` engine's option brainstorm (which choice?). If `<type>` is missing or invalid, ask which of the six. *(The zero-friction captures, `bug`/`todo`/`idea`/`seed`/`note`, deliberately skip this; they're one action, one confirmation.)*
|
|
391
|
+
- **`grill [topic|ticket]`**: run a standalone **Grilling** (+ **Domain vocabulary**) session on a decision or topic, or on a named ticket, without committing to the full work cycle. Record the outcome where it belongs: a ticket's resolution, the map's `Decisions so far`, or a fresh `decision` ticket.
|
|
392
|
+
- **`config`**: a **guided, menu-driven** setup (see [Configuration → Guided setup](#configuration)): pick the scope, then walk each setting as an `AskUserQuestion` menu with icon-labelled options, and write the result. `config get` prints the effective config read-only (project `.trailhead/config.json` merged over global over defaults, showing which source wins each key); `config set <key> <value>` writes one key directly: to the project `.trailhead/config.json`, or to `~/.claude/trailhead/config.json` with `--global`.
|
|
393
|
+
- **`split [ticket]`**: split the named (or in-play) ticket per [Splitting a ticket](#working-as-a-team): create children, supersede & close the original.
|
|
394
|
+
- **`pause [note]`** / **`resume [ticket]`**: checkpoint and pick back up per [Pausing & resuming](#working-as-a-team).
|
|
395
|
+
- **`update`**: check for a newer trailhead and install it where safe, per **`references/updating.md`** (detect the install channel, compare the installed version with the latest from the matching source, install on a go-ahead). The **`trailhead-check-update.js`** SessionStart hook keeps the check cache fresh; the statusline shows a `⬆ trailhead <version>` flag when one is available.
|
|
428
396
|
|
|
429
|
-
The user may work unblocked tickets in parallel: expect concurrent sessions editing the tracker
|
|
397
|
+
The user may work unblocked tickets in parallel: expect concurrent sessions editing the tracker, see [Working as a team](#working-as-a-team) for claiming, splitting, and safe map edits.
|