--- name: triage description: This skill should be used to move intents and user-story files through the wiki's triage state machine. Intents (`draft` → `active` / archived with `promoted_to`, `dismissed`, or `fixed_by`) are triaged first, ordered by sightings; stories move `needs-triage` → `needs-info` / `ready-for-agent` / `ready-for-human` / `wontfix`. Reads and writes frontmatter, recommends transitions, promotes an intent into a story via the elaboration skill, dismisses with a recorded reason, writes `adr-no-{slug}.md` when rejecting an enhancement with design rationale, and in `github`/`gitlab` mode mirrors story state to remote labels. Use when the user says "/triage", "what needs my attention", "triage the intents", "what's in the intent queue", "promote intent X", "dismiss intent X", "triage the new stories", "move story X to ready-for-agent", or similar. --- # Triage — Move Stories Through the State Machine A small state machine over wiki story files. Lifts Matt Pocock's triage pedagogy onto the wiki's `kind: story` artifact. ## Prerequisites - `WIKI_SCOPE: ` declared in the project instructions. If missing, suggest `/wiki`. - At least one intent under `projects//intent/` or one story file under `projects//plan//`. With neither, suggest `/to-stories`. ## Roles Intents carry no triage labels; their state is `status` plus one outcome field ([[adr-intent-kind]]): - `draft` — filed, unread by triage - `active` — scheduled: accepted, story not yet written - `archived` with exactly one of `promoted_to` (story slug), `dismissed` (reason), `fixed_by` (regression test path) - `superseded` with `superseded_by` — merged into another intent, whose `sightings` absorbed the count Stories carry two label roles. Two **category** roles (story has exactly one): - `bug` — something is broken - `enhancement` — new feature or improvement Five **state** roles (story has exactly one, lives in `triage_state` frontmatter): - `needs-triage` — story needs evaluation - `needs-info` — agent waits on user clarification (in a solo-operator context: the agent is blocked on a user decision) - `ready-for-agent` — fully specified, AFK-ready (an agent can pick it up with no human context) - `ready-for-human` — needs human implementation (judgment, external access, hardware, design review) - `wontfix` — will not be actioned State transitions: an unlabeled or `needs-triage` story moves to `needs-info`, `ready-for-agent`, `ready-for-human`, or `wontfix`. `needs-info` returns to `needs-triage` once the user provides the missing input. The user can override at any time — flag transitions that look unusual and confirm before proceeding. ## AI disclaimer (GH/GitLab mode only) When `WIKI_ISSUE_TRACKER` is `github` or `gitlab`, every comment posted to the remote tracker during triage **must** start with: ``` > *This was generated by AI during triage.* ``` In `local` mode, no remote comments are posted; the disclaimer doesn't apply. ## Invocation patterns The user invokes `/triage` and describes what they want in natural language. Examples: - *"Show me anything that needs my attention"* → bucket view, intents first - *"Promote intent X"* / *"Dismiss intent X — it's covered by story Y"* → intent outcome - *"Let's look at story 2 of plan-billing-mvp"* → triage a specific story - *"Move story 3 to ready-for-agent"* → quick state override - *"What's ready for agents to pick up?"* → filtered view ## Pattern A — Show what needs attention Read state from the vault filesystem, never from an index query: the index is disposable and the `search` tool returns ranked candidates, not frontmatter. - Intents: every `projects//intent/intent-*.md`; read `status`, `sightings`, `origin`, `updated`. - Stories: every `projects//plan/*/story-*.md`; read `triage_state`, `category`, `updated`, and the slice tick counts. - `search(query, scope, kind="intent")` (MCP) or `kmd search "" --scope --kind intent` (CLI) finds related candidates by content when a finding needs a twin check — the walk above is still the state source. Present the buckets in this order, each with counts and one line per item: 1. **Intents, `draft`** — ordered by `sightings` descending, then `updated` ascending. Two sightings, or a confirmed falsification, is the promotion threshold; flag those first. 2. **Intents, `active`** — scheduled and waiting for a story; oldest first. 3. **Stale AFK stories** — `triage_state: ready-for-agent`, `status: active`, zero ticked slices, and `updated` more than thirty days before the clock. Accepted work nobody started is the backlog's debt; it is offered three actions, never left in the queue by default: - **Demote** — `triage_state: needs-triage`, a Triage Notes line naming the age; the story re-enters evaluation. - **Dismiss** — `triage_state: wontfix`, `status: archived`, the reason in Triage Notes. A capacity call, not a design rejection: no `adr-no-*` unless the operator wants rationale on record. - **Keep** — the operator affirms it; `updated` moves from the clock and the story leaves the band for another thirty days. 4. **`needs-triage` stories** — never evaluated, oldest first. 5. **`needs-info` stories** — blocked on user input, oldest first. 6. **`ready-for-agent` stories** — count plus a one-line summary each, so the AFK queue is visible. Let the user pick. ## Pattern B — Triage a specific story ### Step 1 — Gather context - Read the full story file (frontmatter + body). - Read the parent `plan/plan-{name}.md` for context. - Read `glossary.md` for vocabulary. - Read any wikilinked specs/ADRs the story references. - Check existing `adr-no-*.md` files in the scope — if a similar enhancement was previously rejected, surface it. (This is the wiki-native equivalent of Matt's `.out-of-scope/` knowledge base.) - If the story is `bug` category, attempt reproduction: trace the relevant code, run tests if needed. ### Step 2 — Recommend Tell the user your category and state recommendation with reasoning. Wait for direction. Format: > "**Recommendation: `category: enhancement`, `triage_state: needs-info`.** > > Reasoning: > - Acceptance criteria are clear (3 scenarios). > - But Slice 2 references a payment-gateway integration that isn't specified anywhere — needs an ADR or a spec stub before this can be AFK. > > Specifically I need from you: > 1. Which payment gateway? (Stripe, Adyen, …) > 2. Are we using their hosted form or building our own? > > Confirm and I'll update the story frontmatter and post triage notes." ### Step 3 — Reproduce (bugs only) For `bug` stories, attempt repro before grilling. Read the reporter's steps, trace the relevant code, run tests or commands. Report what happened: - **Successful repro with code path** — strong signal toward `ready-for-agent` or `ready-for-human` - **Failed repro** — strong `needs-info` signal - **Insufficient detail** — strong `needs-info` signal A confirmed repro makes a much stronger agent brief. ### Step 4 — Grill (if needed) If the story body is too thin for `ready-for-agent`, chain into `/intent` to flesh it out. Don't try to grill within `/triage` — separation of concerns. ### Step 5 — Apply the outcome **`ready-for-agent`:** - Verify scenarios are concrete (Given/When/Then, not free-form). - Verify slices are vertical (each cuts through every layer). - Verify `blocked_by:` is populated correctly (no missing dependencies). - Update frontmatter: `triage_state: ready-for-agent`. - Append an *agent brief* to the body (see template below) — short, durable, sufficient for an AFK agent to pick up cold. - In GH/GitLab mode: create a remote issue with the story body inlined and a wikilink back; apply the `ready-for-agent` label. **`ready-for-human`:** - Same brief structure as `ready-for-agent`. - Add a "Why human" section explaining what makes this non-AFK (judgment, external access, hardware, design decision, manual testing). - Update frontmatter: `triage_state: ready-for-human`. **`needs-info`:** - Update frontmatter: `triage_state: needs-info`. - Post triage notes to the body (see template below). - In GH/GitLab mode: comment on the remote issue with the same notes (with AI disclaimer). **`wontfix-bug`:** - Polite explanation in the body's `## Triage Notes` section. - Update frontmatter: `triage_state: wontfix`, `status: archived`. - In GH/GitLab mode: close the remote issue with the explanation. **`wontfix-enhancement`:** - Write `projects//adr/adr-no-.md` capturing the rejection rationale (see template below). - Update story frontmatter: `triage_state: wontfix`, `status: archived`. - Add wikilink to the new ADR in the story body's `## Triage Notes`. - In GH/GitLab mode: close the remote issue with a link to the ADR. **`needs-triage`:** - Apply the role. Optional comment on partial progress. ### Step 6 — Update parent plan Update the Story Index table in `plan/plan-{name}.md` to reflect the new state column. Don't drift — the parent plan's table is canonical for the at-a-glance view. ### Step 7 — Update `updated:` field Set the story's `updated:` from the clock — `date -u +%Y-%m-%dT%H:%M:%SZ`, quoted — never composed. ### Step 8 — Confirm the resync Harnesses with the posttool hook validate and sync automatically. Check `kmd config`: if the `synced` line did not advance past your edits, the hook is not wired — run `kmd validate`, fix findings, then `kmd sync`. ## Pattern C — Quick state override If the user says "move story X to `ready-for-agent`", trust them. Confirm what you're about to do (frontmatter changes, comment, plan-table update), then act. Skip grilling. If moving to `ready-for-agent` without a grilling session, ask whether the body needs an agent brief appended. ## Pattern D — Triage an intent ### Step 1 — Gather context - Read the intent: the six sections, `origin`, `sightings`, the Falsification path. - Twin check across every status, archived included: `search` with `kind="intent"` on the intent's distinctive terms, then read the hits. A dismissed twin carries its reason in `dismissed` — surface it before recommending anything. - Read any story or spec the intent links; if a story already covers the finding, the outcome is dismiss with that story named. - If the Falsification path is cheap to run, run it and report the result. A confirmed path meets the promotion threshold on its own. ### Step 2 — Recommend One of three outcomes, with reasoning, then wait for direction: - **Promote** — `sightings` ≥ 2, or the falsification confirmed, or the operator calls it. The story gets written now. - **Hold** — worth doing, not yet; `status: active` marks it scheduled. - **Dismiss** — covered elsewhere, not worth the fix, or falsified. The reason is the record. ### Step 3 — Apply **Promote:** - Chain to the elaboration skill (`/to-stories`) with the intent as input: the story's problem, scenarios, and first slices derive from the intent's sections, under the active plan. - Intent frontmatter: `status: archived`, `promoted_to: `, `updated` from the clock. - Parent plan: add the Story Index row. The new story's `triage_state` is whatever the operator chose at promotion — `ready-for-agent` when the brief is complete, `needs-triage` otherwise. **Hold:** - Intent frontmatter: `status: active`, `updated` from the clock. Nothing else moves. **Dismiss:** - Intent frontmatter: `status: archived`, `dismissed: ""`, `updated` from the clock. - No ADR unless the operator wants design rationale on record; then `adr-no-.md` as for a rejected enhancement, linked from `dismissed`. **Merge** (two intents for one finding): - The later one: `status: superseded`, `superseded_by: `. The survivor's `sightings` absorbs the count and its `updated` moves. ### Step 4 — Confirm the resync As for stories: the posttool hook validates and syncs; if `kmd config`'s `synced` line did not advance, run `kmd validate` then `kmd sync`. ## Templates ### Agent brief (append to story body when transitioning to `ready-for-agent` or `ready-for-human`) ```markdown ## Agent Brief **What to build:** **Acceptance criteria** (lifted from scenarios above): - [ ] - [ ] **Relevant code paths:** - `` — **Out of scope:** - **Definition of done:** - All scenarios pass as integration tests - `` slice checkboxes all ticked - No unresolved [[spec-X]] divergence ``` ### Triage notes (append when transitioning to `needs-info`) ```markdown ## Triage Notes **What we've established so far:** - - **What we still need from you:** - - ``` Capture everything resolved during triage under "established so far" so the work isn't lost. Questions must be specific and actionable, not "please provide more info". ### `adr-no-{slug}.md` (when rejecting an enhancement) Use `wiki://template/project/adr` (MCP resource, or `kmd resource `) with frontmatter: ```yaml --- title: "" kind: adr scope: status: active summary: "Why was rejected from ." tags: [out-of-scope, ...] supersedes: superseded_by: sources: - projects//plan//story-N-.md created: "" updated: --- ``` Body: ```markdown # ## Status active ## Context The story `[[story-N-<slug>]]` proposed <enhancement>. Triage rejected this for v1. ## Decision We will not <enhancement> in <plan-name>. ## Rationale - <reason 1> - <reason 2> ## Consequences What stays simple. What's deferred. Conditions under which we'd revisit. ## Reconsider when Specific signals that should re-open this decision (e.g., "if >5 customers ask for it", "if a related story emerges"). ``` ## Resuming a previous session If prior triage notes exist in a story body, read them, check whether the user has answered any outstanding questions, and present an updated picture before continuing. Don't re-ask resolved questions. ## Rules - **Intents before stories.** The intent queue is the loop's entry; a session that triages stories while draft intents wait is working the wrong end. - **An archived intent carries exactly one outcome field** — `promoted_to`, `dismissed`, or `fixed_by`. A dismissal without a reason is not a dismissal. - **Every triaged story must carry exactly one category and one state.** If labels conflict, flag it and ask the user. - **Always show your recommendation with reasoning before transitioning.** Don't move state silently. - **For `wontfix-enhancement` on design grounds, always write `adr-no-{slug}.md`.** Rejection without rationale loses institutional memory. A stale-band dismissal is a capacity call and records its reason in Triage Notes instead. - **Always update the parent plan's Story Index table** when state changes. - **Confirm the resync after frontmatter changes** — the posttool hook syncs automatically; if `kmd config`'s `synced` line did not advance, run `kmd validate` then `kmd sync`. - **In GH/GitLab mode, always lead remote comments with the AI disclaimer.** - **Never grill within `/triage`** — chain to `/intent` if needed. - **Quote prose-bearing frontmatter scalars** to avoid breaking the sync walker.