@marcomigozzi/trailhead 0.17.0 → 0.18.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 +28 -18
- package/README.md +28 -18
- package/bin/base-preflight-docs.test.js +4 -1
- package/bin/bulk-cross-reference-docs.test.js +36 -0
- package/bin/engine-route-docs.test.js +7 -0
- package/bin/facet-stamp-docs.test.js +79 -0
- package/bin/forced-mutation-docs.test.js +38 -0
- package/bin/guard-axis-docs.test.js +69 -0
- package/bin/inbox-needs-info-docs.test.js +68 -0
- package/bin/lib/codex-projection.js +8 -8
- package/bin/lib/codex-projection.test.js +19 -3
- package/bin/needs-you-docs.test.js +9 -2
- package/bin/options-in-chat-docs.test.js +65 -0
- package/bin/pick-stress-test-docs.test.js +1 -1
- package/bin/pipeline-review-docs.test.js +118 -0
- package/bin/plan-review-budget-docs.test.js +63 -0
- package/bin/repo-target-docs.test.js +41 -0
- package/bin/seam-coverage-docs.test.js +46 -0
- package/bin/standards-brief-docs.test.js +112 -0
- package/bin/test-once-docs.test.js +101 -0
- package/bin/test-run-docs.test.js +47 -0
- package/bin/trailhead.js +9 -4
- package/bin/trailhead.test.js +18 -6
- package/bin/yes-accepts-recommended-docs.test.js +54 -0
- package/package.json +1 -1
- package/plugins/trailhead/.claude-plugin/plugin.json +1 -1
- package/plugins/trailhead/agents/trailhead-code-review.md +2 -1
- package/plugins/trailhead/agents/trailhead-grill.md +2 -2
- package/plugins/trailhead/agents/trailhead-plan.md +1 -1
- package/plugins/trailhead/agents/trailhead-verify.md +1 -0
- package/plugins/trailhead/hooks/lib/audit-walk.test.js +62 -0
- package/plugins/trailhead/hooks/lib/git-env-mod.test.js +55 -0
- package/plugins/trailhead/hooks/lib/git-env-spawn.test.js +73 -0
- package/plugins/trailhead/hooks/lib/git-env.test.js +51 -0
- package/plugins/trailhead/hooks/lib/global-config-dir.test.js +48 -0
- package/plugins/trailhead/hooks/lib/heredoc-mod.test.js +85 -0
- package/plugins/trailhead/hooks/lib/inbox-needs-info.test.js +179 -0
- package/plugins/trailhead/hooks/lib/issue-body.test.js +81 -0
- package/plugins/trailhead/hooks/lib/needs-you.test.js +45 -1
- package/plugins/trailhead/hooks/lib/plan-review-rounds.test.js +272 -0
- package/plugins/trailhead/hooks/lib/repo-target.test.js +256 -0
- package/plugins/trailhead/hooks/lib/statusline-git-env.test.js +47 -0
- package/plugins/trailhead/hooks/lib/test-run.test.js +757 -0
- package/plugins/trailhead/hooks/lib/trailhead-git-env.js +40 -0
- package/plugins/trailhead/hooks/mod/README.md +30 -14
- package/plugins/trailhead/hooks/mod/auto-run.ts +2 -1
- package/plugins/trailhead/hooks/mod/git-env.ts +31 -0
- package/plugins/trailhead/hooks/mod/heredoc.ts +79 -0
- package/plugins/trailhead/hooks/mod/map-pane-view.ts +3 -1
- package/plugins/trailhead/hooks/mod/map-pane.tsx +144 -45
- package/plugins/trailhead/hooks/mod/repo-pin.ts +105 -4
- package/plugins/trailhead/hooks/mod/work-state-locate.ts +2 -1
- package/plugins/trailhead/hooks/trailhead-bash-guards.js +8 -8
- package/plugins/trailhead/hooks/trailhead-bash-guards.test.js +28 -14
- package/plugins/trailhead/hooks/trailhead-check-update.js +3 -1
- package/plugins/trailhead/hooks/trailhead-labels-guard.js +2 -2
- package/plugins/trailhead/hooks/trailhead-labels-guard.test.js +1 -1
- package/plugins/trailhead/hooks/trailhead-test-guard.js +232 -0
- package/plugins/trailhead/hooks/trailhead-test-guard.test.js +172 -0
- package/plugins/trailhead/skills/_shared/capture-core.md +5 -0
- package/plugins/trailhead/skills/_shared/configuration-reference.md +12 -0
- package/plugins/trailhead/skills/_shared/load-first.md +2 -0
- package/plugins/trailhead/skills/_shared/substrate-commands.md +11 -2
- package/plugins/trailhead/skills/_shared/substrate.md +3 -2
- package/plugins/trailhead/skills/_shared/teamwork.md +5 -3
- package/plugins/trailhead/skills/_shared/techniques/code-review-pipeline.md +24 -0
- package/plugins/trailhead/skills/_shared/techniques/code-review.md +18 -5
- package/plugins/trailhead/skills/_shared/techniques/codebase-map.md +2 -2
- package/plugins/trailhead/skills/_shared/techniques/debug.md +1 -1
- package/plugins/trailhead/skills/_shared/techniques/grilling.md +3 -3
- package/plugins/trailhead/skills/_shared/techniques/plan-review.md +16 -4
- package/plugins/trailhead/skills/_shared/techniques/tdd.md +4 -2
- package/plugins/trailhead/skills/_shared/techniques/verify.md +2 -0
- package/plugins/trailhead/skills/_shared/techniques.md +3 -3
- package/plugins/trailhead/skills/trailhead-chart/SKILL.md +2 -2
- package/plugins/trailhead/skills/trailhead-chart/references/charting.md +1 -1
- package/plugins/trailhead/skills/trailhead-chart/references/inbox.md +8 -3
- package/plugins/trailhead/skills/trailhead-manage/references/auditing.md +17 -2
- package/plugins/trailhead/skills/trailhead-work/SKILL.md +1 -0
- package/plugins/trailhead/skills/trailhead-work/references/auto.md +2 -0
- package/plugins/trailhead/skills/trailhead-work/references/executor-brief.md +2 -0
- package/plugins/trailhead/skills/trailhead-work/references/ticket-engines.md +7 -7
- package/plugins/trailhead/templates/statusline-trailhead.sh +10 -3
- package/plugins/trailhead/templates/trailhead-audit-walk.js +56 -4
- package/plugins/trailhead/templates/trailhead-auto-merge.js +2 -1
- package/plugins/trailhead/templates/trailhead-auto-queue.js +2 -1
- package/plugins/trailhead/templates/trailhead-auto-slot.js +2 -1
- package/plugins/trailhead/templates/trailhead-base-preflight.js +10 -5
- package/plugins/trailhead/templates/trailhead-capture.js +2 -1
- package/plugins/trailhead/templates/trailhead-checkout-find.js +3 -2
- package/plugins/trailhead/templates/trailhead-checkout-in.js +2 -1
- package/plugins/trailhead/templates/trailhead-commit-msg-sync.js +2 -1
- package/plugins/trailhead/templates/trailhead-git-env.js +40 -0
- package/plugins/trailhead/templates/trailhead-inbox-needs-info.js +161 -0
- package/plugins/trailhead/templates/trailhead-isolation-answer.js +2 -1
- package/plugins/trailhead/templates/trailhead-isolation-copy.js +9 -4
- package/plugins/trailhead/templates/trailhead-issue-body.js +92 -6
- package/plugins/trailhead/templates/trailhead-merge-ticket.js +2 -1
- package/plugins/trailhead/templates/trailhead-needs-you.js +38 -4
- package/plugins/trailhead/templates/trailhead-plan-review-rounds.js +261 -7
- package/plugins/trailhead/templates/trailhead-repo-target.js +302 -0
- package/plugins/trailhead/templates/trailhead-test-run.js +752 -0
- package/plugins/trailhead/templates/trailhead-uat-backfill.js +2 -1
- package/plugins/trailhead/templates/trailhead-uat-followup.js +2 -1
- package/plugins/trailhead/templates/trailhead-uat-setup.js +9 -4
- package/plugins/trailhead/templates/trailhead-unblock.js +2 -1
- package/plugins/trailhead/tests/auto-run-ui.test.ts +2 -1
- package/plugins/trailhead/tests/git-env.test.ts +20 -0
- package/plugins/trailhead/tests/map-pane-ui.test.ts +268 -17
- package/plugins/trailhead/tests/map-pane-view.test.ts +6 -0
- package/plugins/trailhead/tests/map-pane-write.test.ts +57 -1
- package/plugins/trailhead/tests/repo-pin-late-repo.test.ts +3 -2
- package/plugins/trailhead/tests/repo-pin-relocate.test.ts +175 -0
- package/plugins/trailhead/tests/unscrub.ts +8 -0
- package/plugins/trailhead/tests/work-state-prompt-hook.test.ts +3 -2
- package/plugins/trailhead/tests/work-state-worktree.test.ts +2 -1
|
@@ -5,11 +5,11 @@ Interrogate the human live, one question at a time, until the decision is unambi
|
|
|
5
5
|
|
|
6
6
|
- **Map the decision as a tree.** Every choice branches into the choices that hang off it. The *frontier* is every question whose prerequisites are already settled: the ones you can ask now without guessing. A question whose answer depends on another still-open one isn't on the frontier yet; it arrives there the moment that answer lands. You don't ask the frontier in a batch: you pick one from it, and recompute after the answer.
|
|
7
7
|
- **Breadth before depth: enumerate the whole surface first.** When the subject is a space with many parts (mapping an app or a project's scope, not a single isolated choice), open by enumerating every functional area it implies (for a video player: media library, search, thumbnails, filters, genres, the player itself, ...) and get them all onto the record before deep-diving any one. Depth on the current thread is seductive, but the tree only ever grows the questions you actually raise, so an area you never name never enters it and stays silently missing. Fan out first (name every area), then converge each. The deliverable is coverage of the whole surface, not a deep answer on one corner of it.
|
|
8
|
-
- **One question at a time, with your pick.** Number the question, state it, and offer your recommended answer, stress-tested per the next bullet. Then stop and wait. Each answer reshapes the tree: settled decisions push the frontier outward. Recompute and ask the next. Grilling stays human-decides: the **grilled questions themselves never offer the delegate option** (a recommended answer is not the delegate option), because converging a decision is the human's. A separate meta-ask a call-site may put *before* grilling (e.g. the `build`/`quick` Discuss "do you want a grilling round?") is its own advisory choice and can offer delegate/defer per that call-site. **When a grilled decision is genuinely blocked on a prerequisite, offer defer in prose** (state it in words as part of the question), per the recipe in `../choices.md`.
|
|
8
|
+
- **One question at a time, with your pick.** Number the question, state it, and offer your recommended answer, stress-tested per the next bullet. **A yes accepts the recommendation.** Word every question around your pick, so a plain "yes" accepts your pick: "Use Postgres for the queue? The alternative is Redis, ...", never an open "should we A or B?" followed by a recommendation, where a yes could mean either. A yes to any grilling question has exactly one meaning; any other answer names what they want instead. A play-back follows the same rule: your restated understanding is the pick, so a yes confirms it. Then stop and wait. Each answer reshapes the tree: settled decisions push the frontier outward. Recompute and ask the next. Grilling stays human-decides: the **grilled questions themselves never offer the delegate option** (a recommended answer is not the delegate option), because converging a decision is the human's. A separate meta-ask a call-site may put *before* grilling (e.g. the `build`/`quick` Discuss "do you want a grilling round?") is its own advisory choice and can offer delegate/defer per that call-site. **When a grilled decision is genuinely blocked on a prerequisite, offer defer in prose** (state it in words as part of the question), per the recipe in `../choices.md`.
|
|
9
9
|
- **Stress-test your pick before offering it**, per **Stress-test the pick before offering it** in `../choices.md` (one pass, its `Weak point:` line shown with the pick). In grilling, its fit check names the earlier questions it touches by question number; it stays a local check, since the whole set is weighed later, in the holistic review below.
|
|
10
10
|
- **Facts are your job, decisions are theirs.** When a question needs a fact from the environment (filesystem, code, tools), go find it yourself; never ask the human what you could look up. Only questions downstream of that lookup wait; ask the rest now.
|
|
11
11
|
- **Refuse vague and hedged answers.** "Good," "fast," "simple," "users," "later" are not answers: probe. *Fast how: sub-second, or handles large datasets? Users: the Customer or the operator?* Make the abstract concrete: "walk me through it," "give me an example," "what does that actually look like?"
|
|
12
|
-
- **Offer concrete options to react to, and show them, not just name them.** Present 2–4 sharp interpretations or specific choices that reveal priorities: never generic buckets ("technical/business/other") or leading options. **Naming the options is not enough: illustrate each one before asking the human to choose**, so the alternatives are legible without the user having to ask. For each option say what it is, what it optimizes for, and what it sacrifices; for a choice spanning several dimensions, lay the options out side by side (a comparison table or widget) as the natural default, rather than listing A/B/C inline in the question and waiting for the user to request the detail. But when the human signals they want to explain in their own words, drop the options and let them; resume structured questions after.
|
|
12
|
+
- **Offer concrete options to react to, and show them, not just name them.** Present 2–4 sharp interpretations or specific choices that reveal priorities: never generic buckets ("technical/business/other") or leading options. **Naming the options is not enough: illustrate each one before asking the human to choose**, so the alternatives are legible without the user having to ask. For each option say what it is, what it optimizes for, and what it sacrifices; for a choice spanning several dimensions, lay the options out side by side (a comparison table or widget) as the natural default, rather than listing A/B/C inline in the question and waiting for the user to request the detail. **The ask is answerable from the chat alone.** Options posted on the ticket (an `OPTIONS` comment, or any comment) are the record, never the ask surface: the turn that asks the human to pick renders every option asked about in chat (its label, a one-line summary, its trade-off, plus the recommendation), or offers them as choices of the host's structured question tool; never "I posted the options on the ticket, pick A1..An" with no option text. But when the human signals they want to explain in their own words, drop the options and let them; resume structured questions after.
|
|
13
13
|
- **Surface hidden assumptions.** Say the unstated premise out loud and put it to them: "this only works if X is true, is it?" Cross-check claims against the code; when they contradict, name the contradiction and force a choice.
|
|
14
14
|
- **Steelman and play devil's advocate.** State the strongest case for the option they're leaning away from. Attack the option they favor from its weakest seam. If it survives, the decision is stronger; if it cracks, you found the real question.
|
|
15
15
|
- **Detect and open up tradeoffs.** When "or / versus / torn between / which is better" appears, pause: name what each side optimizes for, what each sacrifices, which aligns with the goal, then recommend with reasoning. Skip for trivial or already-decided choices.
|
|
@@ -33,7 +33,7 @@ When it runs as `trailhead-grill`, its output is always the report: it never ask
|
|
|
33
33
|
1. Send the agent the full state: this protocol (plus `domain-vocabulary.md` where the call site pairs it); the topic or the ticket body, the relevant decisions, the tickets the subject names and the destination; whether the subject is a single choice or a multi-part space; the numbered Q&A so far with the last `settled`, `frontier`, `areas` and `facts`; the review round (0, 1 or 2) and the last apply/amend/keep answer.
|
|
34
34
|
2. To keep rounds stateless, re-dispatch with the full state each round. Continuing the same agent is allowed, but it still gets the full state. Carrying `facts` and `settled` forward keeps re-reading down.
|
|
35
35
|
3. Check the report with `node <templates>/trailhead-report-status.js --technique grill --file <report>` (see *Malformed review report* in `../techniques.md`), then branch on the returned verdict:
|
|
36
|
-
- `question`: relay `question` and `recommended` to the user in prose. Never answer on the user's behalf and never proceed on silence. A vague answer goes back to the agent to probe. A `not looked up:` fact is fetched by the orchestrator and passed in the next round.
|
|
36
|
+
- `question`: relay `question` and `recommended` to the user in prose, with every option in full per **The ask is answerable from the chat alone** (if the `question` only names them, ask the agent to re-render it), and worded per **A yes accepts the recommendation**, which the relay keeps (a question that breaks it goes back to the agent the same way). Never answer on the user's behalf and never proceed on silence. A vague answer goes back to the agent to probe. A `not looked up:` fact is fetched by the orchestrator and passed in the next round.
|
|
37
37
|
- `review` with `amendments` or `conflicts` not `none`: put the apply/amend/keep `AskUserQuestion` menu to the user (no delegate, no defer), then re-dispatch with the answer. The orchestrator caps reviews at two; anything still standing is recorded as accepted risk.
|
|
38
38
|
- `review` with both fields `none`: play back `settled`, confirm with the user and record it where the call site says.
|
|
39
39
|
|
|
@@ -8,15 +8,27 @@ When enabled, run this after the PLAN is drafted and before Execute:
|
|
|
8
8
|
|
|
9
9
|
2. **Send the same brief to each reviewer.** Give every CLI the ticket's `## Question`, the drafted PLAN, and the map's relevant Notes/decisions in a **self-contained prompt**, so the reviewer needn't explore the repo. Ask for a short list of concerns (*risks, missing steps, wrong assumptions, simpler alternatives*) each tagged **blocking** or **non-blocking**.
|
|
10
10
|
|
|
11
|
+
**Budget each call, then capture its output.** Write the brief once to a file in the plan step's private `mktemp -d` directory. Before every reviewer call run `node <templates>/trailhead-plan-review-rounds.js budget --repo <project-root> --reviewer <cli> --prompt <brief file>` (it estimates the tokens against that CLI's context window, see `plan_review.context` in `../configuration-reference.md`) and follow its `decision`:
|
|
12
|
+
- `arg`: the prompt is small; make the inline call exactly as below, the prompt as the argument.
|
|
13
|
+
- `file`: pass the helper's `pointer` (a short "read the brief in this file" instruction) as the prompt argument, never the brief itself as one argument: a single Linux argument over 128 KiB fails with E2BIG.
|
|
14
|
+
- `skip`: do not invoke that reviewer; record it as skipped with the helper's `reason`.
|
|
15
|
+
|
|
16
|
+
**Every reviewer call goes through the `run` subcommand, whatever the launch mode**: `node <templates>/trailhead-plan-review-rounds.js run --output <dir>/<cli>.out -- <the CLI's headless command>`, launched with **`run_in_background: true`** by default. The helper is what bounds the call (a wall-clock deadline of 600 seconds, `--timeout <seconds>` to change it; SIGTERM then SIGKILL to the reviewer's whole process group), because the Bash tool's `timeout` parameter does not apply to a background call. It also closes stdin and captures stdout and stderr together into the output file, and prints one JSON line. On Claude Code the completion notice therefore always arrives within the bound, so polling needs no stuck-detection judgement. When the shell tool itself fails to launch the call (E2BIG raised before the helper starts), write its error text into that same file. Branch on the JSON `status`:
|
|
17
|
+
- `ok`: collect the output file.
|
|
18
|
+
- `timeout`: the reviewer hit the deadline. **Do not run `overflow`** (partial output of a stalled call is not an overflow signal); apply the one-retry rule below. A second timeout **drops** the reviewer, recorded as skipped with the reason `second timeout` and the seconds from the JSON.
|
|
19
|
+
- `failed` or `error`: run `node <templates>/trailhead-plan-review-rounds.js overflow --output <dir>/<cli>.out --prompt <brief file>` (the brief lets it ignore lines the CLI echoed back; rate-limit errors never count as an overflow): `overflow: true` means the prompt exceeded the reviewer's context (the estimate is not a guarantee, this catches what it missed), so **do not retry**; record the reviewer as skipped with the returned `reason`. Otherwise the one-retry rule below applies.
|
|
20
|
+
|
|
21
|
+
No human prompt anywhere on this path. The retried and the dropped reviewer are reported in one line each, and a reviewer is never reported as "still running" past the bound. **When `budget` or `overflow` itself fails** (exit 1 or 2, a missing helper or non-JSON output), never guess: for `budget`, fall back to the inline call when the brief is at most 100 KiB and skip the reviewer otherwise, recording the helper's error as the reason; for `overflow`, apply the one-retry rule as for any failed call. **When the `run` helper is missing, exits non-zero with no JSON line (a helper killed by a signal prints none), or prints non-JSON output** (never an unbounded call; a `status: error` JSON line is not this case, it takes the `failed`/`error` branch above): launch the CLI as `timeout -k 10 600 <cmd> < /dev/null > <dir>/<cli>.out 2>&1` (or `gtimeout` where GNU `timeout` is installed as that), where exit 124 or 137 means a timeout and is handled as above; with neither available, skip the reviewer, recording that no bound was available as the reason.
|
|
22
|
+
|
|
11
23
|
Invoke each CLI **headless, in a fast read-only posture, with a generous timeout**. These are full agentic CLIs (startup, sandbox, reasoning), so a tight ~60s wall-clock is the usual cause of a "timed out" review, not a stuck reviewer. Two levers, both needed:
|
|
12
|
-
- **Fast posture.** Make the call quick and non-interactive so it can't stall on an approval prompt or a heavy reasoning pass. For **codex**: `codex exec -s read-only -c model_reasoning_effort=low "<prompt>"
|
|
13
|
-
- **Never block the session; background each reviewer and collect by polling.** Reviews never block the session (a hard engine default, see *Reviews never block the session* in `../techniques.md`), so the **default posture on every reviewer call, from the first attempt**, is to launch
|
|
24
|
+
- **Fast posture.** Make the call quick and non-interactive so it can't stall on an approval prompt or a heavy reasoning pass. The headless command of each CLI is what you pass to `run` after `--`. For **codex**: `codex exec -s read-only -c model_reasoning_effort=low "<prompt>"` (read-only = nothing to approve; low effort = quick), or a preconfigured profile with `-p <profile>`. For **gemini**: `gemini -p "<prompt>"`; **qwen**: `qwen -p "<prompt>"`; **kimi** (Kimi Code): `kimi -p "<prompt>"` (`-m <model>`); **agy** (Antigravity): `agy --print-timeout 540s -p "<prompt>"` (it runs long, so give it a wide timeout floor). Verify each tool's exact headless flag and config key for your version. **Stdin must be closed on every reviewer call**: these agentic CLIs (codex most notably) also read a prompt from stdin, so a call that inherits an open or attached stdin may wait for EOF; passing the prompt as an argument does not exempt it. The `run` helper closes it (the raw fallback form appends `< /dev/null`). The line `Reading additional input from stdin...` is **not evidence of a stdin hang**: codex-cli prints it with stdin already closed, so never diagnose a stall from it. Whether it depends on the stdin state with an open pipe was not tested.
|
|
25
|
+
- **Never block the session; background each reviewer and collect by polling.** Reviews never block the session (a hard engine default, see *Reviews never block the session* in `../techniques.md`), so the **default posture on every reviewer call, from the first attempt**, is to launch the `run` call with **`run_in_background: true`** and collect its output file when it finishes, polling for progress meanwhile; backgrounding also runs a fan-out of reviewers in parallel instead of serially eating the wall-clock. These calls routinely run **several minutes**, and this harness's Bash tool **defaults to a ~2-minute cap that silently cuts a synchronous call**. A reviewer that must be run synchronously instead (a host with no background form) still goes through `run`, with the Bash tool's **`timeout` parameter set to its maximum** (`600000` ms = 10 min) and above the helper's deadline, so the helper rather than the tool ends the call. Either way, **never rely on the default ~2-minute timeout for these calls**. **One retry (never after a context overflow):** if a call ends in `timeout` (or fails without an overflow), retry once; only on a second timeout or a hard error do you **drop** that reviewer and continue. A slow reviewer must never wedge or fail the build. **On Codex** (not Claude Code) the Bash-tool `timeout` / `run_in_background` knobs above do not exist: map them onto Codex's own shell/exec long-timeout, or a background-and-collect posture around the same `run` call, per §H of the projected adapter header.
|
|
14
26
|
|
|
15
|
-
(**Durable fix:** pin each reviewer CLI to a fast "passive posture" once, in the CLI's own config, e.g. codex's `~/.codex/config.toml` model + reasoning effort, so every review call is quick by default. Setting such a profile once is the durable fix; the inline flags above are the per-call equivalent. The fast posture makes a call *quicker* but does not replace non-blocking collection: background the reviewer by default, and **when a reviewer must run synchronously instead, still set the explicit long timeout alongside the fast posture** so a still-slow call is not *cut
|
|
27
|
+
(**Durable fix:** pin each reviewer CLI to a fast "passive posture" once, in the CLI's own config, e.g. codex's `~/.codex/config.toml` model + reasoning effort, so every review call is quick by default. Setting such a profile once is the durable fix; the inline flags above are the per-call equivalent. The fast posture makes a call *quicker* but does not replace non-blocking collection: background the reviewer by default, and **when a reviewer must run synchronously instead, still set the explicit long timeout alongside the fast posture** so a still-slow call is not *cut*; the `run` helper's deadline is that bound.)
|
|
16
28
|
|
|
17
29
|
3. **Converge.** Collect the concerns. The planner (on `config.models.plan`) revises the PLAN to resolve every **blocking** concern and weighs the non-blocking ones. If the plan changed materially, re-review; loop up to `config.plan_review.rounds` (default 2), or, under an autonomous run, up to the run's start-up choice (see `trailhead-work/references/auto.md`, which overrides the key for that run). After each round, write the blocking concerns still open to the ticket's history file (a JSON array, one entry per round, each concern `{"key","text"}` with a short stable `key` reused across rounds; keep it in a private `mktemp -d` directory for the plan step) and call `node <templates>/trailhead-plan-review-rounds.js round --choice <config|1|2|3|until> --configured <n> --history <file>`; the orchestrator follows its decision: `converged` stops clean, `continue` revises and re-reviews, and `exhausted`, `no-progress` or `ceiling` stop with blocking concerns surviving (`surviving` carries their full text). Outside an autonomous run, with the key's rounds, stop when no blocking concerns remain or the rounds are exhausted.
|
|
18
30
|
|
|
19
|
-
4. **Record and decide.** Post the final PLAN plus a short **`PLAN-REVIEW`** comment: who reviewed, the concerns raised, and how each was resolved or consciously deferred. **If blocking concerns survive the last round** (the rounds are exhausted with concerns still open), stopping is fine, but **say so plainly and never advance silently in either direction**: tell the user in the chat that the review stopped with the rounds spent and blocking concerns still open, **name each surviving blocking concern**, and **put the choice to them**: (a) **run another review cycle** (re-review + revise for more rounds, the point of this ask), (b) proceed to Execute accepting or consciously deferring the surviving concerns, (c) keep revising the plan first, then re-review, or (d) **run until it converges** (keep the revise + re-review loop going with no round cap, stopping only when no blocking concern remains; as a guard against an endless loop, if a round makes no progress, i.e. the same blocking concerns come back unresolved, stop and bring the choice back to this checkpoint, naming them). **Wait for their pick; never auto-proceed to Execute, and never just stop, over open blocking concerns.** This checkpoint's choice **offers** the delegate/defer options, per the injection recipe in `../choices.md`.
|
|
31
|
+
4. **Record and decide.** Post the final PLAN plus a short **`PLAN-REVIEW`** comment: who reviewed, the concerns raised, and how each was resolved or consciously deferred. It also lists **every reviewer skipped or dropped, with its reason** (budget skip, context overflow, second timeout, hard error): never a silent drop. If every reviewer ends up skipped, the no-reviewer-left rule of step 1 applies. **If blocking concerns survive the last round** (the rounds are exhausted with concerns still open), stopping is fine, but **say so plainly and never advance silently in either direction**: tell the user in the chat that the review stopped with the rounds spent and blocking concerns still open, **name each surviving blocking concern**, and **put the choice to them**: (a) **run another review cycle** (re-review + revise for more rounds, the point of this ask), (b) proceed to Execute accepting or consciously deferring the surviving concerns, (c) keep revising the plan first, then re-review, or (d) **run until it converges** (keep the revise + re-review loop going with no round cap, stopping only when no blocking concern remains; as a guard against an endless loop, if a round makes no progress, i.e. the same blocking concerns come back unresolved, stop and bring the choice back to this checkpoint, naming them). **Wait for their pick; never auto-proceed to Execute, and never just stop, over open blocking concerns.** This checkpoint's choice **offers** the delegate/defer options, per the injection recipe in `../choices.md`.
|
|
20
32
|
|
|
21
33
|
**Under an autonomous run the checkpoint is not posed.** The rounds were chosen at start-up, so once the helper reports surviving blocking concerns, post the `PLAN` and `PLAN-REVIEW` comments, then park the ticket with `node <templates>/trailhead-needs-you.js park` (the reason listing the surviving blocking concerns with the full `text` of each, never just its key) and move on; the run continues with the next ticket. Interactive behaviour (outside a run) is unchanged: the checkpoint above stands.
|
|
22
34
|
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# TDD
|
|
2
2
|
Use TDD wherever behaviour has a definable input→output: business logic, parsers, validators, transforms, algorithms, state machines, API contracts. The gate: can you write `expect(fn(input)).toBe(output)` before `fn` exists? If yes, go test-first. Skip it for UI layout/styling, config, glue, one-off scripts, trivial CRUD, and exploratory spikes; add tests after if they earn their keep.
|
|
3
3
|
|
|
4
|
-
Before writing any test, name the **seams** (the public boundaries where behaviour is observable without reaching inside) and confirm them. Tests live at seams, never against internals. Work in vertical slices, one behaviour at a time: one test → one implementation → repeat. Never write all tests then all code (that tests *imagined* behaviour and locks in structure you don't yet understand).
|
|
4
|
+
Before writing any test, name the **seams** (the public boundaries where behaviour is observable without reaching inside) and confirm them; the PLAN lists each one with its one line, `<seam>: catches <what a test here fails on>; misses <what gets through it>`, so a coverage gap shows before any code is written (`misses` is never empty: a seam that seems to miss nothing has not been looked at). Tests live at seams, never against internals. Work in vertical slices, one behaviour at a time: one test → one implementation → repeat. Never write all tests then all code (that tests *imagined* behaviour and locks in structure you don't yet understand).
|
|
5
5
|
|
|
6
|
-
- **RED**: one failing test that pins the intended behaviour as a specification (`should reject empty email`, not `test1`). Run it; SEE it fail for the *right reason*: a real assertion mismatch, not an import/syntax error. If it passes before you write code, STOP: the feature already exists or the test is wrong. Commit `test(...): add failing test for X`.
|
|
6
|
+
- **RED**: one failing test that pins the intended behaviour as a specification (`should reject empty email`, not `test1`). Run it; SEE it fail for the *right reason*: a real assertion mismatch, not an import/syntax error. If it passes before you write code, STOP: the feature already exists or the test is wrong. When the red comes from a forced change (you mutated code or a fixture to make the test fail), prove the change landed before trusting the run: diff the mutated file against a clean copy, or re-read it, and see the mutation there. A mutation that silently failed to apply leaves the code as it was, so the run proves nothing: a red then has another cause, and a green says nothing about the test. Commit `test(...): add failing test for X`.
|
|
7
7
|
- **GREEN**: the minimal code to pass. No cleverness, no speculative features, no anticipating future tests. If unrelated tests break, stop and investigate coupling. Commit `feat(...): implement X`.
|
|
8
8
|
- **REFACTOR**: only with tests green, clean up; rerun. Tests MUST still pass or you undo and go smaller. Commit if changed: `refactor(...): clean up X`.
|
|
9
9
|
|
|
10
|
+
Every RED, GREEN and REFACTOR run executes focused tests only (the files at hand); the full suite runs once per ticket, at Verify.
|
|
11
|
+
|
|
10
12
|
A good test verifies behaviour through the public interface and survives refactors. Reject **implementation-coupled** tests (mock internal collaborators, assert private state, hit the DB not the interface; break on refactor though behaviour didn't) and **tautological** tests (recompute the expected value the way the code does; expected values come from an independent source: a known-good literal, a worked example, the spec). Prefer integration tests exercising real seams over isolated unit mocks. **Mock only at system boundaries** (external APIs, the DB, the clock, randomness, the filesystem) and never your own code: needing to mock an internal collaborator is a design smell, so inject the dependency instead. Design for testability: accept dependencies rather than construct them, return results rather than fire side effects, keep the seam's surface small. The discipline is absolute: **no implementation code before a failing test exists.**
|
|
11
13
|
|
|
@@ -6,6 +6,8 @@ Prove the change **delivers what the ticket promised**, not merely that steps ra
|
|
|
6
6
|
|
|
7
7
|
Run it in the `build`/`bug` **Verify** step, after the tests / the plan's criterion have run, as the routed subagent on `config.models.verify` (on `route: dispatch` the **`trailhead-verify`** agent, read-only, with the route's `alias` as the model override; a `subagentToolkit == none` host routes inline). Like the other reviews it **never blocks the session**: run it in the background and collect it by polling (see *Reviews never block the session* in `../techniques.md`).
|
|
8
8
|
|
|
9
|
+
**Test record.** When the orchestrator passes a test record (`test record: ...`, the full-suite outcome of Verify), read it and never re-run the full suite (a focused test to probe one finding is fine); with no record passed, test as today (without `testing.full` no test record is passed).
|
|
10
|
+
|
|
9
11
|
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.
|
|
10
12
|
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.
|
|
11
13
|
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`.
|
|
@@ -2,11 +2,11 @@
|
|
|
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`, Grilling → `trailhead-grill` (one round per spawn, the session stays the HITL orchestrator; see **Who runs a round** in `techniques/grilling.md`). 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. **Whether a step spawns, and on which model, is decided by `model-route.js`, never by hand**: see **Route every step through model-route.js** below. The route's `alias` is the `Agent`-tool `model` override at spawn, so config stays authoritative (the `.md` is model-agnostic; the override wins). **An unset key is not `inherit session`:** the helper resolves it to its Balanced default (`configuration-reference.md`), so with nothing configured the planner still spawns on the strong tier even from a standard-tier session; never spawn with defaults. 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. The helper makes the inline-versus-dispatch decision on the full id, 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; `trailhead-grill`: reads and searches only, no Bash) 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`, Grilling → `trailhead-grill` (one round per spawn, the session stays the HITL orchestrator; see **Who runs a round** in `techniques/grilling.md`). 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. Every engine agent is spawned with **fresh context, never a fork** of the orchestrator conversation, on every host: on Claude Code the `trailhead-*` subagent type, never the `fork` type; on Codex the no-fork spawn parameter (`fork_turns="none"` or `fork_context=false`, per the adapter), since a full-history fork inherits the conversation and ignores the model/effort override. 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. **Whether a step spawns, and on which model, is decided by `model-route.js`, never by hand**: see **Route every step through model-route.js** below. The route's `alias` is the `Agent`-tool `model` override at spawn, so config stays authoritative (the `.md` is model-agnostic; the override wins). **An unset key is not `inherit session`:** the helper resolves it to its Balanced default (`configuration-reference.md`), so with nothing configured the planner still spawns on the strong tier even from a standard-tier session; never spawn with defaults. 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. The helper makes the inline-versus-dispatch decision on the full id, 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; `trailhead-grill`: reads and searches only, no Bash) 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
|
**Route every step through model-route.js.** Before dispatching any engine step, run `node <hooks>/lib/model-route.js <key> --session <your full model id> [--session-effort <e>] [--host codex] [--no-subagents] <project root>` and obey its `route`. `<hooks>` is the first of `../../hooks/lib/` and `../trailhead/hooks/lib/` (relative to the `_shared/` dir, as an absolute path) that contains it: the same two-path lookup `load-first.md` uses for `model-defaults-review.js`. Your full model id is in your system prompt or environment ("The exact model ID is ..."); pass `unknown` only if it is truly absent (a trailing `[...]` suffix such as `[1m]` is ignored). Pass `--session-effort` whenever the session's reasoning effort is known, `--host codex` on Codex and `--no-subagents` on a subagent-less host. The key per step: Plan -> `plan`, Execute -> `execute`, Fix -> `execute` (spawn `trailhead-fix` in place of the returned `agent`), Code review -> `review`, Goal-backward verification -> `verify`, Research -> `research`, Debug -> `debug`, Codebase map -> `codebase-map`, Grilling -> `grill` (through **Who runs a round** in `techniques/grilling.md`). The helper prints one JSON line `{"route","agent","model","alias","suggest","warning"}`. `route: inline`: run the protocol in the session; when the helper returns `suggest`, show it as one line, and a `warning` is shown once. `route: dispatch`: spawn the step's agent and use its `agent` and `alias` verbatim: `agent` as the subagent and `alias` as the `Agent` model override on Claude Code (a family, not the exact version), `agent_type=<agent>` on Codex (where `alias` is null). Never substitute another agent or model. If the agent is not registered in this environment, run inline, per the dispatch rule above. Never decide inline versus dispatch by hand, and never read the config yourself to decide. Two cases sit outside the route, and only these two: an accepted `lean` skip (the user chose a lighter inline pass, so there is no subagent step to route), and an autonomous run with more than one slot (`trailhead-work/references/auto.md`: every step dispatches, so on `route: inline` it spawns the agent with no model override, inheriting the session model, which is the resolved one; the helper is still run).
|
|
8
8
|
|
|
9
|
-
**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.
|
|
9
|
+
**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 (up to its bound: a Cross-AI plan review call runs under the `run` helper's deadline, so a stalled reviewer ends as a dropped reviewer, recorded in `PLAN-REVIEW`, never an open wait; Code review and Goal-backward verification are still awaited to their outcome); 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.
|
|
10
10
|
|
|
11
11
|
**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, codebase-map and grill report (a dispatched `trailhead-grill` round), 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. An inline `grill` round writes no report, so it is not checked: only a dispatched `trailhead-grill` report is checked, because the checker guards the handoff from agent to orchestrator and an inline round has none.
|
|
12
12
|
|
|
@@ -27,7 +27,7 @@ The ticket engines call these by name. Each technique's full protocol lives in i
|
|
|
27
27
|
| **TDD** | `techniques/tdd.md` | RED → GREEN → REFACTOR at the seams; no implementation before a failing test |
|
|
28
28
|
| **Codebase map** | `techniques/codebase-map.md` | one-time fan-out of 5 read-only readers, distilled into the repo's `trailhead:codebase` issue |
|
|
29
29
|
| **Debug** | `techniques/debug.md` | scientific method: reproduce → localise → falsifiable hypotheses → confirm cause → verify |
|
|
30
|
-
| **Code review** | `techniques/code-review.md` | review the ticket's own commits (its `Refs: #<n>` set) on
|
|
30
|
+
| **Code review** | `techniques/code-review.md` | review the ticket's own commits (its `Refs: #<n>` set) on 5 axes, adversarially verify each finding before reporting; the brief lists the repo's standards files; a pipeline-touching diff also loads `techniques/code-review-pipeline.md` |
|
|
31
31
|
| **Goal-backward verification** | `techniques/verify.md` | check the change delivers what the ticket promised, working backward from its stated goal |
|
|
32
32
|
| **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 |
|
|
33
33
|
| **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`) |
|
|
@@ -28,7 +28,7 @@ Read **`references/charting.md`** and follow it. It holds both protocols: **Mode
|
|
|
28
28
|
|
|
29
29
|
## Open a ticket on the fly: `ticket <type> <title>`
|
|
30
30
|
|
|
31
|
-
Open ticket(s) of the map on the fly, for any of the six types (`decision`, `research`, `prototype`, `build`, `bug`, `task`), the escape hatch the capture verbs do not cover. **Adding a ticket is a micro-charting act, so diverge briefly first, do not blind-commit to a single piece:** run a short breadth-first pass around the request: is this really *one* session-sized ticket, or a small **cluster** (a `decision` that needs a `research` before it, a UI `build` that needs a `prototype`, obvious siblings)? Does it imply a blocker? Surface the neighbours, *then* create the ticket(s): each gets `trailhead:ticket` plus its `trailhead:<type>`, its map's `trailhead:map-<n>` label and native sub-issue edge, a `Parent:` line, and a `## Question`; put each on the frontier, or, for a known-blocked one, create it already carrying `trailhead:blocked` (in its `--label` list) and wire its `## Blocked by` + native dependency (the three-move blocker wiring, with the label riding at creation, and the child-to-map link are in `../_shared/substrate.md`; the exact gh commands are in `../_shared/substrate-commands.md
|
|
31
|
+
Open ticket(s) of the map on the fly, for any of the six types (`decision`, `research`, `prototype`, `build`, `bug`, `task`), the escape hatch the capture verbs do not cover. **Adding a ticket is a micro-charting act, so diverge briefly first, do not blind-commit to a single piece:** run a short breadth-first pass around the request: is this really *one* session-sized ticket, or a small **cluster** (a `decision` that needs a `research` before it, a UI `build` that needs a `prototype`, obvious siblings)? Does it imply a blocker? Surface the neighbours, *then* create the ticket(s): each gets `trailhead:ticket` plus its `trailhead:<type>`, its map's `trailhead:map-<n>` label and native sub-issue edge, a `Parent:` line, and a `## Question`; put each on the frontier, or, for a known-blocked one, create it already carrying `trailhead:blocked` (in its `--label` list) and wire its `## Blocked by` + native dependency (the three-move blocker wiring, with the label riding at creation, and the child-to-map link are in `../_shared/substrate.md`; the exact gh commands are in `../_shared/substrate-commands.md`; a cluster follows **Cross-references in a bulk create** there). This is a framing brainstorm (is this the right work?), distinct from the `decision` engine's option brainstorm (which choice?). If `<type>` is missing or invalid, ask which of the six. *(The zero-friction captures, `bug` / `todo` / `idea` / `seed` / `note`, deliberately skip this diverge-first pass; they are one action, one confirmation, and are served by the capture cluster.)*
|
|
32
32
|
|
|
33
33
|
## Triage the inbox: `inbox [issue]`
|
|
34
34
|
|
|
@@ -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/`). Before any question, first run the routing step at the top of `grilling.md`, then follow **Who runs a round** there (`models.grill`); this session keeps asking the user
|
|
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 any question, first run the routing step at the top of `grilling.md`, then follow **Who runs a round** there (`models.grill`); this session keeps asking the user, with the options in chat per **The ask is answerable from the chat alone**. 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 resolves a `decision` ticket**, its close follows the decision engine's **Carry it into build** step (`../trailhead-work/references/ticket-engines.md`): the build split goes into the final confirmation, the build tickets are created, and the resolution comment carries the `Builds:` line before `gh issue close`. **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
|
|
|
@@ -9,7 +9,7 @@ The user invokes with a loose idea.
|
|
|
9
9
|
1. **Name the destination.** Run the **Grilling** + **Domain vocabulary** techniques (first run the routing step at the top of `../../_shared/techniques/grilling.md`, then the rounds as **Who runs a round** there says) 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 (first run the routing step at the top of `../../_shared/techniques/grilling.md`, then the rounds as **Who runs a round** there says), **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
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
|
-
4. **Create the tickets you can specify now** (the **Ticket or fog** test below) 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. **Ticket or fog.** Run it on every area from step 2: "specify now" means you can write the area's `## Question` now, sized to one session, not that it can be answered now. An area whose Question you can write is a ticket, created already blocked (`trailhead:blocked` plus its wiring) on the tickets whose answers it needs: being blocked is not a reason to leave it in the fog, because a blocked ticket graduates onto the frontier by itself when its blockers close, while a fog entry waits for someone to dissolve it. An area stays fog only when you **cannot phrase** its Question yet (you don't know what to ask: "Monetization" before the audience is known), or when an open decision could **reshape** the question itself (not just its answer), in which case the fog entry names that decision so it is re-examined when that decision closes. An area too big for one session is several tickets, or one ticket plus fog for the rest. Volume is not a reason to keep an area in the fog: the only brake is GitHub's 100 sub-issue cap per map, and near it, say so and keep the furthest-out areas as fog.
|
|
12
|
+
4. **Create the tickets you can specify now** (the **Ticket or fog** test below) 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**). Every other cross-reference between these tickets is written in this same pass, from the ids just created, never a guessed number (**Cross-references in a bulk create** in `../../_shared/substrate-commands.md`). **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. **Ticket or fog.** Run it on every area from step 2: "specify now" means you can write the area's `## Question` now, sized to one session, not that it can be answered now. An area whose Question you can write is a ticket, created already blocked (`trailhead:blocked` plus its wiring) on the tickets whose answers it needs: being blocked is not a reason to leave it in the fog, because a blocked ticket graduates onto the frontier by itself when its blockers close, while a fog entry waits for someone to dissolve it. An area stays fog only when you **cannot phrase** its Question yet (you don't know what to ask: "Monetization" before the audience is known), or when an open decision could **reshape** the question itself (not just its answer), in which case the fog entry names that decision so it is re-examined when that decision closes. An area too big for one session is several tickets, or one ticket plus fog for the rest. Volume is not a reason to keep an area in the fog: the only brake is GitHub's 100 sub-issue cap per map, and near it, say so and keep the furthest-out areas as fog.
|
|
13
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
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
`/trailhead:inbox [issue]` triages issues that aren't trailhead tickets (bug reports, feature requests, questions) and **integrates the worthwhile ones into the map**, keeping the reporter's authorship.
|
|
4
4
|
|
|
5
|
-
`/trailhead:inbox` presents **
|
|
5
|
+
`/trailhead:inbox` presents **four sections**, each named for what it holds. **Present them to the user by these descriptive names, never as a bare letter or "section A/B/C"** (result-oriented output: no internal labels). The four:
|
|
6
6
|
|
|
7
7
|
**Incoming issues to triage.** Open issues carrying **no** `trailhead:*` label (plus any `trailhead:unverified`, applied by a session under the Trust rule in `../../_shared/teamwork.md`; the guard Action strips labels instead):
|
|
8
8
|
```bash
|
|
@@ -10,6 +10,8 @@ gh issue list --state open --json number,title,author,labels \
|
|
|
10
10
|
| jq '[.[] | select(([.labels[].name] | any(startswith("trailhead:")) | not) or ([.labels[].name] | any(. == "trailhead:unverified")))]'
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
**Waiting on the reporter.** The open `trailhead:needs-info` issues: ones the maintainer asked a question and parked. They are absent from the incoming query (the `trailhead:*` label drops them), and they come back only when the reporter answers. Read them with the read-only helper, never by hand (a per-issue loop is what goes wrong): `node <templates>/trailhead-inbox-needs-info.js --repo <owner/name> --config-root <project root>`, with `<templates>` found by the same three-dir lookup as `trailhead-work-state.js` (see `../../_shared/teamwork.md`, Work state file). It prints one JSON line `{status, days, answered, timedOut, waiting, unknown, warnings}`: `answered` is an issue whose reporter commented after the label (it wins over the timeout), `timedOut` one with no answer after `days` whole days (`inbox.needs_info_days`, default 14, see `../../_shared/configuration-reference.md`), `waiting` the rest, `unknown` one the helper could not classify (a failed read or a reporter with no usable identity), named with its reason and never offered for closing. Surface `warnings` in one line. Show the answered ones, each proposed for triage by the moves below; the timed-out ones, each offered for closing (see **Needs info**); the still-waiting ones as one count line, no list; and any unknown ones by number and reason. If the helper fails (a missing script, a node error, `status: error`), say the waiting section could not be read and go on with the other sections; never guess which are answered or timed out.
|
|
14
|
+
|
|
13
15
|
**Parked fog ready to graduate?** The open `trailhead:fog` issues, **most-recently-active first**: recent discussion is exactly where the fog has likely cleared, so you review those, not all of them:
|
|
14
16
|
```bash
|
|
15
17
|
gh issue list --label trailhead:fog --state open --search "sort:updated-desc"
|
|
@@ -18,12 +20,15 @@ This is **how you notice a fog has cleared**: you don't watch each thread; you r
|
|
|
18
20
|
|
|
19
21
|
**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
22
|
|
|
21
|
-
For each issue in the first
|
|
23
|
+
For each issue in the first three sections (incoming issues, waiting on the reporter, parked fog; for the waiting ones, the answered issues and the timed-out offers), 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
24
|
|
|
23
25
|
- **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
26
|
- **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
27
|
- **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
28
|
- **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
|
-
- **Needs info.** Ask the reporter for a repro or specifics; leave it open.
|
|
29
|
+
- **Needs info.** Ask the reporter for a repro or specifics; leave it open and park it. Ensure the label exists (`gh label create trailhead:needs-info --color FBCA04 --description "Waiting on the reporter: back in the inbox when they answer" 2>/dev/null || true`), post the question **first**, then add `trailhead:needs-info`. The order matters: when the maintainer is also the reporter, a question posted after the label would count as the answer. The label parks the issue (it drops out of the incoming query) and starts the clock.
|
|
30
|
+
- **Resurfacing.** The issue returns, in the **Waiting on the reporter** section, only when the reporter comments after the label. On an answered issue the moves are the same as for a new issue, each **dropping `trailhead:needs-info` first**. **Ask again** posts the new question, then removes and re-adds the label (two calls: adding a label that is already present makes no event, so the clock would not restart and the issue would resurface on every run). A failed removal leaves the old clock, so it resurfaces next run; a failed re-add leaves it in the incoming queue: both stay visible, and both are reported.
|
|
31
|
+
- **Timeout.** After `days` with no answer the issue is only **offered** for closing, as one more item in the same confirmed list as every other move; it is never closed automatically. On a yes: remove the label, **then** `gh issue close <n> --reason "not planned" --comment "<standard message>"`, in that order (a failed close leaves an open issue back in the incoming queue, visible; a failed removal after the close would leave a reopened issue parked on a stale clock, so a reopened issue is back in the incoming query). The standard message, written in `config.ticket.language`, states when the question was asked and how many days passed, and asks for a new issue linking this one (a reporter without write access cannot reopen an issue a maintainer closed, and a comment on a closed issue would never resurface). **Keep waiting** removes and re-adds the label (two calls, as for Ask again: the label is still present on a timed-out issue, and adding a present label makes no event, so the clock would not restart and the offer would return on every run). A failed removal keeps the old clock, so the offer returns next run; a failed re-add leaves the issue in the incoming queue: both stay visible, and both are reported. Any failed step of a move is reported naming the issue and the state it was left in, with no retry on the user's behalf.
|
|
32
|
+
- **Direct path.** `/trailhead:inbox <n>` on a needs-info issue triages it directly, whatever its state (it is absent from the incoming query), and every move drops the label first.
|
|
28
33
|
|
|
29
34
|
A rogue issue wearing a `trailhead:*` label it shouldn't (one whose label came from outside the trusted labellers of the Trust rule in `../../_shared/teamwork.md#trust--provenance`, so a label from the bot, an allowlisted login or a write+ collaborator is not rogue; 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.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Auditing closed work and open overlap
|
|
2
2
|
|
|
3
|
-
`/trailhead:audit [map] [--all]` answers two questions about a repo's tickets: is each closed one **still true against today's code** (it hunts **regressions over time**, not just close-time delivery), and are any open ones duplicates or small enough to land together? Unchanged code is checked cheaply and deterministically, with no agent; the full agent verification is kept for the tickets whose code actually moved. The first run (no marker) covers the whole closed history; later runs keep the marker window. It is **read-only until you confirm**: the verification and the comparison only read, the result is one consolidated report, and every change that follows is a separate proposal applied only on an explicit yes. Nothing is written to GitHub before that.
|
|
3
|
+
`/trailhead:audit [map] [--all]` answers two questions about a repo's tickets: is each closed one **still true against today's code** (it hunts **regressions over time**, not just close-time delivery), and are any open ones duplicates or small enough to land together? A whole-repo run also flags codebase facets whose cited files changed after their stamp (step 7b). Unchanged code is checked cheaply and deterministically, with no agent; the full agent verification is kept for the tickets whose code actually moved. The first run (no marker) covers the whole closed history; later runs keep the marker window. It is **read-only until you confirm**: the verification and the comparison only read, the result is one consolidated report, and every change that follows is a separate proposal applied only on an explicit yes. Nothing is written to GitHub before that.
|
|
4
4
|
|
|
5
5
|
All `gh` mechanics are in `../../_shared/substrate-commands.md`; this protocol only says how to use them. Only the purely process choices (the >20 cost-guard ask, the re-run offer for unverifiable tickets) are advisory, so offer the delegate option on those per `../../_shared/choices.md`. The reopen, duplicate-close and merge proposals reshape the map, so they are **human-owned**: do not offer the delegate option on them. The marker advance stays an explicit yes too.
|
|
6
6
|
|
|
@@ -100,9 +100,23 @@ Findings:
|
|
|
100
100
|
|
|
101
101
|
Over 60 open tickets in scope, run stage (a) per map plus one cross-map pass over titles and Questions only, and say so.
|
|
102
102
|
|
|
103
|
+
## 7b. Codebase facets
|
|
104
|
+
|
|
105
|
+
A whole-repo run also reports which facets of the `trailhead:codebase` issue are stale. It is repo-scoped, so a `[map]` run skips it, and so does a repo with no `trailhead:codebase` issue. Look the issue up with the fail-closed singleton rule (**Find a singleton issue** in `../../_shared/substrate-commands.md`): more than one open issue means skip the step and name the duplicates in the report.
|
|
106
|
+
|
|
107
|
+
Deterministic, no agent: read the body with `gh issue view <n> --json body -q .body` into a file inside a private `mktemp -d` directory, run `node <templates>/trailhead-audit-walk.js facets --repo <root> --body-file <file>` (same `<templates>` lookup as above), and remove the directory on every path, failure included. The script prints `{head, facets:[{name, sha, status, paths, changed}]}`. For each facet it compares the `_as of <sha>_` stamp with HEAD over the files the facet cites in backticks (those that exist at the stamp), and reports one status:
|
|
108
|
+
|
|
109
|
+
- `current`: none of the cited files changed since the stamp.
|
|
110
|
+
- `stale`: at least one cited file changed, was deleted or renamed since the stamp; `changed` lists them.
|
|
111
|
+
- `unstamped`: the facet carries no stamp yet (it gains one on its next patch).
|
|
112
|
+
- `unresolved`: the stamp is unknown or not an ancestor of HEAD (a shallow clone, a rewritten commit).
|
|
113
|
+
- `no-paths`: no cited file exists at the stamp, so there is nothing to compare.
|
|
114
|
+
|
|
115
|
+
A script failure is one line in the report and never blocks the audit or the marker. The flag is a hint, not a verdict: a facet citing a frequently edited file reads stale after any edit to it. `current` is not proof either: patching one line moves the stamp over the whole facet, so an earlier edit to another file it cites stops being flagged.
|
|
116
|
+
|
|
103
117
|
## 8. Report
|
|
104
118
|
|
|
105
|
-
One consolidated report: counts per class, which must sum to the audited set (each ticket is counted **once by its primary class**, the step 6 precedence, and plain unverifiable only when no unmet claim exists): implemented (by the agent or by the light check, giving the light-check share), **unchanged**, **evolved**, implemented wrong, not implemented, unverifiable, then each evolved ticket by name and number with the evolving ticket(s), then each other non-implemented or unverifiable ticket by name and number with a one-line reason, the tickets **also unverifiable in part** listed separately, **outside the sum**, then the decisions flagged **no build** (step 4), also outside the sum, each by name and number, then the duplicate and groupable findings, and any dirty-file note or shallow-clone note from step 4. Nothing has changed yet.
|
|
119
|
+
One consolidated report: counts per class, which must sum to the audited set (each ticket is counted **once by its primary class**, the step 6 precedence, and plain unverifiable only when no unmet claim exists): implemented (by the agent or by the light check, giving the light-check share), **unchanged**, **evolved**, implemented wrong, not implemented, unverifiable, then each evolved ticket by name and number with the evolving ticket(s), then each other non-implemented or unverifiable ticket by name and number with a one-line reason, the tickets **also unverifiable in part** listed separately, **outside the sum**, then the decisions flagged **no build** (step 4), also outside the sum, each by name and number, then the duplicate and groupable findings, and any dirty-file note or shallow-clone note from step 4. On a whole-repo run, also the **stale facets** of step 7b, each by name with its changed files, and the counts of the other statuses, all outside the class sum. Nothing has changed yet.
|
|
106
120
|
|
|
107
121
|
## 9. Proposals, each on an explicit yes
|
|
108
122
|
|
|
@@ -111,6 +125,7 @@ One proposal per finding, shown one at a time, applied only on an explicit yes.
|
|
|
111
125
|
- **Implemented wrong** → a new `bug` carrying `Regression of: <ticket>`, via the capture cluster's `bug --of <n>` (a bug in already-closed work is always a new ticket; see the capture cluster's `bug` engine in `../../trailhead-capture/`). Before proposing it for ticket <n>, search exhaustively (`is:issue`, open, or closed after <n>'s `closedAt`) for an existing ticket whose body carries `Regression of:` pointing at <n> (match the number or its URL). If one exists, report <n> as already tracked, naming that ticket, instead of proposing a new bug; this prevents duplicates when a rewound marker re-audits tickets.
|
|
112
126
|
- **Not implemented** → `gh issue reopen <n>` plus a comment stating the gap (the premature-close branch of new-bug-versus-reopen).
|
|
113
127
|
- **Evolved** → no proposal: listed in the report only, because a later ticket changed the code on purpose.
|
|
128
|
+
- **Stale facets** → no proposal: listed in the report only (step 7b), because the flag is report-only; the facet is patched at the Resolve of the next ticket that changes it.
|
|
114
129
|
- **No build** → open the missing build tickets, per the decision engine's **Carry it into build** step (`../../trailhead-work/references/ticket-engines.md`): read the decision's resolution, propose the split (titles, one line of scope each, blockers, any existing open ticket that already covers a piece), and on a yes create them create-then-wire on the decision's map (the whiteboard for a whiteboard decision), each with a `Decided in: <name+link>` line, then post a comment on the decision carrying the `Builds:` line. If the human says the decision builds nothing, post `Builds: none (<why>)` with their reason instead. The ticket split is human-owned: ask it as plain yes/no, never with the delegate option.
|
|
115
130
|
- **Duplicate** → first **carry the duplicate's wiring onto the kept ticket**, in all three views (the `## Blocked by` line, the native `blocked_by` edge, the `trailhead:blocked` label): each open dependent of the duplicate is re-pointed to the kept ticket unless already wired there, and the union of blockers is built by adding each open blocker of the duplicate that the kept ticket lacks, both **excluding the pair itself** so the kept ticket is never re-pointed onto itself or made to block on itself. Then close the newer one natively as a duplicate of the kept one, `gh issue close <n> --duplicate-of <k>` (this sets the `DUPLICATE` state reason the window filter reads), plus a comment naming the kept ticket. If the duplicate was one of the kept ticket's blockers, that edge is removed (the native edge auto-reflects the close; also drop it from the `## Blocked by` line) instead of transferred, and the kept ticket's `trailhead:blocked` label is re-evaluated only after the duplicate's blockers have been added to it: drop `trailhead:blocked` if that was its last open blocker. Its native sub-issue edge stays: the eager drop is reserved to superseded and out-of-scope closes, and `/trailhead:prune` reclaims closed edges under cap pressure.
|
|
116
131
|
- **Groupable** → **supersede-and-merge**, the reverse of the split in `../../_shared/teamwork.md`. Open one merged ticket on the same map or whiteboard (create-then-wire). Its blockers are the union of the originals' open blockers **minus the group's own members**, wired in all three views (the `## Blocked by` line, the native `blocked_by` edge, and the `trailhead:blocked` label at creation). Every dependent of an original **outside the group** is re-pointed onto the merged ticket in all three views (body line edited, native edge added to the merged ticket; the original's edge reflects its close on its own). Intra-group edges are dropped, never re-pointed, so the merged ticket never blocks on, or depends on, itself. The merged ticket's body carries a `Merged from: <name+link>, ...` provenance line (mirroring split's `Split from:`). Then close each original as `trailhead:superseded` with the eager edge drop and a closing comment linking the merged ticket.
|
|
@@ -57,6 +57,7 @@ The user invokes with a map (URL or number). A ticket is optional: without one,
|
|
|
57
57
|
- **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`.
|
|
58
58
|
- **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.
|
|
59
59
|
- **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.
|
|
60
|
+
- **Sweep stale needs-you marks** at every work-start: `node <templates>/trailhead-needs-you.js sweep --repo <owner/name>` drops `trailhead:needs-you` from every closed ticket still carrying it (a ticket closed by hand or in the web UI keeps it otherwise). Stay silent when `cleared` is empty; otherwise one result-oriented line naming the tickets cleaned up; an `error` is one line and never blocks the work.
|
|
60
61
|
- **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`.
|
|
61
62
|
- **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`.
|
|
62
63
|
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**, `../_shared/surfaced-scope.md`, 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.
|
|
@@ -79,6 +79,8 @@ Every path above that hands a ticket to the human parks it **at once** with `nod
|
|
|
79
79
|
|
|
80
80
|
A needs-you ticket is a mark for autonomous runs only: it stays on the frontier and the map for the human, and `/trailhead:work` still offers it. The queue skips it, and a re-invocation of `/trailhead:auto` does not reconsider it; it comes back to a run only after the human clears the label (a `work`/`quick` claim clears it, or `gh issue edit <n> --remove-label trailhead:needs-you`). A helper error from `park` is reported in chat and the ticket still joins the skip list for this run; never read it as a success.
|
|
81
81
|
|
|
82
|
+
A closed ticket never keeps the mark: every Resolve runs `trailhead-needs-you.js clear` right after the close, and the run starts with `node <templates>/trailhead-needs-you.js sweep --repo <owner/name>` (before the queue is read), which drops the label from every closed ticket still carrying it, however it was closed. A sweep error is reported in one line and never stops the run.
|
|
83
|
+
|
|
82
84
|
## Per-ticket failure budget (a ticket that will not verify)
|
|
83
85
|
On the green path a ticket resolves and the loop continues. On a **Verify failure**, auto does not hand back at the would-be checkpoint. Instead it **extends past the engine's normal bounded fix-and-re-review rounds**, taking the advisory "which fix" sub-choice as **delegate** (the accept / resolve-anyway decision still stays the human's, so at true exhaustion it does not silently resolve a red ticket). The per-ticket **failure budget** is: a **no-progress rule** (stop iterating once a round makes no measurable progress) **plus a hardcoded ceiling of 3 extra rounds** (tunable only in code; there is no config surface). At budget exhaustion, still red, auto **pauses the stuck ticket keeping its claim** (a `PAUSED` checkpoint) and **skips to the next frontier ticket** (skip-and-continue, not stop-the-run). A single stuck ticket never ends the run; its dependents stay blocked naturally. Skipped / stuck tickets are reported at run end. The accepted cost is stranded dependents and accumulated paused tickets.
|
|
84
86
|
|
|
@@ -14,6 +14,8 @@ The orchestrator passes: the ticket number `<n>`, the PLAN (build) or the confir
|
|
|
14
14
|
|
|
15
15
|
**TDD per the `config.tdd` value passed.** `seams`: test-first at the seams the PLAN names; `on`: test-first throughout; `off`: no test-first requirement. For a fix, where sensible, a test that fails before and passes after.
|
|
16
16
|
|
|
17
|
+
**Test runs.** In Execute and Fix, run only the focused tests (those on the files you touched or that exercise them), never the whole suite: the one full run belongs to Verify. When `testing.targeted` is set, run them through `node <templates>/trailhead-test-run.js run --repo <working-root> --config-root <project root> --kind targeted --files <a,b>`, which queues behind other heavy runs on the machine; otherwise use a scoped invocation of the project's own runner (a file or pattern argument) that demonstrably does not run everything. When no scoped invocation exists, write `focused testing unavailable` in `tests:` and run none. Put the helper's JSON (`source`, `tree`, exit) in the `tests:` line of your report; `status: unconfigured` means run the project's own focused command as usual. The single full run, its record and the cache reuse apply only when `testing.full` is set (see Verify in the engine); the focused-tests rule here holds either way.
|
|
18
|
+
|
|
17
19
|
## New dependencies
|
|
18
20
|
**Vet before installing any new dependency** (slopsquatting barrier): name and typosquat similarity to popular packages, age / first-seen date, downloads (counts), repository metadata present and matching. On a host with a hook bus the `trailhead-install-guard` `PreToolUse` hook blocks a named public-registry install (npm / PyPI / crates.io / Go / RubyGems) until it is vetted.
|
|
19
21
|
- **Clean result, or the orchestrator passed that exact package as user-approved:** install by re-running the exact install command prefixed with `TRAILHEAD_VERIFIED_INSTALL=1`.
|