@marcomigozzi/trailhead 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.it.md +158 -34
- package/README.md +147 -33
- package/bin/auto-intro-docs.test.js +72 -0
- package/bin/auto-merge-docs.test.js +33 -0
- package/bin/auto-merge-ticket-docs.test.js +32 -0
- package/bin/auto-review-rounds-docs.test.js +45 -0
- package/bin/auto-run-docs.test.js +60 -0
- package/bin/auto-scope-docs.test.js +33 -0
- package/bin/auto-slots-docs.test.js +45 -0
- package/bin/auto-uat-docs.test.js +47 -0
- package/bin/base-preflight-docs.test.js +65 -0
- package/bin/execute-split-docs.test.js +146 -0
- package/bin/grilling-holistic-review-docs.test.js +90 -0
- package/bin/isolation-answer-docs.test.js +41 -0
- package/bin/isolation-copy-docs.test.js +28 -0
- package/bin/lib/codex-projection.js +6 -6
- package/bin/lib/codex-projection.test.js +43 -2
- package/bin/needs-you-docs.test.js +41 -0
- package/bin/pick-stress-test-docs.test.js +105 -0
- package/bin/report-status-docs.test.js +195 -0
- package/bin/resolve-helpers-docs.test.js +36 -0
- package/bin/scope-confirm-skip-docs.test.js +70 -0
- package/bin/trailhead.js +6 -6
- package/bin/trailhead.test.js +355 -16
- package/bin/uat-setup-docs.test.js +65 -0
- package/package.json +1 -1
- package/plugins/trailhead/.claude-plugin/plugin.json +1 -1
- package/plugins/trailhead/agents/trailhead-code-review.md +15 -2
- package/plugins/trailhead/agents/trailhead-codebase-map.md +20 -1
- package/plugins/trailhead/agents/trailhead-debug.md +20 -1
- package/plugins/trailhead/agents/trailhead-executor.md +29 -2
- package/plugins/trailhead/agents/trailhead-fix.md +29 -2
- package/plugins/trailhead/agents/trailhead-plan.md +20 -1
- package/plugins/trailhead/agents/trailhead-research.md +22 -3
- package/plugins/trailhead/agents/trailhead-verify.md +15 -2
- package/plugins/trailhead/commands/auto.md +2 -2
- package/plugins/trailhead/hooks/lib/auto-merge.test.js +467 -0
- package/plugins/trailhead/hooks/lib/auto-queue.test.js +245 -0
- package/plugins/trailhead/hooks/lib/auto-run.test.js +318 -0
- package/plugins/trailhead/hooks/lib/auto-slot.test.js +278 -0
- package/plugins/trailhead/hooks/lib/base-preflight.test.js +513 -0
- package/plugins/trailhead/hooks/lib/checkout-in.test.js +173 -0
- package/plugins/trailhead/hooks/lib/commit-message-check.js +1 -1
- package/plugins/trailhead/hooks/lib/commit-message-check.test.js +3 -2
- package/plugins/trailhead/hooks/lib/commit-msg-module-repo.test.js +116 -0
- package/plugins/trailhead/hooks/lib/commit-msg-sync.test.js +100 -9
- package/plugins/trailhead/hooks/lib/dashboard.test.js +472 -0
- package/plugins/trailhead/hooks/lib/fixtures/commit-msg-legacy-js +104 -0
- package/plugins/trailhead/hooks/lib/isolation-answer.test.js +258 -0
- package/plugins/trailhead/hooks/lib/isolation-copy.test.js +298 -0
- package/plugins/trailhead/hooks/lib/issue-body.test.js +470 -0
- package/plugins/trailhead/hooks/lib/merge-ticket.test.js +394 -0
- package/plugins/trailhead/hooks/lib/needs-you.test.js +177 -0
- package/plugins/trailhead/hooks/lib/plan-review-rounds.test.js +319 -0
- package/plugins/trailhead/hooks/lib/report-status.test.js +366 -0
- package/plugins/trailhead/hooks/lib/shell-scan.js +36 -1
- package/plugins/trailhead/hooks/lib/shell-scan.test.js +14 -0
- package/plugins/trailhead/hooks/lib/statusline-context.test.js +140 -0
- package/plugins/trailhead/hooks/lib/uat-backfill.test.js +150 -0
- package/plugins/trailhead/hooks/lib/uat-followup.test.js +169 -0
- package/plugins/trailhead/hooks/lib/uat-setup.test.js +340 -0
- package/plugins/trailhead/hooks/lib/unblock.test.js +207 -0
- package/plugins/trailhead/hooks/lib/work-state.test.js +45 -0
- package/plugins/trailhead/hooks/mod/README.md +209 -36
- package/plugins/trailhead/hooks/mod/auto-run-view.ts +93 -0
- package/plugins/trailhead/hooks/mod/auto-run.ts +165 -0
- package/plugins/trailhead/hooks/mod/map-pane-view.ts +304 -26
- package/plugins/trailhead/hooks/mod/map-pane.tsx +763 -160
- package/plugins/trailhead/hooks/mod/register.tsx +312 -40
- package/plugins/trailhead/hooks/mod/repo-pin.ts +167 -0
- package/plugins/trailhead/hooks/mod/session-registry.ts +65 -0
- package/plugins/trailhead/hooks/mod/turn-state.ts +14 -0
- package/plugins/trailhead/hooks/mod/whiteboard-follow.ts +98 -0
- package/plugins/trailhead/hooks/mod/work-state-locate.ts +191 -0
- package/plugins/trailhead/hooks/mod/work-state-prompt.ts +13 -15
- package/plugins/trailhead/hooks/mod/work-state-view.ts +38 -5
- package/plugins/trailhead/hooks/mod/worktree-porcelain.ts +17 -0
- package/plugins/trailhead/hooks/trailhead-bash-guards.js +8 -8
- package/plugins/trailhead/hooks/trailhead-bash-guards.test.js +22 -16
- package/plugins/trailhead/hooks/trailhead-install-guard.js +169 -34
- package/plugins/trailhead/hooks/trailhead-install-guard.test.js +180 -1
- package/plugins/trailhead/hooks/trailhead-labels-guard.js +231 -0
- package/plugins/trailhead/hooks/trailhead-labels-guard.test.js +212 -0
- package/plugins/trailhead/hooks/trailhead-search-guard.js +43 -7
- package/plugins/trailhead/hooks/trailhead-search-guard.test.js +66 -2
- package/plugins/trailhead/hooks/trailhead-secret-read-guard.js +296 -22
- package/plugins/trailhead/hooks/trailhead-secret-read-guard.test.js +253 -0
- package/plugins/trailhead/skills/_shared/choices.md +11 -4
- package/plugins/trailhead/skills/_shared/configuration-reference.md +17 -5
- package/plugins/trailhead/skills/_shared/load-first.md +3 -3
- package/plugins/trailhead/skills/_shared/multi-map.md +2 -2
- package/plugins/trailhead/skills/_shared/session-handoff.md +1 -1
- package/plugins/trailhead/skills/_shared/substrate-commands.md +47 -14
- package/plugins/trailhead/skills/_shared/substrate.md +9 -4
- package/plugins/trailhead/skills/_shared/teamwork.md +17 -8
- package/plugins/trailhead/skills/_shared/techniques/acceptance-testing.md +18 -10
- package/plugins/trailhead/skills/_shared/techniques/code-review.md +1 -1
- package/plugins/trailhead/skills/_shared/techniques/codebase-map.md +1 -1
- package/plugins/trailhead/skills/_shared/techniques/debug.md +2 -0
- package/plugins/trailhead/skills/_shared/techniques/grilling.md +4 -2
- package/plugins/trailhead/skills/_shared/techniques/plan-review.md +3 -1
- package/plugins/trailhead/skills/_shared/techniques/research.md +4 -2
- package/plugins/trailhead/skills/_shared/techniques/verify.md +1 -1
- package/plugins/trailhead/skills/_shared/techniques.md +12 -2
- package/plugins/trailhead/skills/trailhead/SKILL.md +1 -1
- package/plugins/trailhead/skills/trailhead-capture/SKILL.md +1 -1
- package/plugins/trailhead/skills/trailhead-capture/references/capture.md +3 -3
- package/plugins/trailhead/skills/trailhead-chart/SKILL.md +2 -2
- package/plugins/trailhead/skills/trailhead-chart/references/charting.md +5 -5
- package/plugins/trailhead/skills/trailhead-chart/references/inbox.md +2 -2
- package/plugins/trailhead/skills/trailhead-manage/SKILL.md +1 -1
- package/plugins/trailhead/skills/trailhead-manage/references/auditing.md +1 -1
- package/plugins/trailhead/skills/trailhead-view/SKILL.md +9 -6
- package/plugins/trailhead/skills/trailhead-work/SKILL.md +31 -18
- package/plugins/trailhead/skills/trailhead-work/references/auto.md +70 -22
- package/plugins/trailhead/skills/trailhead-work/references/executor-brief.md +29 -0
- package/plugins/trailhead/skills/trailhead-work/references/ticket-engines.md +29 -13
- package/plugins/trailhead/templates/statusline-trailhead.sh +77 -12
- package/plugins/trailhead/templates/trailhead-auto-merge.js +318 -0
- package/plugins/trailhead/templates/trailhead-auto-queue.js +186 -0
- package/plugins/trailhead/templates/trailhead-auto-run.js +294 -0
- package/plugins/trailhead/templates/trailhead-auto-slot.js +239 -0
- package/plugins/trailhead/templates/trailhead-base-preflight.js +305 -0
- package/plugins/trailhead/templates/trailhead-checkout-in.js +88 -0
- package/plugins/trailhead/templates/trailhead-commit-msg +38 -102
- package/plugins/trailhead/templates/trailhead-commit-msg-sync.js +58 -27
- package/plugins/trailhead/templates/trailhead-commit-msg.cjs +114 -0
- package/plugins/trailhead/templates/trailhead-dashboard.js +360 -0
- package/plugins/trailhead/templates/trailhead-isolation-answer.js +227 -0
- package/plugins/trailhead/templates/trailhead-isolation-copy.js +239 -0
- package/plugins/trailhead/templates/trailhead-issue-body.js +459 -0
- package/plugins/trailhead/templates/trailhead-merge-ticket.js +366 -0
- package/plugins/trailhead/templates/trailhead-needs-you.js +193 -0
- package/plugins/trailhead/templates/trailhead-plan-review-rounds.js +289 -0
- package/plugins/trailhead/templates/trailhead-report-status.js +274 -0
- package/plugins/trailhead/templates/trailhead-uat-backfill.js +114 -0
- package/plugins/trailhead/templates/trailhead-uat-followup.js +128 -0
- package/plugins/trailhead/templates/trailhead-uat-setup.js +385 -0
- package/plugins/trailhead/templates/trailhead-unblock.js +111 -0
- package/plugins/trailhead/templates/trailhead-work-state.js +25 -7
- package/plugins/trailhead/tests/auto-run-ui.test.ts +444 -0
- package/plugins/trailhead/tests/auto-run-view.test.ts +111 -0
- package/plugins/trailhead/tests/map-pane-ui.test.ts +2519 -382
- package/plugins/trailhead/tests/map-pane-view.test.ts +600 -18
- package/plugins/trailhead/tests/map-pane-write.test.ts +51 -0
- package/plugins/trailhead/tests/repo-pin-late-repo.test.ts +77 -0
- package/plugins/trailhead/tests/repo-pin.test.ts +123 -0
- package/plugins/trailhead/tests/session-registry.test.ts +50 -0
- package/plugins/trailhead/tests/whiteboard-follow.test.ts +90 -0
- package/plugins/trailhead/tests/work-state-locate.test.ts +33 -0
- package/plugins/trailhead/tests/work-state-prompt-hook.test.ts +276 -2
- package/plugins/trailhead/tests/work-state-prompt.test.ts +29 -35
- package/plugins/trailhead/tests/work-state-ui.test.ts +73 -1
- package/plugins/trailhead/tests/work-state-view.test.ts +44 -1
- package/plugins/trailhead/tests/work-state-worktree.test.ts +310 -0
- package/plugins/trailhead/types/index.d.ts +62 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Research
|
|
2
|
-
Spawn a focused subagent (AFK, parallelisable) with exactly ONE clear question: sharp enough that the answer is a decision, not a survey. Vague scope breeds rabbit holes; a narrow question is its own leash. Dispatch the **`trailhead-research`** agent (
|
|
2
|
+
Spawn a focused subagent (AFK, parallelisable) with exactly ONE clear question: sharp enough that the answer is a decision, not a survey. Vague scope breeds rabbit holes; a narrow question is its own leash. Dispatch the **`trailhead-research`** agent (it commits nothing and creates no branch: the report is the artifact), passing `config.models.research` as the model override; **never** an external-plugin researcher agent that another installed tool registered.
|
|
3
3
|
|
|
4
4
|
- **Treat your own prior knowledge as hypothesis, not fact**: training is stale. "As of my training" is a warning flag, not a citation.
|
|
5
5
|
- **Chase primary, high-trust sources**: official docs, source code, specs, first-party APIs; never a secondary write-up. Follow every claim back to the source that owns it; resolve *current* docs rather than reciting from memory.
|
|
@@ -8,5 +8,7 @@ Spawn a focused subagent (AFK, parallelisable) with exactly ONE clear question:
|
|
|
8
8
|
- **Distinguish verified fact from inference explicitly.** Assign honest confidence (LOW when only training data backs a claim). "I couldn't find X", "sources contradict", "LOW confidence" are valuable findings, not failures; never hide uncertainty behind confident prose.
|
|
9
9
|
- **Investigation, not confirmation:** gather evidence, then conclude; don't start from the answer you want and collect support.
|
|
10
10
|
- **Stop at sufficiency; no redundant re-reads.** Once the evidence is decision-ready, conclude: gathering past a confident answer is analysis paralysis, not rigour. Don't re-open a source you have already extracted from in this pass (re-reading the same file to re-derive a fact you already hold); cross-referencing a *new* source for confirmation is different, and still required.
|
|
11
|
-
-
|
|
11
|
+
- Return a **decision-ready answer** (verdict, tradeoffs, confidence, citations), not a raw data dump, as the agent's investigating report (`Return contract` in its definition): `answer:` holds the verdict, tradeoffs and confidence; `evidence:` holds every claim with its source URL; `open questions:` what stays unsettled. No branch, no committed artifact: parallel research agents share one checkout, so the report is the artifact.
|
|
12
|
+
|
|
13
|
+
**Report (orchestrator).** Run the report-status check on each report (`--technique research`, the *Malformed review report* protocol in `../techniques.md`) and branch on the checker's `verdict`, reading its `fields`. Post the report as a `RESEARCH` comment on the research ticket (every claim with its URL in `evidence:`). `status: found` -> post, then resolve the ticket. `status: inconclusive` -> post, then ask the user: accept the answer at that confidence, narrow the question, or run another round. `status: needs_input` -> ask the user the `open questions:`, then re-spawn with the answers.
|
|
12
14
|
|
|
@@ -7,7 +7,7 @@ Run it in the `build`/`bug` **Verify** step, after the tests / the plan's criter
|
|
|
7
7
|
1. **Restate the goal.** From the ticket's `## Question` and the `PLAN`'s verification criteria, list what "done" means as concrete, checkable claims. If the ticket named acceptance criteria, those are the claims. **When the ticket had an approved mockup**, add one explicit claim: the built UI is faithful to the approved mockup (layout, spacing, sizing, colour, typography, copy, and every state it shows). Being read-only and running before Acceptance testing, check it to the extent statically evaluable: does the built code carry the mockup's specified copy, states, and structural elements? A dropped element or missing state surfaces as `gaps_found`, and **defer the visual, pixel-level verdict to Acceptance testing's mockup-fidelity check** (a screenshot, not the diff, settles that) rather than forcing it here.
|
|
8
8
|
2. **Work backward from each claim to the ticket's own commits.** Take the change from the **Ticket diff** rule (canonical in `code-review.md`; restated here because this protocol is loaded on its own): the commits carrying this ticket's `Refs: #<n>` trailer, computed with exactly `git -C <repo> log --reverse --basic-regexp --grep='^Refs: #<n>$' --format=%H HEAD` and read per commit, oldest first, with `git -C <repo> show --remerge-diff <sha>`; recompute it on every run, never use a `<base>...HEAD` range, and treat an empty set as an error surfaced to the caller, never a `met`. For every claim, find the commit(s) and lines that satisfy it, record it as **unmet** if the code does not satisfy it, or as **inconclusive** (with a reason-class, see the Verdict) if you could not actually evaluate it: the check would not run, the evidence was unreadable, or the criterion is not falsifiable as written. Read the actual diff and the resulting code, not the commit messages.
|
|
9
9
|
3. **Check for silent gaps.** A task marked done whose criterion no code satisfies; a criterion the tests do not actually exercise; a partial implementation that passes the tests but misses an edge the ticket called out. A claim you **could not evaluate at all** is not a gap: it is `inconclusive` (step 4), never forced to `gaps_found`.
|
|
10
|
-
4. **Verdict.**
|
|
10
|
+
4. **Verdict.** Return the verdict with a leading `status:` tag the engine branches on; the orchestrator posts it under the `## Goal-backward verification` heading of the round's `VERIFY` comment (layout in *Malformed review report* in `../techniques.md`). One of three:
|
|
11
11
|
- `status: met`: every claim was evaluated and satisfied.
|
|
12
12
|
- `status: gaps_found`: at least one claim was **evaluated and the code does not satisfy it** (list each unmet or partial claim with its evidence).
|
|
13
13
|
- `status: inconclusive`: at least one claim **could not be evaluated at all**, and none is an outright gap. Every inconclusive claim carries **exactly one** mandatory `reason:` from this closed set:
|
|
@@ -2,10 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
The ticket engines call these by name. Each technique's full protocol lives in its own file under `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; each technique is dispatched to its **trailhead-owned agent** (`trailhead-<technique>`, shipped as committed `agents/trailhead-*.md`) via the `Agent` tool, with the technique's full protocol injected at spawn.
|
|
4
4
|
|
|
5
|
-
**Dispatch each technique to its trailhead-owned agent.** Every technique subagent carries its full protocol in the prompt (from its `techniques/` file), so the agent definition stays **thin**: identity + tool policy, model-agnostic. Dispatch each technique to **its own `trailhead-<technique>` agent** (set `subagent_type` accordingly): the Plan step → `trailhead-plan`, Execute → `trailhead-executor`, Research → `trailhead-research`, Code review → `trailhead-code-review`, Debug → `trailhead-debug`, Fix → `trailhead-fix`, Codebase map → `trailhead-codebase-map`, Goal-backward verification → `trailhead-verify`. These agents ship with trailhead (committed `agents/trailhead-*.md`) and are registered wherever trailhead is installed as a Claude Code **plugin** (its `agents/` load automatically), or its agent files are otherwise present as subagent types. **If a `trailhead-<technique>` agent is NOT registered in the current environment** (the `Agent`/`Task` tool reports the type unknown, or it is absent from the available-agents list, as on a skills-only install that did not register the agents), **run that technique inline in the main session**, following its `techniques/` protocol directly. Do **not** divert to a host built-in (`general-purpose`/`Explore`) or any external-plugin agent as the engine target. Inline is a graceful degradation: the technique still runs, only on the session model rather than its `config.models.<key>` pin (the same inline path used when that model equals the session model). The dispatch rule is therefore: the real `trailhead-<technique>` agent when it is registered, else inline in the main session, never a foreign agent. **Pass the `config.models.<key>` model as the `Agent`-tool `model` override at spawn** so config stays authoritative (the `.md` is model-agnostic; the override wins), running inline instead when that model is unset or equals the session model. On Claude Code the `Agent` tool's `model` parameter accepts only the short family aliases (`opus` / `sonnet` / `haiku` / `fable`), not the full ids `config.models.*` stores, so **map the configured id to its family alias only for the literal `model` value passed at spawn**: `claude-opus-*` -> `opus`, `claude-sonnet-*` -> `sonnet`, `claude-haiku-*` -> `haiku`, `claude-fable-*` -> `fable`; if an id matches none of these, don't guess: warn and inherit the session model (per `configuration-reference.md`). The alias names a family, not a version, so the host resolves it to that family's current model: the exact version the id pinned cannot be conveyed to the `Agent` tool and is not honoured at subagent spawn. **Keep the inline-vs-subagent decision on the full id**, running inline only when `config.models.<key>` is unset or its full id equals the session model's, never on the alias, so a deliberate same-family but different-version pin (e.g. `claude-opus-5` under a `claude-opus-4-8` session) still dispatches instead of collapsing to inline. (Codex projects `models.codex.*`, OpenAI ids, separately and is unaffected by this.) **Never dispatch to an external-plugin agent** (a `gsd-*`, `*-reviewer`, `*-researcher`, `*-mapper`, or similarly named agent another installed tool registered): it follows *its own* protocol and artifact conventions (a foreign code-review agent writes its own review file, not trailhead's `VERIFY` ticket comment), silently swapping trailhead's engine for another tool's. Host built-ins (`Explore` / `general-purpose`) stay available in general but are **no longer the engine-dispatch target**. The tool policy is baked into each agent's `tools:` field, mirroring the old split: **read-only** (`trailhead-plan`, `trailhead-code-review`, `trailhead-codebase-map`, `trailhead-verify`: reads, searches, runs tests; no Edit/Write) vs **full** (`trailhead-executor`, `trailhead-research`, `trailhead-debug`, `trailhead-fix`: can Edit/Write/commit). **Search-command hygiene (every fan-out subagent, and any built-in `Explore`/`general-purpose` fan-out the engine leans on).** Prefer the Grep and Read tools over shelling out to search or read a file; when a Bash `grep`/read is genuinely needed, pass the path as an **explicit argument** (`grep -niE "<patterns>" <abs>/<file>`, `wc -l <abs>/<file>`) and run git as `git -C <abs> ...`. **Never `cd` (or `pushd`/`env -C`) into the target directory first, in any shape**: not `cd <abs> && grep <relative>`, not a bare `cd <abs>` line followed by a relative `grep`/`wc` on the next line or after a `;`, not inside a heredoc or any other multi-statement block, and not as its own separate Bash call or turn (the shell working directory persists across Bash calls, so a standalone `cd <abs>` still leaves a later relative read non-static). Any `cd` makes the post-`cd` working directory non-static, so under bypass permissions with a `Read()` deny rule the harness cannot resolve the read target against the allow rules and falls back to a manual approval prompt (defeating headless operation); a path-explicit target (absolute-path argument, `git -C`) stays statically determinable and runs uninterrupted regardless of how the statements are separated. When the engine spawns a built-in `Explore`/`general-purpose` search, state this rule in its spawn prompt. On a `subagentToolkit == none` host there is no fan-out and this is moot: every technique runs inline in the one session. (Codex is **not** such a host: it has the native multi_agent toolkit, so techniques fan out there too, dispatched by `agent_type` to the projected `~/.codex/agents/trailhead-*.toml`; see the Codex adapter's §D.)
|
|
5
|
+
**Dispatch each technique to its trailhead-owned agent.** Every technique subagent carries its full protocol in the prompt (from its `techniques/` file), so the agent definition stays **thin**: identity + tool policy, model-agnostic. Dispatch each technique to **its own `trailhead-<technique>` agent** (set `subagent_type` accordingly): the Plan step → `trailhead-plan`, Execute → `trailhead-executor`, Research → `trailhead-research`, Code review → `trailhead-code-review`, Debug → `trailhead-debug`, Fix → `trailhead-fix`, Codebase map → `trailhead-codebase-map`, Goal-backward verification → `trailhead-verify`. These agents ship with trailhead (committed `agents/trailhead-*.md`) and are registered wherever trailhead is installed as a Claude Code **plugin** (its `agents/` load automatically), or its agent files are otherwise present as subagent types. **If a `trailhead-<technique>` agent is NOT registered in the current environment** (the `Agent`/`Task` tool reports the type unknown, or it is absent from the available-agents list, as on a skills-only install that did not register the agents), **run that technique inline in the main session**, following its `techniques/` protocol directly. Do **not** divert to a host built-in (`general-purpose`/`Explore`) or any external-plugin agent as the engine target. Inline is a graceful degradation: the technique still runs, only on the session model rather than its `config.models.<key>` pin (the same inline path used when that model equals the session model). The dispatch rule is therefore: the real `trailhead-<technique>` agent when it is registered, else inline in the main session, never a foreign agent. **Pass the `config.models.<key>` model as the `Agent`-tool `model` override at spawn** so config stays authoritative (the `.md` is model-agnostic; the override wins), running inline instead when that model is unset or equals the session model. Execute and Fix inject `trailhead-work/references/executor-brief.md` into their agent as the protocol text, not the whole step (the orchestrator part of the step stays with the session). On Claude Code the `Agent` tool's `model` parameter accepts only the short family aliases (`opus` / `sonnet` / `haiku` / `fable`), not the full ids `config.models.*` stores, so **map the configured id to its family alias only for the literal `model` value passed at spawn**: `claude-opus-*` -> `opus`, `claude-sonnet-*` -> `sonnet`, `claude-haiku-*` -> `haiku`, `claude-fable-*` -> `fable`; if an id matches none of these, don't guess: warn and inherit the session model (per `configuration-reference.md`). The alias names a family, not a version, so the host resolves it to that family's current model: the exact version the id pinned cannot be conveyed to the `Agent` tool and is not honoured at subagent spawn. **Keep the inline-vs-subagent decision on the full id**, running inline only when `config.models.<key>` is unset or its full id equals the session model's, never on the alias, so a deliberate same-family but different-version pin (e.g. `claude-opus-5` under a `claude-opus-4-8` session) still dispatches instead of collapsing to inline. (Codex projects `models.codex.*`, OpenAI ids, separately and is unaffected by this.) **Never dispatch to an external-plugin agent** (a `gsd-*`, `*-reviewer`, `*-researcher`, `*-mapper`, or similarly named agent another installed tool registered): it follows *its own* protocol and artifact conventions (a foreign code-review agent writes its own review file, not trailhead's `VERIFY` ticket comment), silently swapping trailhead's engine for another tool's. Host built-ins (`Explore` / `general-purpose`) stay available in general but are **no longer the engine-dispatch target**. The tool policy is baked into each agent's `tools:` field, mirroring the old split: **read-only** (`trailhead-plan`, `trailhead-code-review`, `trailhead-codebase-map`, `trailhead-verify`: reads, searches, runs tests; no Edit/Write) vs **full** (`trailhead-executor`, `trailhead-research`, `trailhead-debug`, `trailhead-fix`: can Edit/Write/commit). **Search-command hygiene (every fan-out subagent, and any built-in `Explore`/`general-purpose` fan-out the engine leans on).** Prefer the Grep and Read tools over shelling out to search or read a file; when a Bash `grep`/read is genuinely needed, pass the path as an **explicit argument** (`grep -niE "<patterns>" <abs>/<file>`, `wc -l <abs>/<file>`) and run git as `git -C <abs> ...`. **Never `cd` (or `pushd`/`env -C`) into the target directory first, in any shape**: not `cd <abs> && grep <relative>`, not a bare `cd <abs>` line followed by a relative `grep`/`wc` on the next line or after a `;`, not inside a heredoc or any other multi-statement block, and not as its own separate Bash call or turn (the shell working directory persists across Bash calls, so a standalone `cd <abs>` still leaves a later relative read non-static). Any `cd` makes the post-`cd` working directory non-static, so under bypass permissions with a `Read()` deny rule the harness cannot resolve the read target against the allow rules and falls back to a manual approval prompt (defeating headless operation); a path-explicit target (absolute-path argument, `git -C`) stays statically determinable and runs uninterrupted regardless of how the statements are separated. When the engine spawns a built-in `Explore`/`general-purpose` search, state this rule in its spawn prompt. On a `subagentToolkit == none` host there is no fan-out and this is moot: every technique runs inline in the one session. (Codex is **not** such a host: it has the native multi_agent toolkit, so techniques fan out there too, dispatched by `agent_type` to the projected `~/.codex/agents/trailhead-*.toml`; see the Codex adapter's §D.)
|
|
6
6
|
|
|
7
7
|
**Reviews never block the session (engine-wide, every host).** The three **review** techniques, **Code review** (build/bug Verify), **Goal-backward verification** (build/bug Verify), and **Cross-AI plan review** (build Plan), always run as a **background job collected by polling**, never a blind open-ended blocking wait. This is a hard default: not a `config` toggle, and not a Codex-only or fallback posture. Because trailhead works one ticket at a time and Resolve depends on the review's outcome, "background" here means a **poll loop with a one-line progress note, the session responsive and interruptible between polls**, the outcome always awaited; what is banned is the blind freeze, never "proceed without the result." On Claude Code the `Agent` tool already backgrounds the review and notifies on completion, so poll its progress instead of awaiting it blindly; on Codex, collect it through the §D bounded-wait poll loop (external plan-review CLIs via §H). **Acceptance testing is excluded**: it is interactive by design (guided UAT / browser-drive with the user in the loop), not a blind wait to background.
|
|
8
8
|
|
|
9
|
+
**Malformed review report (every engine-agent report).** The leading `status:` line, and the fields each report must carry, are machine-checked before the engine branches on them. This MUST run on every Code review and Goal-backward verification report and on every plan, executor, fix, debug, research and codebase-map report, whether it came from the subagent, an inline pass, or the lean inline pass; the report-status checker is `trailhead-report-status.js`, one JSON line `{"action","verdict","problem","report","fields","body"}` with `action` one of `accept` / `normalise` / `reask`. The return contract of each family (statuses, required fields, invariants) is stated once in the agents' `Return contract` blocks; the checker enforces it.
|
|
10
|
+
|
|
11
|
+
1. **Check.** Per report run `mktemp -d` once (a private 0700 random directory); write the raw report to `<that dir>/report.md` with the Write tool at the literal printed path; run `node <templates>/trailhead-report-status.js --technique <verify|code-review|plan|executor|fix|debug|research|codebase-map> --file <that dir>/report.md`, where `<templates>` is the first of `${CLAUDE_PLUGIN_ROOT}/templates/` (skipped when unset), `~/.claude/trailhead/templates/` and `~/.codex/skills/trailhead/templates/` that contains it; then `rm -rf <that dir>` with the literal absolute path. Cleanup runs after every path: accept, normalise, each re-ask check, a second failure, and a checker failure. Never a fixed or predictable path, never reused. The checker also reads stdin, but this protocol does not use it (a heredoc would put the report into the shell-scanned command and needs a delimiter the report cannot contain).
|
|
12
|
+
2. **Accept or normalise.** The checker-returned `report` replaces the raw report where the technique places it (the layout below); the engine branches only on the returned `verdict`, and reads the returned `fields` (and, for a `ready` plan, `body`) instead of re-deriving them from the prose of the report.
|
|
13
|
+
3. **Reask.** Ask once: continue the same agent where the host allows it, else re-dispatch the same `trailhead-<technique>` agent on the same model override with the malformed report quoted, using this corrective prompt verbatim (placeholders filled): "Your report could not be accepted: `<problem>` (missing: no `status:` line; unknown-token: `<found>` is not an allowed token; conflicting: more than one `status:` line, so quote or reword any status text in the body, which must not start a line with `status:` outside a code fence or blockquote; counts: the counts line is missing or repeated; fields: a required field is missing or an invariant is broken). Return the complete report again with its first line exactly `status: <token>`, where `<token>` is one of `<allowed tokens>`, and no other `status:` line<for code-review: , and its second line exactly `critical: <n> · warning: <n> · info: <n>`><for fields: , with each of `<required fields>` as a top-level `<label>:` line and `<the broken invariant>` holding, with nothing after the last field>. Do not post it anywhere." For code-review the prompt always restates both required lines; for `fields` it names the missing or empty fields and the invariant that broke.
|
|
14
|
+
4. **Re-check the reply** the same way. Still a reask: no verdict, nothing posted for that round, no third ask, no inference from the body. Surface a HITL checkpoint in the chat naming the technique and the problem; Resolve stays blocked, and so does any step that needed the report; for a review the previous `VERIFY` comment stays latest.
|
|
15
|
+
5. **No checker.** If the checker is missing, fails with exit 2, or prints non-JSON, apply the same rules by hand: the prose above stays the spec. Never guess a verdict.
|
|
16
|
+
|
|
17
|
+
**VERIFY comment layout (the reviewing pair only).** There is one `VERIFY` comment per Verify round, in this order: (1) the checker-returned code-review report verbatim (`status:` line 1, counts line 2, ledger, findings); (2) a `## Goal-backward verification` heading followed by the checker-returned verify report verbatim (its own `status:` first); (3) the test/criterion outcome, then any acceptance-testing results. A code-review round ending `issues_found` posts parts 1 and 3 only. A round re-running only goal verification repeats the latest checker-returned code-review report verbatim as part 1, so the latest `VERIFY` comment always holds the authoritative counts line and ledger (where the Disposition gate reads them). The engine branches on the checker `verdict`, never by reading the comment back.
|
|
18
|
+
|
|
9
19
|
| Technique | File | In one line |
|
|
10
20
|
|---|---|---|
|
|
11
21
|
| **Grilling** | `techniques/grilling.md` | interrogate the human, one question at a time, to converge on a decision |
|
|
@@ -17,5 +27,5 @@ The ticket engines call these by name. Each technique's full protocol lives in i
|
|
|
17
27
|
| **Debug** | `techniques/debug.md` | scientific method: reproduce → localise → falsifiable hypotheses → confirm cause → verify |
|
|
18
28
|
| **Code review** | `techniques/code-review.md` | review the ticket's own commits (its `Refs: #<n>` set) on 4 axes, adversarially verify each finding before reporting |
|
|
19
29
|
| **Goal-backward verification** | `techniques/verify.md` | check the change delivers what the ticket promised, working backward from its stated goal |
|
|
20
|
-
| **Acceptance testing** | `techniques/acceptance-testing.md` | prove it does what the *user* wanted: ask who drives (AI or human), then automated → browser-drive → guided UAT |
|
|
30
|
+
| **Acceptance testing** | `techniques/acceptance-testing.md` | prove it does what the *user* wanted: ask who drives (AI or human, or skip), then automated → browser-drive → guided UAT |
|
|
21
31
|
| **Cross-AI plan review** | `techniques/plan-review.md` | send a PLAN to external AI CLIs, converge on their concerns (opt-in via `config.plan_review`) |
|
|
@@ -8,7 +8,7 @@ argument-hint: "[new|adopt|work|quick|whiteboard|ticket|inbox|map|dashboard|conf
|
|
|
8
8
|
|
|
9
9
|
## Load first, in order
|
|
10
10
|
|
|
11
|
-
Before doing anything,
|
|
11
|
+
Before doing anything, load `<skill base>/../_shared/load-first.md` and follow it: the shared-core load contract (the six core files, in order, then the effective config). `<skill base>` is this skill's **base directory** (Claude Code prints it as `Base directory for this skill:` when the skill loads; on another host, the directory holding this `SKILL.md`): resolve the path to an **absolute path** and open it with the **Read tool** on that absolute path. Never `cd` into a skills directory and never read a relative path from the shell (a `cd` compound followed by a read is denied by the search guard). The same rule covers every later `../_shared/...` or `references/...` path in this skill: resolve it against the base directory to an absolute path before reading. `_shared/` is a **sibling** of this cluster's own directory (at `../_shared/`), never a child of it; its absence from a listing of the cluster dir is expected, not a missing core.
|
|
12
12
|
|
|
13
13
|
## Routing: verb to engine
|
|
14
14
|
|
|
@@ -9,7 +9,7 @@ argument-hint: "[bug|todo|idea|seed|note] [--of <ticket>] <text>"
|
|
|
9
9
|
|
|
10
10
|
## Load first, in order
|
|
11
11
|
|
|
12
|
-
Before doing anything,
|
|
12
|
+
Before doing anything, load `<skill base>/../_shared/load-first.md` and follow it: the shared-core load contract (the six core files, in order, then the effective config). `<skill base>` is this skill's **base directory** (Claude Code prints it as `Base directory for this skill:` when the skill loads; on another host, the directory holding this `SKILL.md`): resolve the path to an **absolute path** and open it with the **Read tool** on that absolute path. Never `cd` into a skills directory and never read a relative path from the shell (a `cd` compound followed by a read is denied by the search guard). The same rule covers every later `../_shared/...` or `references/...` path in this skill: resolve it against the base directory to an absolute path before reading. `_shared/` is a **sibling** of this cluster's own directory (at `../_shared/`), never a child of it; its absence from a listing of the cluster dir is expected, not a missing core.
|
|
13
13
|
|
|
14
14
|
A capture writes at most one issue; no isolation workspace is set up.
|
|
15
15
|
|
|
@@ -6,13 +6,13 @@ Capture without breaking flow, but with the **tracker** as destination, not scat
|
|
|
6
6
|
|
|
7
7
|
**Language (do not skip):** the captured ticket (title + body) is written in `config.ticket.language` (default `en`), **regardless of the language you are chatting in**. A capture fired in Italian still lands as an English ticket. This is the easiest place to slip, because a capture echoes the user's one-line phrasing; translate it, do not mirror it. See the *Ticket language* rule in `../../_shared/ticket-language.md`.
|
|
8
8
|
|
|
9
|
-
**Map or whiteboard:** a capture that produces a **ticket** (`todo`, `bug`, `seed`, and `idea` when it is sharp enough) must land on a **map** or on the **whiteboard** (map-less). **When a map is open, ask the user which** before creating it (active map vs whiteboard; several maps open -> the ask also picks the map). **Map eligibility is "is the map open", not "is it still in progress".** An **exhausted** map (destination reached) that is **still open** stays a valid capture target: a capture thematically tied to it is offered that map on the which-map ask, never silently dropped to the whiteboard just because the destination was reached (that would sever the thematic link). The exhausted state is a reason to *offer to close the map* (the dashboard's `exhausted · closeable` flag, see the dashboard freshness rule in `../../_shared/substrate.md`), never a reason to drop it as a capture target. Only a **closed** map is no longer eligible: then the whiteboard is the right fallback. Whiteboard tickets get the `trailhead:whiteboard` label and **no** map label or `Parent:`; map tickets get the map's `trailhead:map-<n>` label + native sub-issue edge. **No map open at all -> whiteboard, no ask** (nowhere else to go). `note` and a non-sharp `idea` stay map-fog and skip this; with no open map they fall back to the global notes location (`$HOME/.claude/notes/`), or a now-sharp `idea` becomes a whiteboard ticket. **When a capture creates a whiteboard ticket, refresh the pinned dashboard** afterwards (a structural event: the whiteboard surface the dashboard indexes grew, and a whiteboard ticket has no native progress bar, so the dashboard is the only place it shows); a capture landing on a *map* does not (native sub-issue progress bars track map tickets). See *The whiteboard* and the dashboard freshness rule in `../../_shared/substrate.md`.
|
|
9
|
+
**Map or whiteboard:** a capture that produces a **ticket** (`todo`, `bug`, `seed`, and `idea` when it is sharp enough) must land on a **map** or on the **whiteboard** (map-less). **When a map is open, ask the user which** before creating it (active map vs whiteboard; several maps open -> the ask also picks the map). **Map eligibility is "is the map open", not "is it still in progress".** An **exhausted** map (destination reached) that is **still open** stays a valid capture target: a capture thematically tied to it is offered that map on the which-map ask, never silently dropped to the whiteboard just because the destination was reached (that would sever the thematic link). The exhausted state is a reason to *offer to close the map* (the dashboard's `exhausted · closeable` flag, see the dashboard freshness rule in `../../_shared/substrate.md`), never a reason to drop it as a capture target. Only a **closed** map is no longer eligible: then the whiteboard is the right fallback. Whiteboard tickets get the `trailhead:whiteboard` label and **no** map label or `Parent:`; map tickets get the map's `trailhead:map-<n>` label + native sub-issue edge. **No map open at all -> whiteboard, no ask** (nowhere else to go). The ask is also skipped for a `bug --of` whose origin resolves to an open map or the whiteboard (see the `bug` bullet in the capture forms). `note` and a non-sharp `idea` stay map-fog and skip this; with no open map they fall back to the global notes location (`$HOME/.claude/notes/`), or a now-sharp `idea` becomes a whiteboard ticket. **When a capture creates a whiteboard ticket, refresh the pinned dashboard** afterwards (a structural event: the whiteboard surface the dashboard indexes grew, and a whiteboard ticket has no native progress bar, so the dashboard is the only place it shows); a capture landing on a *map* does not (native sub-issue progress bars track map tickets). See *The whiteboard* and the dashboard freshness rule in `../../_shared/substrate.md`.
|
|
10
10
|
|
|
11
11
|
- **`note`** → a verbatim line in `## Not yet specified` (outside a project → `$HOME/.claude/notes/`). No questions, no rewriting.
|
|
12
12
|
- **`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.
|
|
13
13
|
- **`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.
|
|
14
|
-
- **`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.
|
|
15
|
-
- **`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.
|
|
14
|
+
- **`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). **`--of` also inherits the origin's destination instead of asking** "whiteboard or which map?". Resolve the origin to an issue number first (a number, `#n` or URL parses directly; a name is resolved by a title search, `gh issue list --state all --search "<name> in:title"`, asking the user when it matches none or several), then read its labels: `gh issue view <origin> --json labels,title`. If the origin carries exactly one `trailhead:map-<n>`, check that map's state, `gh issue view <n> --json state,title`: **open** -> land on map `<n>` (its label + native sub-issue edge + `Parent:`), no ask; **closed** -> fall back. If it carries `trailhead:whiteboard` (and no map label), land on the whiteboard, no ask (dashboard refresh as for any whiteboard capture). Fall back to the existing Map or whiteboard rule (ask when a map is open, whiteboard when none) when the origin map is closed, when the origin carries neither label (a pre-whiteboard ticket, a map, a non-trailhead issue), when the labels are contradictory (both whiteboard and a map label, or several map labels), or when any lookup fails; never guess between conflicting labels. **Confirmation line** on an inheriting capture: name the destination as inherited from the origin, the map by name and number (or the whiteboard), and the origin by name and number; on a fallback say nothing about inheritance. **Don't derail**: capture and continue; you resolve it in a dedicated session.
|
|
15
|
+
- **`seed`** → a ticket like any other (`trailhead:ticket` + one type + one destination, a map or the whiteboard) labelled `trailhead:seed` + `trailhead:blocked`, all in the one create call, 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.
|
|
16
16
|
|
|
17
17
|
**note · idea · seed · todo form a spectrum** of commitment and timing (`bug` is separate: a defect, not a capture-tier):
|
|
18
18
|
|
|
@@ -9,7 +9,7 @@ argument-hint: "[new|adopt|ticket|inbox|grill] [idea | <type> <title> | issue |
|
|
|
9
9
|
|
|
10
10
|
## Load first, in order
|
|
11
11
|
|
|
12
|
-
Before doing anything,
|
|
12
|
+
Before doing anything, load `<skill base>/../_shared/load-first.md` and follow it: the shared-core load contract (the six core files, in order, then the effective config). `<skill base>` is this skill's **base directory** (Claude Code prints it as `Base directory for this skill:` when the skill loads; on another host, the directory holding this `SKILL.md`): resolve the path to an **absolute path** and open it with the **Read tool** on that absolute path. Never `cd` into a skills directory and never read a relative path from the shell (a `cd` compound followed by a read is denied by the search guard). The same rule covers every later `../_shared/...` or `references/...` path in this skill: resolve it against the base directory to an absolute path before reading. `_shared/` is a **sibling** of this cluster's own directory (at `../_shared/`), never a child of it; its absence from a listing of the cluster dir is expected, not a missing core.
|
|
13
13
|
|
|
14
14
|
## Routing: verb to engine
|
|
15
15
|
|
|
@@ -36,7 +36,7 @@ Read **`references/inbox.md`** and follow it. It triages issues opened by others
|
|
|
36
36
|
|
|
37
37
|
## Standalone grill: `grill [topic|ticket]`
|
|
38
38
|
|
|
39
|
-
Run a standalone **Grilling** (plus **Domain vocabulary**) session on a decision or topic, or on a named ticket, without committing to the full work cycle. Read the two techniques and follow them: `../_shared/techniques/grilling.md` and `../_shared/techniques/domain-vocabulary.md` (these are multi-cluster techniques, so their bodies live in `_shared/`). Record the outcome where it belongs: a ticket's resolution, the map's `Decisions so far`, or a fresh `decision` ticket. **When the session's decision unblocks or spawns follow-on tickets** (builds graduating from a resolved blocker, or siblings the decision implies), opening them **defers to the shared "Map or whiteboard" rule** (`../trailhead-capture/references/capture.md`): when one or more maps are open, ask the user which destination (a thematically eligible map vs the whiteboard), never silently picking the whiteboard and never treating "no coherent map" as a reason to auto-drop them there. Only with **no** open map does the whiteboard follow without asking.
|
|
39
|
+
Run a standalone **Grilling** (plus **Domain vocabulary**) session on a decision or topic, or on a named ticket, without committing to the full work cycle. Read the two techniques and follow them: `../_shared/techniques/grilling.md` and `../_shared/techniques/domain-vocabulary.md` (these are multi-cluster techniques, so their bodies live in `_shared/`). Before the final confirmation of the choices, run the **Holistic review before the final confirmation** defined in that technique. Record the outcome where it belongs: a ticket's resolution, the map's `Decisions so far`, or a fresh `decision` ticket. **When the session's decision unblocks or spawns follow-on tickets** (builds graduating from a resolved blocker, or siblings the decision implies), opening them **defers to the shared "Map or whiteboard" rule** (`../trailhead-capture/references/capture.md`): when one or more maps are open, ask the user which destination (a thematically eligible map vs the whiteboard), never silently picking the whiteboard and never treating "no coherent map" as a reason to auto-drop them there. Only with **no** open map does the whiteboard follow without asking.
|
|
40
40
|
|
|
41
41
|
## First-time config offer (end of chart / adopt)
|
|
42
42
|
|
|
@@ -8,9 +8,9 @@ The user invokes with a loose idea.
|
|
|
8
8
|
0. **Ensure a tracker repo exists (offer to create one if missing).** Everything trailhead writes lives on a GitHub repo's Issues, so before charting, confirm the current directory has one. Detect it with `gh repo view`: on success a GitHub repo with a remote is present, so say nothing and proceed. If there is **no GitHub repo** (no `origin` / GitHub remote, or `gh repo view` fails), **stop and ask the user** whether to create one, offering **private (default)** or **public**. On their choice: if the directory is not yet a git repo, `git init` and make an initial commit first; then `gh repo create <name> --private|--public --source=. --remote=origin --push` (take `<name>` from the directory, confirmed with the user), so the repo exists on GitHub with `origin` set. Charting continues only once a GitHub repo is in place: every step below writes to its Issues. If the user declines to create one, there is nowhere to chart, so stop and say so. This never fires when a repo already exists (`/trailhead:adopt` and re-charting on an existing repo skip straight past it).
|
|
9
9
|
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.
|
|
10
10
|
2. **Map the frontier.** Grill again, **breadth-first**: **enumerate every functional area the destination implies** and **capture each one (headed for a fog entry under *Not yet specified* at minimum) before deep-diving any single area**. Fan out across the whole space rather than deep on one thread, surfacing the open decisions and the first steps takeable now. **The deliverable is the coverage the map will carry, not the grilling transcript: every area the destination implies must land on the map at step 3, as fog or as a ticket, so nothing is silently missing.** **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**) **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.
|
|
11
|
-
3. **Create the map** (`trailhead:map`): Destination and Notes filled in, Decisions-so-far empty, the fog sketched into *Not yet specified*. **Every functional area enumerated in step 2 must appear here, as a fog entry or a ticket, before moving on: the map is the coverage deliverable.** Run the **first-use repo setup** (see **Substrate** → `../../_shared/substrate-commands.md`) 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**). **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:` `auto`|`none`|`worktree`|`clone` (default **`auto`**, which decides per ticket at work-start by asking *"will more than one ticket be worked at once on this machine this session?"* and picking `worktree` for a plain repo or `clone` for a submodule superrepo; a project can force `none`/`worktree`/`clone` explicitly), **plus** the narrative a new contributor would need: release/deploy specifics (environments, manual steps, OTA vs full build, versioning), tooling that must be driven a certain way (a specific MCP/CLI, e.g. a Lovable project piloted via its MCP), code & style rules, testing/UAT norms. Distil into the issue (header + prose); link it from the map's Notes. Don't invent: leave out or ask what you can't determine. If one already exists, just link it (reuse across maps). Finally, **ensure the repo's `trailhead:dashboard` index issue exists** (see **The dashboard**): create and pin it if missing (the fixed 3rd pinned slot, alongside codebase + conventions), then **write it to list this map** (charting a map is a structural event); if it already exists, refresh it.
|
|
11
|
+
3. **Create the map** (`trailhead:map`), after the **Holistic review before the final confirmation** of the Grilling technique has run on the step 1 destination and the step 2 choices together: Destination and Notes filled in, Decisions-so-far empty, the fog sketched into *Not yet specified*. **Every functional area enumerated in step 2 must appear here, as a fog entry or a ticket, before moving on: the map is the coverage deliverable.** Run the **first-use repo setup** (see **Substrate** → `../../_shared/substrate-commands.md`) 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**). **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 (singleton lookup: see **Singletons** in `../../_shared/substrate.md`), 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:` `auto`|`none`|`worktree`|`clone` (default **`auto`**, which decides per ticket at work-start by asking *"will more than one ticket be worked at once on this machine this session?"* and picking `worktree` for a plain repo or `clone` for a submodule superrepo; a project can force `none`/`worktree`/`clone` explicitly), **plus** the narrative a new contributor would need: release/deploy specifics (environments, manual steps, OTA vs full build, versioning), tooling that must be driven a certain way (a specific MCP/CLI, e.g. a Lovable project piloted via its MCP), code & style rules, testing/UAT norms. Distil into the issue (header + prose); link it from the map's Notes. Don't invent: leave out or ask what you can't determine. If one already exists, just link it (reuse across maps). Finally, **ensure the repo's `trailhead:dashboard` index issue exists** (see **The dashboard**): create and pin it if missing (the fixed 3rd pinned slot, alongside codebase + conventions), then **write it to list this map** (charting a map is a structural event); if it already exists, refresh it.
|
|
12
12
|
4. **Create the tickets you can specify now** as child issues (each with `trailhead:ticket` + its type + this map's `trailhead:map-<n>` label, **plus `trailhead:blocked` when the mapped dependency graph already shows it has a blocker**, and set as a **native sub-issue** of the map, see **Child→map link** under Substrate), 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) by wiring its `## Blocked by` line + native dependency together (moves 1-2 of the three-move wiring; the `trailhead:blocked` label, move 3, already rode in at creation, see **Substrate**). **Set the blocked label at creation, not via a post-creation `--add-label` pass:** a bulk `--add-label` burst spawns one repo-side label-guard run per ticket and reddens the Actions history with sheddable no-op runs; riding the label in the ticket's `issues.opened` event avoids that entirely. Wiring sorts them into frontier (unblocked) and blocked; the rest stays fog.
|
|
13
|
-
5. **Fire the research subagents.** For each `research` ticket created, run the **Research** technique in parallel,
|
|
13
|
+
5. **Fire the research subagents.** For each `research` ticket created, run the **Research** technique in parallel; each agent returns its report, and the orchestrator posts it on the ticket as a `RESEARCH` comment (no branch: the report is the artifact; see the technique for the `found` / `inconclusive` / `needs_input` branches).
|
|
14
14
|
6. **Offer config, then stop.** Charting is one session's work; it hand-resolves nothing. Before ending, make the **first-time config offer** (see **First-time config offer** in `../SKILL.md`): 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** row under Substrate), so at chart two of the three permanent pins (the dashboard + conventions) are filled and the codebase slot fills in later. Maps are never pinned.
|
|
15
15
|
|
|
16
16
|
**Next-step block (the close of chart, and of adopt).** Charting hand-resolves nothing, so the **Session handoff** ritual (`../../_shared/session-handoff.md`) does not fire here; render this codified next-step block instead, **right after the first-time config offer above**, styled on the handoff's own next-step block and written in the user's conversation language. Newly charted tickets are **map-frontier** tickets, so the primary move is `/trailhead:work` (the full engine: it can split and keeps the map record), with `/trailhead:quick` only as the off-map alternative. Every bullet below is a thing to do **after** the `/clear`, which leads; never point at the next command without it, and never demote `/clear` to a trailing parenthetical.
|
|
@@ -26,9 +26,9 @@ Same rules as the handoff block: **name the ticket, put its number (never the ti
|
|
|
26
26
|
## Mode 1-bis: Adopt an existing project, `/trailhead:adopt`
|
|
27
27
|
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.
|
|
28
28
|
|
|
29
|
-
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.
|
|
30
|
-
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".
|
|
29
|
+
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 (singleton lookup: see **Singletons** in `../../_shared/substrate.md`): 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.
|
|
30
|
+
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"; close with the **Holistic review before the final confirmation** of the Grilling technique.
|
|
31
31
|
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.
|
|
32
|
-
4. **Map the frontier of the remainder**, **breadth-first as in Mode 1 step 2
|
|
32
|
+
4. **Map the frontier of the remainder**, **breadth-first as in Mode 1 step 2**, closing with the **Holistic review before the final confirmation** of the Grilling technique over this step's choices and step 2's destination together: enumerate every functional area still standing between the code and the destination and **land each on the map (fog in *Not yet specified* at minimum) before deep-diving any one**, so the map makes every remaining section visible; the map, not the transcript, is the deliverable to check for that coverage. This yields → 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.
|
|
33
33
|
|
|
34
34
|
The tracker is the existing repo's (`gh` in its directory); run the **first-use repo setup** (see `../../_shared/substrate-commands.md`) 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, keep the default `isolation: auto` (it picks `worktree` for such a plain/monorepo checkout at work-start) 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 (on a branch), published, then a gitlink bump in the parent**, and that a **submodule-bearing superrepo resolves to `clone`** under the default `auto` (a superproject `worktree` cannot safely carry submodule state, so worktree is escalated to `clone`); a submodule path is a natural `Scope:` (see **Working as a team**). **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 prefer an explicit **`isolation: clone`** (a per-ticket clone that installs its own deps and builds), or `isolation: none` + **serialize** if the user doesn't want per-ticket clones. Worktree is for units that build from any path. Propose the values you inferred and let the human correct/complete them; create the issue if absent, link it from the map's Notes. Also **ensure the repo's `trailhead:dashboard` index issue exists** (see **The dashboard**): create and pin it if missing (the fixed 3rd pinned slot, alongside codebase + conventions) and write it to list this map.
|
|
@@ -18,12 +18,12 @@ This is **how you notice a fog has cleared**: you don't watch each thread; you r
|
|
|
18
18
|
|
|
19
19
|
**Out-of-scope lines actually deferred?** Scan the map's *Out of scope* section for lines that are **deferred**, not truly ruled out: ones that **name a gate or trigger** (*gated by X*, *quando X*, *dipende da X*), carry a **this-only scope qualifier** (*per questo slice*, *for now*, *in this milestone*, not for the product), describe a **feature wanted later** (already annotated `→ future map`, or plainly still on the roadmap), or **are the gate of an existing `trailhead:seed`**. That last one is a **near-deterministic check**: cross-reference every open `trailhead:seed`'s trigger / `## Blocked by` against the *Out of scope* lines; a line some seed waits on is a **live gate**, so it cannot be "ruled out" and the cluster is **mis-wired** (the gate was left behind), re-route it. A line **decided against for the product** (won't be built, wrong approach, no "for this slice" qualifier) stays put; and a line **already properly parked** (annotated `→ future map` with its dependent seeds wired to a live trigger) is settled, skip it. Only **un-parked** deferred lines surface here. For each deferred line, **ask the user** (never decide silently) how to re-route it, `seed` / `idea` / `todo`, and **which map** it belongs to (this one, or a future / other map), then apply the **out-of-scope-vs-deferred** rules (see `../../_shared/out-of-scope.md`): **re-route the gate first**, tie its dependents to the gate's new form with a **live trigger** (never a dead "ruled out" line), and **move the whole cluster together**. Update or remove the *Out of scope* line to match where each item went (a future-map item stays as an annotated `→ future map` line; an item pulled into this map leaves *Out of scope* entirely).
|
|
20
20
|
|
|
21
|
-
For each issue in the first two sections (incoming issues, parked fog),
|
|
21
|
+
For each issue in the first two sections (incoming issues, parked fog), propose a disposition (one of the moves below, with a one-line why) and put them to the user as one list, which they confirm, or amend item by item, before anything is written: every move labels, comments on or closes an issue someone else opened, and an out-of-scope ruling is the human's (`../../_shared/choices.md`). Act only on the user's explicit yes; never close or comment on an issue on your own judgement, and silence or an ambiguous reply leaves it untouched. The moves:
|
|
22
22
|
|
|
23
23
|
- **Adopt → a ticket.** Integrate it *in place* (same issue number, so the reporter stays credited): append a trailhead block to the body: `Parent: <map name+link>`, `Adopted-from: opened by @<author>`, a reframed `## Question` (the decision/build the *map* will act on), and `## Blocked by`, **without erasing the reporter's original text above it**. Apply `trailhead:ticket` + the right type label (`bug`/`decision`/`build`/`research`/`task`) as a write-access maintainer (which also satisfies Trust & provenance), plus the map's `trailhead:map-<n>` label + native sub-issue edge (like any ticket, so it joins the right map's frontier); add `trailhead:blocked` if it has an open blocker. It lands on the frontier. Comment to the reporter that it's been picked up.
|
|
24
24
|
- **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.
|
|
25
25
|
- **Out of scope.** Beyond the destination → label `trailhead:out-of-scope`, **drop its native sub-issue edge** to reclaim a slot (the eager drop in `../../_shared/substrate-commands.md`, 404-tolerant, so an un-adopted inbound issue with no edge is a no-op), then close with the why.
|
|
26
|
-
- **Duplicate.**
|
|
26
|
+
- **Duplicate.** In the proposal, name what each one covers (the issue and the existing ticket) and where they differ; only when the existing ticket covers all of it, link it and close. A partial overlap is not a duplicate: link the two and triage the issue by the other moves.
|
|
27
27
|
- **Needs info.** Ask the reporter for a repro or specifics; leave it open.
|
|
28
28
|
|
|
29
29
|
A rogue issue wearing a `trailhead:*` label it shouldn't (now `trailhead:unverified`) is triaged the same way: **adopt** (a maintainer applies the real labels + `Parent:`) or **reject** (strip the `trailhead:*` labels, leave it a normal inbound issue). Never adopt by trusting a label that was already there; you apply the labels on adoption.
|
|
@@ -9,7 +9,7 @@ argument-hint: "[config|update|prune|audit] [get|set … | map | --all]"
|
|
|
9
9
|
|
|
10
10
|
## Load first, in order
|
|
11
11
|
|
|
12
|
-
Before doing anything,
|
|
12
|
+
Before doing anything, load `<skill base>/../_shared/load-first.md` and follow it: the shared-core load contract (the six core files, in order, then the effective config). `<skill base>` is this skill's **base directory** (Claude Code prints it as `Base directory for this skill:` when the skill loads; on another host, the directory holding this `SKILL.md`): resolve the path to an **absolute path** and open it with the **Read tool** on that absolute path. Never `cd` into a skills directory and never read a relative path from the shell (a `cd` compound followed by a read is denied by the search guard). The same rule covers every later `../_shared/...` or `references/...` path in this skill: resolve it against the base directory to an absolute path before reading. `_shared/` is a **sibling** of this cluster's own directory (at `../_shared/`), never a child of it; its absence from a listing of the cluster dir is expected, not a missing core.
|
|
13
13
|
|
|
14
14
|
These admin verbs resolve no ticket and set up no isolation workspace.
|
|
15
15
|
|
|
@@ -123,7 +123,7 @@ The marker advance is the **last proposal** of the run, shown with its value and
|
|
|
123
123
|
- The value is the **cutoff captured in step 2**, never the completion time, so a ticket closed mid-run falls into the next window.
|
|
124
124
|
- An **unchanged** or **evolved** ticket never rewinds the marker.
|
|
125
125
|
- If any ticket was flagged **unverifiable**, in whole or in part (step 4 or step 6), write instead the earliest unverifiable ticket's `closedAt` minus one second (never past the cutoff), so the next default run picks it up again. No extra state, nothing silently dropped.
|
|
126
|
-
- Write it into the dashboard body with the fail-safe read-modify-write (see Body generation in `../../_shared/substrate-commands.md`). `--all` advances it the same way.
|
|
126
|
+
- Write it into the dashboard body: default path `node <templates>/trailhead-dashboard.js --set-last-audit <ISO-8601 UTC>` (the only writer path for the marker; `<templates>` is the same three-dir lookup as `trailhead-work-state.js`, see **Work state file** in `../../_shared/teamwork.md`), or, when the helper is missing, by hand with the fail-safe read-modify-write (see Body generation in `../../_shared/substrate-commands.md`). `--all` advances it the same way.
|
|
127
127
|
|
|
128
128
|
## Notes
|
|
129
129
|
|
|
@@ -9,7 +9,7 @@ argument-hint: "[map|dashboard|whiteboard] [map]"
|
|
|
9
9
|
|
|
10
10
|
## Load first, in order
|
|
11
11
|
|
|
12
|
-
Before doing anything,
|
|
12
|
+
Before doing anything, load `<skill base>/../_shared/load-first.md` and follow it: the shared-core load contract (the six core files, in order, then the effective config). `<skill base>` is this skill's **base directory** (Claude Code prints it as `Base directory for this skill:` when the skill loads; on another host, the directory holding this `SKILL.md`): resolve the path to an **absolute path** and open it with the **Read tool** on that absolute path. Never `cd` into a skills directory and never read a relative path from the shell (a `cd` compound followed by a read is denied by the search guard). The same rule covers every later `../_shared/...` or `references/...` path in this skill: resolve it against the base directory to an absolute path before reading. `_shared/` is a **sibling** of this cluster's own directory (at `../_shared/`), never a child of it; its absence from a listing of the cluster dir is expected, not a missing core.
|
|
13
13
|
|
|
14
14
|
These are read-only renders: no isolation workspace is set up.
|
|
15
15
|
|
|
@@ -31,13 +31,15 @@ A single `trailhead:map` issue is the canonical artifact: an **index**, not a st
|
|
|
31
31
|
|
|
32
32
|
Render, by name:
|
|
33
33
|
- **Destination**: the one-line where-we're-headed.
|
|
34
|
-
- **Frontier**: takeable now (open, unassigned, not blocked/unverified, and **not** `trailhead:whiteboard`, which has its own view), each with its type. Present independent tickets as a **set to choose from**, not a crowned first (see **Frontier order carries no priority** in `../_shared/substrate.md`).
|
|
34
|
+
- **Frontier**: takeable now (open, unassigned, not blocked/unverified, **not** `trailhead:paused`, and **not** `trailhead:whiteboard`, which has its own view), each with its type. Present independent tickets as a **set to choose from**, not a crowned first (see **Frontier order carries no priority** in `../_shared/substrate.md`).
|
|
35
35
|
- **In progress**: claimed tickets, with who holds each.
|
|
36
|
+
- **Paused**: tickets set aside on a `PAUSED` checkpoint (`trailhead:paused`), shown once here whatever their assignee or blocked state, with the holder if any; take one back with `/trailhead:resume <n>`.
|
|
36
37
|
- **Blocked**: with what each waits on; flag any **blocked-by drift** (prose `## Blocked by` differs from the native dependency, see **Blocked-by reconciliation** in `../_shared/substrate.md`) as an advisory line.
|
|
37
38
|
- **Decisions so far**: the index of what's settled.
|
|
38
39
|
- **Not yet specified** + **parked fog**: the coarse fog with **each top-level entry numbered** (`1.`, `2.`, ...; one number per top-level bullet or line, numbered top-to-bottom in displayed order and restarting at 1 on every render, nested sub-points not numbered separately), and a count/link of open `trailhead:fog` issues. The numbers are a **render-time handle**, stable within a single render and never persisted into the map body: they exist so a specific fog point can be named by its number when offering to dissolve or graduate one. Resolve any number the user gives against the **current** render's list; if the map changed since it was rendered, re-render and confirm the point before graduating it.
|
|
39
40
|
- **Out of scope**: what's been ruled out; **flag as an advisory** any line that's actually **deferred** rather than truly beyond the destination (the tells: a gate/trigger, a this-only qualifier, a feature wanted later, or the gate of a live `trailhead:seed`; see `../_shared/out-of-scope.md`), unless it's already parked (`→ future map` with dependent seeds on a live trigger, in which case say nothing). **Name any un-parked ones and suggest `/trailhead:inbox` to re-route them** (seed/idea/todo).
|
|
40
41
|
- **Inbox**: a count of untriaged inbound issues, as a nudge.
|
|
42
|
+
- **Open across the repo**: one line, `N open tickets across all maps and the whiteboard`, the sum of the open tickets of every open map plus the whiteboard. It uses the dashboard's own count queries (the `trailhead:map-<n>` label per open map, the `trailhead:whiteboard` label), over the open maps listed with `gh issue list --label trailhead:map --state open --limit 200`; the exact recipe is in `../_shared/substrate-commands.md` (Count queries). A map or the whiteboard label is only ever applied to `trailhead:ticket` issues and a ticket carries one of them, never both, so the label scopes are the ticket count and nothing is counted twice; closed maps never count, whiteboard tickets always do. When the maps list reaches 200, or any count reaches its limit, the line reads `at least N open tickets across all maps and the whiteboard`, a lower bound worded as one.
|
|
41
43
|
- **Exhaustion check**: if the map is exhausted (no open tickets and no fog left: the destination is reached), **say so and ask whether to close the map issue**, the same ask as the work handoff: on a yes, `gh issue close` it and refresh the dashboard so it drops off; on a no, leave it open. Maps aren't pinned, so there's no pin to free either way. Never close unprompted. Route any un-parked deferred *Out of scope* line first (above) so nothing wanted-later is lost. This user-confirmed close is the only change `map` makes to the map, and only on an explicit yes (the dashboard self-heal below touches only the pinned index, never the map).
|
|
42
44
|
|
|
43
45
|
**Dashboard self-heal.** Before rendering, ensure the repo's pinned `trailhead:dashboard` index exists and is pinned: create + pin it only if missing, re-pin it if the pin was dropped, and otherwise do nothing (never rewrite its body on a render). This is the same self-heal the **Render the dashboard** engine documents below; the exact gh commands are in `../_shared/substrate-commands.md`.
|
|
@@ -70,11 +72,11 @@ A repo's trailhead surface is spread across issues: one or more maps, a whiteboa
|
|
|
70
72
|
|
|
71
73
|
It holds:
|
|
72
74
|
- a link to **every open `trailhead:map`** (found via the `trailhead:map` label); GitHub renders each map's sub-issue **progress bar natively**, so the dashboard only links, it never recomputes per-ticket progress. **Per open map it also runs one cheap `0 open scoped issues?` pre-filter** and, for a candidate confirmed to have no remaining fog (see the exhaustion check in `../_shared/substrate-commands.md`), flags an **exhausted-but-open** map (destination reached, map issue never closed) as `exhausted · closeable`, so the render can offer to close it (below). It also flags each open map **`near cap · <total>/100`** (total >= 90) or **`at cap`** (total >= 100) from `sub_issues_summary.total`, appending **`· prunable`** when the map has closed edges to reclaim, so the render can offer `/trailhead:prune` (below);
|
|
73
|
-
- the **whiteboard** as its own section (frontier / in-progress / blocked, or a link to the `whiteboard` view);
|
|
75
|
+
- the **whiteboard** as its own section (frontier / in-progress / paused / blocked, or a link to the `whiteboard` view);
|
|
74
76
|
- dynamic **counts**: untriaged inbox size, whiteboard frontier size.
|
|
75
77
|
- a hidden `<!-- trailhead:last-audit <ISO-8601 UTC> -->` line, when the body already has one, is **preserved verbatim** on every regeneration (it is the `/trailhead:audit` window marker; see Body generation in `../_shared/substrate-commands.md`).
|
|
76
78
|
|
|
77
|
-
**`dashboard`** regenerates the pinned issue body from the live tracker and shows it, both the render and the on-demand refresh, consistent with `map` and `whiteboard`. It creates and pins the issue if the repo doesn't have one yet (ensuring the `trailhead:dashboard` label first, idempotently). The exact gh commands (find, pin/re-pin/unpin, self-heal, body generation with the count queries) are in `../_shared/substrate-commands.md`.
|
|
79
|
+
**`dashboard`** regenerates the pinned issue body from the live tracker and shows it, both the render and the on-demand refresh, consistent with `map` and `whiteboard`. The default path is the template helper `node <templates>/trailhead-dashboard.js` (`<templates>`: the same three-dir lookup as `trailhead-work-state.js`, see **Work state file** in `../_shared/teamwork.md`; `--dry-run` previews the outcome and the old and new body sizes without writing): it gathers the tracker data, renders the whole body deterministically (the body is fully generated, so hand-written prose from an older dashboard is replaced by tracker data and stays in the issue's edit history), self-heals the label and pin, and writes through the guarded body write; the agent then shows the result and makes the offers below. When the helper is missing, do it by hand per the spec below. It creates and pins the issue if the repo doesn't have one yet (ensuring the `trailhead:dashboard` label first, idempotently). The exact gh commands (find, pin/re-pin/unpin, self-heal, body generation with the count queries) are in `../_shared/substrate-commands.md`.
|
|
78
80
|
|
|
79
81
|
**Exhausted-but-open maps: flag and offer to close.** The one-shot "close this map?" prompt fires only at the exhaustion moment (a `work` handoff, or a `map` render on the exhausted map). If that moment was skipped, or the user chose to leave the map open, an exhausted map lingers with nothing re-surfacing it. So the dashboard is the recurring surface that catches it: for every open map flagged `exhausted · closeable` (the `0 open scoped issues?` pre-filter confirmed against remaining fog, see `../_shared/substrate-commands.md`), the render **offers to close it** (route any un-parked deferred *Out of scope* line first, exactly as the `map` exhaustion check does), `gh issue close` on a yes, leave it open on a no. Never auto-close: flag + offer only. The count runs per map, but the body read (fog confirm + deferred routing) is bounded to the exhausted candidates, so this stays within the dashboard-only-links spirit.
|
|
80
82
|
|
|
@@ -98,12 +100,13 @@ Not all work belongs to a map. A **bug**, a **todo**, or a one-off **task** may
|
|
|
98
100
|
It has its **own frontier**, off every map's (see **Map frontier vs whiteboard frontier** in `../_shared/substrate.md`):
|
|
99
101
|
```bash
|
|
100
102
|
gh issue list --label "trailhead:ticket" --label "trailhead:whiteboard" --state open \
|
|
101
|
-
--search "no:assignee -label:trailhead:blocked -label:trailhead:unverified"
|
|
103
|
+
--search "no:assignee -label:trailhead:blocked -label:trailhead:unverified -label:trailhead:paused"
|
|
102
104
|
```
|
|
103
105
|
|
|
104
106
|
**`whiteboard`** renders this as a read-only view, a mirror of the `map` render minus destination, decisions, and fog (the whiteboard has no direction, only loose work), by name:
|
|
105
|
-
- **Frontier**: takeable now (open, unassigned, not blocked/unverified whiteboard tickets), each with its type. Present independent tickets as a **set to choose from**, not a crowned first (see **Frontier order carries no priority** in `../_shared/substrate.md`).
|
|
107
|
+
- **Frontier**: takeable now (open, unassigned, not blocked/unverified and **not** `trailhead:paused` whiteboard tickets), each with its type. Present independent tickets as a **set to choose from**, not a crowned first (see **Frontier order carries no priority** in `../_shared/substrate.md`).
|
|
106
108
|
- **In progress**: claimed whiteboard tickets, with who holds each.
|
|
109
|
+
- **Paused**: whiteboard tickets set aside on a `PAUSED` checkpoint (`trailhead:paused`), shown once here whatever their assignee or blocked state, with the holder if any; take one back with `/trailhead:resume <n>`.
|
|
107
110
|
- **Blocked**: any blocked whiteboard ticket, with what it waits on (a whiteboard ticket can still block on another).
|
|
108
111
|
|
|
109
112
|
**Dashboard self-heal.** Like the `map` render, ensure the repo's pinned `trailhead:dashboard` index exists and is pinned before rendering (create + pin only if missing, re-pin if the pin was dropped; never rewrite its body here). See the **Render the dashboard** self-heal above.
|
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: trailhead-work
|
|
3
3
|
user-invocable: false
|
|
4
|
-
description: "trailhead work cluster: the verbs that resolve tickets on the map and manage a ticket's lifecycle. Work the next frontier ticket or the one you name (work), work one ticket whole without splitting, a new whiteboard ticket or an existing one (quick), checkpoint the ticket in play (pause), resume it from its checkpoint (resume), split an oversized ticket into children (split), and run the
|
|
4
|
+
description: "trailhead work cluster: the verbs that resolve tickets on the map and manage a ticket's lifecycle. Work the next frontier ticket or the one you name (work), work one ticket whole without splitting, a new whiteboard ticket or an existing one (quick), checkpoint the ticket in play (pause), resume it from its checkpoint (resume), split an oversized ticket into children (split), and run a chosen scope (one map, the whiteboard, or everything) autonomously until a stop condition or exhaustion (auto). A cohesion cluster of the trailhead skill split, loading the shared `_shared/` core; reached through the `/trailhead:<verb>` command wrappers. Not auto-invoked: it runs only when one of these verbs is dispatched."
|
|
5
5
|
argument-hint: "[work|quick|pause|resume|split|auto] [ticket | \"text\" | note]"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
`trailhead-work` is the **work cluster** of the trailhead skill: the verbs that resolve tickets and manage a ticket's lifecycle. `work` takes the next frontier ticket (or the one you name) and resolves it with its type's engine; `quick` works one ticket whole without splitting (a new whiteboard ticket from text, or an existing ticket, which stays where it is); `pause` checkpoints the ticket in play; `resume` picks it back up; `split` breaks an oversized ticket into children; `auto` runs
|
|
8
|
+
`trailhead-work` is the **work cluster** of the trailhead skill: the verbs that resolve tickets and manage a ticket's lifecycle. `work` takes the next frontier ticket (or the one you name) and resolves it with its type's engine; `quick` works one ticket whole without splitting (a new whiteboard ticket from text, or an existing ticket, which stays where it is); `pause` checkpoints the ticket in play; `resume` picks it back up; `split` breaks an oversized ticket into children; `auto` runs a chosen run scope autonomously, ticket by ticket, suspending the one-ticket-per-session rule until a stop condition or exhaustion. Everything lives on the GitHub Issues; the repo holds code only.
|
|
9
|
+
|
|
10
|
+
## Move into the handed checkout (`--in=`)
|
|
11
|
+
|
|
12
|
+
The panel starts `work` and `resume` from wherever the session stands and cannot move it, so it appends the pinned checkout to the args as a last word `--in=<token>` (base64url of the checkout root). When the args of `work` or `resume` end with such a word, this section runs first, before the load, the Preflight and anything else; with no such word it does not apply. `quick` never gets one.
|
|
13
|
+
|
|
14
|
+
1. Drop the `--in=<token>` word from the args: the verb, and its ticket number if any, are what is left.
|
|
15
|
+
2. Run `node <templates>/trailhead-checkout-in.js --in <token> --cwd "$PWD"`. `<templates>` is the first of `${CLAUDE_PLUGIN_ROOT}/templates/`, `~/.claude/trailhead/templates/`, `~/.codex/skills/trailhead/templates/` that contains the script (the same lookup as `trailhead-work-state.js`: try all three in order, skipping the first when `CLAUDE_PLUGIN_ROOT` is unset or empty). It prints one JSON line with a `status`. If the script is missing (none of the three has it: an outdated install), stop and report that the checkout could not be reached and the install needs an update: nothing is claimed, and never move by hand.
|
|
16
|
+
3. `invalid` -> stop and report its `reason` in one line; never move, never look for another checkout.
|
|
17
|
+
4. `here` -> the session is already in the checkout: no move, carry on with the args from step 1.
|
|
18
|
+
5. `move` -> run the helper's `cd` field verbatim as its own Bash call (a lone `cd` is what moves the session; never join it to a read or another command, and never edit the quoting). A failed `cd` stops the verb with a report. Then re-run the helper with the new `$PWD` and require `here`; anything else means the session could not move: stop and report that, with nothing claimed.
|
|
19
|
+
6. After a move, say in one result-oriented line which checkout the session is now working in, then continue with **Load first** and, for an explicit ticket number, the Preflight.
|
|
9
20
|
|
|
10
21
|
## Load first, in order
|
|
11
22
|
|
|
12
|
-
|
|
23
|
+
Right after the move above (when there is one) and before anything else, load `<skill base>/../_shared/load-first.md` and follow it: the shared-core load contract (the six core files, in order, then the effective config). `<skill base>` is this skill's **base directory** (Claude Code prints it as `Base directory for this skill:` when the skill loads; on another host, the directory holding this `SKILL.md`): resolve the path to an **absolute path** and open it with the **Read tool** on that absolute path. Never `cd` into a skills directory and never read a relative path from the shell (a `cd` compound followed by a read is denied by the search guard). The same rule covers every later `../_shared/...` or `references/...` path in this skill: resolve it against the base directory to an absolute path before reading. `_shared/` is a **sibling** of this cluster's own directory (at `../_shared/`), never a child of it; its absence from a listing of the cluster dir is expected, not a missing core.
|
|
13
24
|
|
|
14
25
|
Config is a property of the project/map, not the working directory: an isolation workspace inherits this config, never re-resolves it from the isolated copy's own root. **For an explicit `work <n>` / `quick <n>`, the named-ticket Preflight below runs *before* this config load:** never load config, or anything else, for a ticket that turns out closed.
|
|
15
26
|
|
|
@@ -17,16 +28,16 @@ Config is a property of the project/map, not the working directory: an isolation
|
|
|
17
28
|
|
|
18
29
|
The **first word** of the arguments is the verb; the rest is the text (a ticket number, a `"text"` seed, or a pause note).
|
|
19
30
|
|
|
20
|
-
- **`work [ticket]`** to the **Work the map** engine below (Mode 2). It dispatches the chosen ticket to its type's engine in **`references/ticket-engines.md`** (`decision` / `research` / `prototype` / `build` / `bug` / `task`). Read that file when you resolve the ticket.
|
|
31
|
+
- **`work [ticket]`** (after **Move into the handed checkout** when the args end with `--in=<token>`) to the **Work the map** engine below (Mode 2). It dispatches the chosen ticket to its type's engine in **`references/ticket-engines.md`** (`decision` / `research` / `prototype` / `build` / `bug` / `task`). Read that file when you resolve the ticket.
|
|
21
32
|
- **`quick [ticket | "text"]`** to the **Work one ticket whole** engine below. It runs the full `build`/`bug` cycle from `references/ticket-engines.md`, without splitting (off the map only for a new `"text"` ticket; a map ticket stays on its map).
|
|
22
|
-
- **`pause [note]`** / **`resume [ticket]`** / **`split [ticket]`** to the teamwork lifecycle: `../_shared/teamwork.md` (multi-cluster, so it lives in `_shared/`). Read it and follow it.
|
|
23
|
-
- **`auto [map]`** to the **autonomous run** engine in **`references/auto.md`**: it runs
|
|
33
|
+
- **`pause [note]`** / **`resume [ticket]`** / **`split [ticket]`** to the teamwork lifecycle (`resume` first goes through **Move into the handed checkout** when its args end with `--in=<token>`): `../_shared/teamwork.md` (multi-cluster, so it lives in `_shared/`). Read it and follow it.
|
|
34
|
+
- **`auto [map]`** to the **autonomous run** engine in **`references/auto.md`**: it runs a chosen run scope (asked at start: one map, the whiteboard, or everything; `<n>` skips the ask) ticket-by-ticket, suspending one-ticket-per-session, taking every advisory choice as delegate without the confirm gate and stopping only at the safety rail, fog, human-necessary decisions, or human interrupt. Read that file when you engage a run.
|
|
24
35
|
|
|
25
36
|
The cross-cluster situational references and the technique bodies these engines call live in `_shared/`; this cluster names each by its `../_shared/...` path where it needs one (teamwork, out-of-scope, the gh cookbook, the techniques).
|
|
26
37
|
|
|
27
38
|
## Preflight: reject a closed or missing named ticket (before anything else)
|
|
28
39
|
|
|
29
|
-
**When the verb carries an explicit ticket number** (`work <n>` or `quick <n>`), the
|
|
40
|
+
**When the verb carries an explicit ticket number** (`work <n>` or `quick <n>`), the first action (after the move into the handed checkout, when there is one), before config load, before the map load, and before any claim, scope, or isolation setup, is a single cheap state lookup of that ticket: `gh issue view <n> --json state,stateReason,title` (one call, near-free). Branch on the result:
|
|
30
41
|
|
|
31
42
|
- **Open** → proceed into the engine (config load, selection, claim, the rest).
|
|
32
43
|
- **Closed, or the issue does not exist** → **stop immediately and report it by name**, with its close reason where useful (`stateReason`, and the `trailhead:out-of-scope` / `trailhead:superseded` label distinguishes resolved vs out-of-scope vs superseded). Suggest the sensible next step: for a defect in already-closed work open a **new** `bug` per the new-bug-vs-reopen rule (`references/ticket-engines.md`), otherwise pick a frontier ticket. Do **not** load config, load the map, claim, or set up any workspace: a stale or mistyped closed number must be a near-instant, near-free rejection, not a full ramp-up wasted.
|
|
@@ -38,30 +49,32 @@ This guard is the **explicit-number paths only**. Bare `work` selects from the l
|
|
|
38
49
|
The user invokes with a map (URL or number). A ticket is optional: without one, you pick the next decision.
|
|
39
50
|
|
|
40
51
|
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. **Named ticket:** its map becomes the active map, a whiteboard ticket leaves it alone (see **Active map** in `../_shared/multi-map.md`, under **Multiple maps on one repo**). **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**, **don't interrupt the work to offer now**: mark the offer **pending** and surface it **at the handoff**, just before the `/clear`-first next-step block (not as a line inside it: see the **pending codebase offer** rule in `../_shared/session-handoff.md`), exactly once. On the user's yes, generate it via the **Codebase map** technique, then pin it (one of the three permanent repo pins: dashboard, codebase, conventions) and link it from the map's Notes; if the user declines, don't nag again this session. A repo that already has the issue, or is still too thin to map, skips this.
|
|
41
|
-
2. Choose the ticket. **If the user named a ticket, the Preflight above already confirmed it is open** (a closed, superseded, out-of-scope, or nonexistent number stopped there, before any setup ran). If the user names one, use it (a **named** ticket may be a `trailhead:whiteboard` one: `work <n>` works it whole, off the map, with the normal cycle and split allowed; use `quick <n>` for the no-split variant). Otherwise take a frontier ticket to act on, **scoped to this map** (add its `trailhead:map-<n>` label to the frontier query when several maps are live) and **never a whiteboard ticket** (bare `work` stays on the map; the whiteboard frontier is reached via `/trailhead:whiteboard` and `quick`). Bare `work` may act on the first ticket the query returns, but that order is `gh`'s newest-first, not a priority: present several independent tickets as a **set to choose from**, not the graph's natural next step (see **Frontier order carries no priority** in `../_shared/substrate.md`). **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 `../_shared/teamwork.md`). If the ticket proves too big once you're in it, propose a **split** rather than grind, but never split on your own: present the children you would create and wait for the user's go-ahead before creating anything (same file).
|
|
42
|
-
- **First, resolve the effective isolation mode.** The `isolation:` header defaults to **`auto`** (an absent key means `auto`; the engine never writes the key). Under **`auto`**, at work-start **before any file edit or diagnostic probe**, first run the read-only **assigned-ticket pre-check** (full rule in `../_shared/teamwork.md`): other open tickets already assigned to you **pre-bias** the ask toward `worktree`/`clone` (still asking, easy neutral), and the ask is **skipped** only when the session is already inside this ticket's own linked worktree (`trailhead/t<N>`, `N` the current ticket). Then ask the user once whether they will work more than one ticket at once on this machine this session. **No** (a lone ticket): the mode resolves to the current checkout, exactly like `none` (the scope-free check below applies). **Yes**: resolve by repo shape, a plain repo -> `worktree`, a **superrepo** (a `.gitmodules` at the checkout root declaring at least one submodule) -> `clone`; that "yes" **is** the clone gate's answer, so the resolved `clone` path proceeds without re-asking.
|
|
52
|
+
2. Choose the ticket. **If the user named a ticket, the Preflight above already confirmed it is open** (a closed, superseded, out-of-scope, or nonexistent number stopped there, before any setup ran). If the user names one, use it (a **named** ticket may be a `trailhead:whiteboard` one: `work <n>` works it whole, off the map, with the normal cycle and split allowed; use `quick <n>` for the no-split variant; a **named** ticket may also be a `trailhead:paused` one, which `work <n>` / `quick <n>` takes back). Otherwise take a frontier ticket to act on, **scoped to this map** (add its `trailhead:map-<n>` label to the frontier query when several maps are live), **never a `trailhead:paused` ticket** (it is off the frontier: `/trailhead:resume` takes it back) and **never a whiteboard ticket** (bare `work` stays on the map; the whiteboard frontier is reached via `/trailhead:whiteboard` and `quick`). Bare `work` may act on the first ticket the query returns, but that order is `gh`'s newest-first, not a priority: present several independent tickets as a **set to choose from**, not the graph's natural next step (see **Frontier order carries no priority** in `../_shared/substrate.md`). **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 `../_shared/teamwork.md`). A claimed ticket that carries `trailhead:paused` drops it at the claim (`gh issue edit <n> --remove-label trailhead:paused`): working it is resuming it. Likewise a claimed ticket that carries `trailhead:needs-you` (an autonomous run parked it for the human) has the mark cleared at the claim: `node <templates>/trailhead-needs-you.js clear --issue <n> --repo <owner/name>` (fallback by hand: `gh issue edit <n> --remove-label trailhead:needs-you`). A failed `clear` is reported to the user in one line but never blocks the claim. If the ticket proves too big once you're in it, propose a **split** rather than grind, but never split on your own: present the children you would create and wait for the user's go-ahead before creating anything (same file).
|
|
53
|
+
- **First, resolve the effective isolation mode.** The `isolation:` header defaults to **`auto`** (an absent key means `auto`; the engine never writes the key). Under **`auto`**, at work-start **before any file edit or diagnostic probe**, first run the read-only **assigned-ticket pre-check** (full rule in `../_shared/teamwork.md`): other open tickets already assigned to you **pre-bias** the ask toward `worktree`/`clone` (still asking, easy neutral), and the ask is **skipped** only when the session is already inside this ticket's own linked worktree (`trailhead/t<N>`, `N` the current ticket). Then run the remembered-answer reuse: `node <templates>/trailhead-isolation-answer.js check --repo <project root> --others <csv|none|unavailable>`; on `reuse` print the one-line note and resolve as that answer without asking, otherwise (`ask` or `skipped`) ask the user once whether they will work more than one ticket at once on this machine this session, then `node <templates>/trailhead-isolation-answer.js record --repo <project root> --answer yes|no --others <csv|none>` (sequence, 8-hour idle window and exclusions in `../_shared/teamwork.md`). **No** (a lone ticket): the mode resolves to the current checkout, exactly like `none` (the scope-free check below applies). **Yes**: resolve by repo shape, a plain repo -> `worktree`, a **superrepo** (a `.gitmodules` at the checkout root declaring at least one submodule) -> `clone`; that "yes" **is** the clone gate's answer, so the resolved `clone` path proceeds without re-asking. This asks at most once per ticket work-start (a within-session ticket switch re-asks at the new ticket's work-start), and a remembered answer may suppress even that. An autonomous run (`/trailhead:auto`) never asks and never runs the pre-check; it resolves to the current checkout with one parallel slot and to a worktree per ticket above one (see `references/auto.md`). If the header carries an **explicit** `none`/`worktree`/`clone`, honour it as-is (never subject it to auto's ask): explicit `clone` keeps its own gate, explicit `worktree` is eager. See `../_shared/teamwork.md`.
|
|
43
54
|
- **When the resolved mode is the current checkout (`isolation: none`, or `auto` answered no), 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 `../_shared/teamwork.md`.
|
|
44
55
|
- **Write the session-ticket marker**: 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 lets an external tool show "what am I on" offline (the tracker stays the source of truth), but it is not merely that cheap hint: while it is present, the git commit-msg hook also requires every commit to carry a `Refs: #<n>` trailer matching it, so a stale marker left behind would wrongly demand that trailer on unrelated commits. Remove it at Resolve/handoff (see `../_shared/session-handoff.md`); 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** in `../_shared/multi-map.md`.
|
|
45
|
-
- **Sync the commit-msg hook**: at the working root, run `node <templates>/trailhead-commit-msg-sync.js --repo <working-root>` (where `<templates>` lives and what each status means: the commit-msg hook paragraph in `../_shared/teamwork.md`). Stay silent when it reports installed or current. On **upgraded**, give the user one result-oriented line: this clone's commit-msg hook was updated to the current rules. On **foreign**, one line that the user's own hook was left untouched and git does not enforce trailhead's rules here. On **skipped**, one line that nothing was set up here (a `core.hooksPath` without a trailhead hook, or a working root that is not a git repo) and git does not enforce trailhead's rules. On a missing script or a node error (missing means none of the three template dirs has it: try them all before concluding that), inspect the hooks dir (`git rev-parse --git-path hooks`) and report per the fallback in that same paragraph of `../_shared/teamwork.md` (trailhead hook present and executable: enforced but freshness unchecked, update the install; present but not executable: git skips it, not enforced; foreign hook: untouched, not enforced; no hook: not enforced, update the install), never a blind "not enforced"; it never blocks the work.
|
|
46
|
-
- **
|
|
56
|
+
- **Sync the commit-msg hook**: at the working root, run `node <templates>/trailhead-commit-msg-sync.js --repo <working-root>` (where `<templates>` lives and what each status means: the commit-msg hook paragraph in `../_shared/teamwork.md`). Stay silent when it reports installed or current. On **upgraded**, give the user one result-oriented line: this clone's commit-msg hook was updated to the current rules. On **foreign**, one line that the user's own hook was left untouched and git does not enforce trailhead's rules here. On **skipped**, one line that nothing was set up here (a `core.hooksPath` without a trailhead hook, or a working root that is not a git repo) and git does not enforce trailhead's rules. On a missing script or a node error (missing means none of the three template dirs has it: try them all before concluding that), inspect the hooks dir (`git rev-parse --git-path hooks`) and report per the fallback in that same paragraph of `../_shared/teamwork.md` (trailhead hook present and executable: enforced but freshness unchecked, and only while node is on the PATH git runs with, update the install; present but not executable: git skips it, not enforced; foreign hook: untouched, not enforced; no hook: not enforced, update the install), never a blind "not enforced"; it never blocks the work.
|
|
57
|
+
- **Backfill the UAT label once per clone**: at the working root, run `node <templates>/trailhead-uat-backfill.js --repo <owner/name> --marker <working-root>/.trailhead/uat-backfill` (same `<templates>` lookup as the commit-msg sync; pass the marker as an absolute path, since the shell may not sit at the working root). It tags any open ticket with a `UAT of:` line with `trailhead:uat` (a pending UAT follow-up opened before the label existed), and writes the `.trailhead/uat-backfill` marker itself only on a clean run, so later work-starts exit `skipped` with no `gh` call. **Keep the marker gitignored** (add `.trailhead/uat-backfill` to that repo's `.gitignore` if absent): per-clone local state, never committed. Stay silent when it tagged nothing or skipped; when it tagged tickets, one result-oriented line saying how many pending UAT follow-ups now carry the label; on `failed` entries or an `error` status, one line saying the backfill did not finish and will retry at the next work-start.
|
|
58
|
+
- **When the resolved mode is `worktree` or `clone` (explicit, or `auto` answered yes), set up the isolated workspace NOW, before any file edit or diagnostic probe** (not later at Execute/Fix): **if the repo has submodules (a `.gitmodules`), `isolation: worktree` is escalated to `clone`** (a superproject worktree cannot safely carry submodule state; see `../_shared/teamwork.md`). For `worktree` (no submodules), create/enter this ticket's `git worktree` on a `trailhead/t<n>` branch for the package/path the ticket's `Scope:` names (a submodule-bearing repo would have escalated to `clone` above), e.g. `git 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` (add `--recurse-submodules` when the repo has submodules) + the install step, or a **folder copy that brings `node_modules`** (and the submodule working trees) to skip the reinstall (then verify the copy's git is independent, `git -C <copy> rev-parse --absolute-git-dir` resolves inside it, and the same inside every submodule, else commits leak back to the original); a clone with submodules is provisioned via `git submodule update --init --recursive` (nested included), each scoped submodule pinned at the superproject's recorded commit. **If this `clone` was reached via `auto`'s work-start ask (answered yes), that ask already served as the clone gate: do not re-ask, proceed to set up the clone. Under an explicit `isolation: clone`, run the gate as follows.** **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. Right after creating the workspace (before any install or build step), copy the declared gitignored files into it with `node <templates>/trailhead-isolation-copy.js --from <main-checkout> --to <working-root>`, per **Gitignored files in a new workspace** in `../_shared/teamwork.md`. 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 `../_shared/teamwork.md`.
|
|
47
59
|
- **Write the work state, last** (the working root is final only now): `node <templates>/trailhead-work-state.js set --repo <working-root> --map <n> --ticket <n> --title "<title>" --step discuss`, and add `.trailhead/work-state.json` to that repo's `.gitignore` if absent. It is updated at every step change and cleared at Resolve; the canonical description, the `<templates>` lookup, and the silent-on-failure rule are the **Work state file** paragraph in `../_shared/teamwork.md`, and the step mapping per type is in `references/ticket-engines.md`.
|
|
48
|
-
3. **Brief the ticket and confirm scope, before the engine runs.** With the ticket claimed, read it and give the user a **brief, result-oriented summary of what resolving it will do** (its goal and the shape of the work, not a plan yet), then **ask whether
|
|
60
|
+
3. **Brief the ticket and confirm scope, before the engine runs.** With the ticket claimed, read it and give the user a **brief, result-oriented summary of what resolving it will do** (its goal and the shape of the work, not a plan yet), then **ask whether to proceed or change something as an `AskUserQuestion` menu, never as free text**: **Proceed** (Recommended) first, **Change something** second (the user types the change through the menu's built-in "Other" or in a follow-up message), plus the **delegate** option (this gate is a process choice, see `../_shared/choices.md`). On a host with no structured question tool (Codex), the same options are a plain-text numbered list, per its adapter. This is a HITL confirm gate: on **Proceed**, proceed to the engine. **If the user asks for a change, hold it against the ticket.** A change **consistent** with the ticket is folded in and the engine proceeds. A change that **diverges** from the ticket is surfaced as a mismatch, and the user is **asked, never forced**, whether to (a) work the ticket **as-is**, or (b) open a **separate new ticket** for the divergent request, as an `AskUserQuestion` menu **without the delegate option** (routing a scope change is the human's, per the one-line test in `../_shared/choices.md`). Offer both and decide neither: open the separate ticket **only if the user chooses it** (via **Scope that surfaces while working a ticket**, served by the capture cluster, the same capture-or-split path used for scope that surfaces mid-work), never automatically. **If the user defers the choice rather than settling it here, mark the divergent follow-up pending for the Pending follow-up guard (`../_shared/session-handoff.md`), so it is re-posed at the handoff rather than lost.** **Under an autonomous run (`auto`) this is a process confirm gate**, so it follows the advisory boundary in `references/auto.md`: `auto` proceeds (takes the delegate option) without stopping to brief.
|
|
61
|
+
- **Skip the confirm on a ticket this session already shaped.** Skip the brief menu when, in this same session (before any `/clear`), (a) the agent created this ticket from the user's own request for it, one ticket at a time (a capture `todo`/`bug`/`idea`/`seed`, `ticket`, `quick "<text>"`, or a `split` child the user approved), or (b) a Grilling round on this ticket concluded (`grill <ticket>`): the user just stated or settled its scope, so asking again is redundant. Not a trigger: tickets created in bulk as a side effect (`new`/`adopt` charting, `inbox` triage, follow-ons spawned by a grill on another topic), where no one confirmed this ticket's own scope. Skipping replaces the menu with a **one-line**, result-oriented note (e.g. "working <name> (#n), scope as just agreed"), so the user can still interrupt with a change; a change typed then follows the same consistent/divergent handling as the menu's **Change something**. Still asked: a ticket created or grilled in an earlier session, by someone else, or before a `/clear` (a fresh session has no memory of it), a `resume`d ticket, a ticket only read or mentioned this session. `auto` is unchanged (it already takes the confirm as delegate). The evidence is the session's own context, never inferred from tracker metadata (an issue's author or creation time does not prove this session made it).
|
|
49
62
|
4. Resolve it with its type's engine in **`references/ticket-engines.md`**, **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.
|
|
50
|
-
5. Record the resolution: a comment with the answer, `gh issue close`, add the pointer to *Decisions so far
|
|
51
|
-
6. 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
|
|
52
|
-
7. **Hand off.** Close the session with the **Session handoff** ritual (`../_shared/session-handoff.md`): 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, when several mutually independent tickets sit on the frontier, presenting them as a **set to choose from** rather than crowning the first-listed), 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 tells in `../_shared/out-of-scope.md`), **stop and ask the user what to do with each** (a `seed` gated on its trigger, an `idea`, or a `todo`, in this or another map), so nothing wanted-later is lost when the map goes quiet. Only once the deferred items are routed (or the user says leave them), **ask the user whether to close the map issue** now that the destination is reached: `gh issue close` it on a yes (it stays the record), leave it open on a no; never close a map unprompted. Either way **refresh the dashboard** so an exhausted/closed map drops off (a structural event). Maps aren't pinned, so there's no pin to free; the dashboard, codebase, and conventions stay pinned regardless.
|
|
63
|
+
5. Record the resolution: a comment with the answer, `gh issue close`, add the pointer to *Decisions so far* (default path: `node <templates>/trailhead-issue-body.js append --issue <map> --section "Decisions so far" --line "<pointer>"`, which does the fail-safe read-modify-write, the concurrent-edit checks and the secret scan in one call; when the helper is missing, do it by hand per the Fail-safe body rewrites rule in `../_shared/substrate.md`). 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.
|
|
64
|
+
6. 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* (default path: `node <templates>/trailhead-issue-body.js remove --issue <map> --section "Not yet specified" --match "<unique text of the entry>"`, which drops exactly one list entry (no match is a reported no-op, several matches abort); when the helper is missing, do the fail-safe read-modify-write by hand). If the answer reveals a ticket sits beyond the destination, apply the **out-of-scope vs deferred** test (`../_shared/out-of-scope.md`): 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, then **drop its native sub-issue edge** to reclaim a slot, then close: the eager drop in `../_shared/substrate-commands.md`, 404-tolerant, references intact) instead of resolving it. If the decision invalidates other parts of the map, update or delete them.
|
|
65
|
+
7. **Hand off.** Close the session with the **Session handoff** ritual (`../_shared/session-handoff.md`): 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, when several mutually independent tickets sit on the frontier, presenting them as a **set to choose from** rather than crowning the first-listed), 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 tells in `../_shared/out-of-scope.md`), **stop and ask the user what to do with each** (a `seed` gated on its trigger, an `idea`, or a `todo`, in this or another map), so nothing wanted-later is lost when the map goes quiet. Only once the deferred items are routed (or the user says leave them), **ask the user whether to close the map issue** now that the destination is reached: `gh issue close` it on a yes (it stays the record), leave it open on a no; never close a map unprompted. Either way **refresh the dashboard** so an exhausted/closed map drops off (a structural event): `node <templates>/trailhead-dashboard.js` regenerates the whole body from the live tracker in one call (`--dry-run` previews it); when the helper is missing, regenerate it by hand per `../_shared/substrate-commands.md`. The `clear` of the work state (`node <templates>/trailhead-work-state.js clear --repo <working-root>`) also removes the session-ticket marker, so the handoff needs just that one call. Maps aren't pinned, so there's no pin to free; the dashboard, codebase, and conventions stay pinned regardless.
|
|
53
66
|
|
|
54
67
|
## Work one ticket whole, without splitting: `quick`
|
|
55
68
|
|
|
56
69
|
A streamlined way to just get one ticket done, without map ceremony. `quick "<text>"` opens a **whiteboard** ticket (a `build`, or a `bug` when the text is clearly a defect) and works it end to end now; `quick <n>` works an **existing** ticket (whiteboard or map) the same way. For `quick <n>`, the **Preflight** above runs first: a closed, out-of-scope, superseded, or nonexistent ticket stops immediately, before config, claim, or isolation setup. Claim it first; still one ticket per session. **Bare `/trailhead:quick`** (no argument, e.g. offered in a Session handoff) asks for the piece to work, like a capture missing its text, then proceeds as `quick "<text>"`.
|
|
57
70
|
|
|
58
71
|
It runs the **full engine** (the `build`/`bug` cycle in `references/ticket-engines.md`: Discuss → Plan → Execute → Verify → Resolve), differing from `work` on these points:
|
|
59
|
-
- **Briefs and confirms scope first, exactly as Work the map step 3.**
|
|
72
|
+
- **Briefs and confirms scope first, exactly as Work the map step 3.** Whether the menu is posed at all follows **Skip the confirm on a ticket this session already shaped** (Work the map step 3). When it is, with the ticket claimed, summarise what resolving it will do and pose the same scope-confirm menu (`AskUserQuestion`: **Proceed** (Recommended), **Change something**, delegate; numbered list on Codex), applying the same consistency rule (a divergent request is offered as work-as-is vs a separate new ticket, as the same no-delegate menu, opened only if the user chooses it, never forced; via **Scope that surfaces while working a ticket**), and, exactly as Work the map step 3, if the user defers that choice rather than settling it here, mark the divergent follow-up pending for the Pending follow-up guard (`../_shared/session-handoff.md`) so it is re-posed at the handoff. Whether the menu is skipped follows **Skip the confirm on a ticket this session already shaped**: a `quick "<text>"` ticket gets the one-line note, no menu; `quick <n>` follows the same rule as `work`.
|
|
60
73
|
- **Grill only if needed** (Discuss): never auto-grill; start from Plan and stop to ask only if a blocking ambiguity surfaces, exactly as the `build` Discuss. The user may ask to grill at any time.
|
|
61
74
|
- **Never splits.** If the ticket turns out large, work it whole anyway: that is the point of `quick`. Do not `split` it and do not spin children. (New scope that surfaces mid-work is still captured out, never folded in, see **Scope that surfaces while working a ticket**, served by the capture cluster.)
|
|
62
75
|
- **No map narrative book-keeping.** A whiteboard ticket has no map, so there is no `Decisions so far` update, no fog to graduate, no frontier re-scan. Everything else holds: atomic commits with `Refs: #<n>`, TDD / Code review / Acceptance testing per the cycle, the resolution comment + `gh issue close`, and the **Session handoff** (`../_shared/session-handoff.md`). **Exception, `quick <n>` on a *map* ticket: dependent-unblocking still runs** (remove `trailhead:blocked` from any dependent whose last blocker just closed, **Work the map** step 5 above); skipping it would strand those dependents off the frontier for good. Only the *narrative* book-keeping above and the split are skipped, so prefer `work <n>` for a map ticket that has dependents or belongs in the map record.
|
|
63
|
-
- **Same isolation setup as `work`.** `quick` **writes the `.trailhead/session-ticket` marker at the working root in every isolation mode** (exactly as **Work the map** step 2, so tooling like the statusline shows what's in play; the active-map marker follows the same rule as `work`: a named ticket's map becomes the active map, a whiteboard ticket leaves it alone, see **Active map** in `../_shared/multi-map.md`). Under `auto` it runs the same work-start concurrency ask **before any edit** and resolves to current-checkout / worktree / clone by the same rule; under an explicit `isolation: worktree`/`clone` it sets up the ticket's `trailhead/t<n>` workspace before any edit (including the submodule-forces-`clone` escalation and submodule provisioning) and re-checks the claim; under `isolation: none` (or `auto` answered no), the scope-free check applies too. `quick` also runs the same commit-msg hook sync at the working root, exactly as **Work the map** step 2. It also writes the work state last, exactly as **Work the map** step 2, with `--map none` for a whiteboard ticket.
|
|
64
|
-
- **Refreshes the dashboard at the handoff, not mid-work.** A `quick "<text>"` whiteboard ticket is born and resolved in the **same** session, so its dashboard refresh rides with the session's end, not with ticket creation: **do not refresh right after the ticket exists** (that lands a structural pinned-issue write ahead of the work `quick` exists to just-get-done, and only duplicates the resolution refresh a moment later). Refresh at **Resolve** (the handoff), as a **full regeneration from the live tracker** (never a partial patch that leaves other sections knowingly stale), so the whiteboard ticket lands on the dashboard as the session closes. **If the session pauses before Resolve** (`/trailhead:pause`), refresh there instead, so a deliberately-unfinished quick still shows (the read-only renders never rewrite the dashboard body, so nothing else would surface it). `quick <n>` on a *map* ticket does **not** refresh (map ticket progress shows via the native bar). See **The dashboard** and its freshness rule in `../_shared/substrate.md`.
|
|
76
|
+
- **Same isolation setup as `work`.** A claimed ticket that carries `trailhead:paused` drops it at the claim, exactly as in **Work the map** step 2. It clears `trailhead:needs-you` the same way (`node <templates>/trailhead-needs-you.js clear --issue <n> --repo <owner/name>`); a failed `clear` is reported to the user in one line but never blocks the claim. `quick` **writes the `.trailhead/session-ticket` marker at the working root in every isolation mode** (exactly as **Work the map** step 2, so tooling like the statusline shows what's in play; the active-map marker follows the same rule as `work`: a named ticket's map becomes the active map, a whiteboard ticket leaves it alone, see **Active map** in `../_shared/multi-map.md`). Under `auto` it runs the same work-start concurrency ask (with the same `check`/`record` answer reuse) **before any edit** and resolves to current-checkout / worktree / clone by the same rule; under an explicit `isolation: worktree`/`clone` it sets up the ticket's `trailhead/t<n>` workspace before any edit (including the submodule-forces-`clone` escalation and submodule provisioning) and re-checks the claim; under `isolation: none` (or `auto` answered no), the scope-free check applies too. Under `worktree`/`clone` it also runs the same gitignored-file copy (`trailhead-isolation-copy.js --from <main-checkout> --to <working-root>`) right after creating the workspace. `quick` also runs the same commit-msg hook sync at the working root, exactly as **Work the map** step 2. It also writes the work state last, exactly as **Work the map** step 2, with `--map none` for a whiteboard ticket.
|
|
77
|
+
- **Refreshes the dashboard at the handoff, not mid-work.** A `quick "<text>"` whiteboard ticket is born and resolved in the **same** session, so its dashboard refresh rides with the session's end, not with ticket creation: **do not refresh right after the ticket exists** (that lands a structural pinned-issue write ahead of the work `quick` exists to just-get-done, and only duplicates the resolution refresh a moment later). Refresh at **Resolve** (the handoff), as a **full regeneration from the live tracker** (`node <templates>/trailhead-dashboard.js`, never a partial patch that leaves other sections knowingly stale), so the whiteboard ticket lands on the dashboard as the session closes. **If the session pauses before Resolve** (`/trailhead:pause`), refresh there instead, so a deliberately-unfinished quick still shows (the read-only renders never rewrite the dashboard body, so nothing else would surface it). `quick <n>` on a *map* ticket does **not** refresh (map ticket progress shows via the native bar). See **The dashboard** and its freshness rule in `../_shared/substrate.md`.
|
|
65
78
|
- **Always size-triages, independent of `config.effort`.** `quick` is the home of the cost-aware path: reaching for `quick` is itself the signal the piece is small, so it runs the **same size triage and ask-to-skip offer defined canonically under effort in `../_shared/configuration-reference.md`** (the same one the `build`/`bug` engine runs, `references/ticket-engines.md`) **every time, at the start before planning, whatever `config.effort` is set to**. `quick` does not wait for `effort: lean`, and an explicit `effort: standard` does not suppress it: `effort` governs `work` / the map cycle, not `quick`. On a trivial/small ticket it surfaces the one consolidated offer (the plan pass, TDD, cross-AI review, the full code review, the goal-backward verify) and goes lean **only on an explicit yes**; on a large ticket there is no offer and the full cycle runs. The correctness spine (atomic `Refs:` commits, tests/criterion, post-commit self-check, Resolve) is never in the offer, whatever the size or the answer.
|
|
66
79
|
|
|
67
80
|
Type follows the ticket: `quick "<text>"` defaults to `build` (`bug` if a defect); `quick <n>` uses the existing type and its matching engine.
|