Imported from forhas/pure-dev (
plugins/quick-dev/skills/flow-triage/SKILL.md). Install upstream withnpx skills add forhas/pure-dev --skill flow-triage. Copyright stays with the author.
flow-triage — choose the build flow
Recommend which build flow fits a feature: feature-dev (small–medium tasks) or superpowers (medium-plus and large tasks). A read-only scout probes the codebase, a deterministic scorecard turns its findings into a score, and a per-repo ledger of past outcomes breaks ties. The heuristic must never block development — every failure degrades to a usable recommendation.
Input
Arguments: $ARGUMENTS
Parse and remove flags; everything remaining is the feature description (required — if empty, stop and ask):
--auto— no confirmation prompt; the recommendation is final.--advise-only— analysis only: no ledger writes of any kind (no decision line, no orphan sweep). Standalone advisory use ("which flow would this be?") should pass this flag — otherwise the run writes a decision line that can never receive a real outcome.--forced-flow=<flow>— the caller already chose; skip the probe and scoring. Valid values:feature-dev,superpowers. Anything else: stop and report the two valid values.--ledger-root=<path>— directory containing.claude/quick-dev/. Default:git rev-parse --show-toplevel. Callers running inside a worktree MUST pass the primary checkout's root explicitly.--run-id=<id>— ledger join key. Default: kebab-case slug of the description (lowercase, alphanumerics and hyphens, ~40 chars).
Step 1 — Read the ledger
Read <ledger-root>/.claude/quick-dev/ledger.jsonl following the tolerance rules in references/ledger.md: missing file → stateless; malformed lines → skip silently. Then, unless --advise-only, run the orphan sweep defined there (close prior runs' dangling decisions with result: "stopped").
Step 2 — Forced flow shortcut
If --forced-flow was given: skip Steps 3–6. Do run Step 7 item 1 (derive the acceptance criteria) — a forced flow is still a flow, still has a feature description, and needs criteria before the build. Unless --advise-only, ensure the ledger directory exists with its self-ignoring .gitignore (commands in references/ledger.md), then append a decision line with scores: null, flow_recommended: null, confidence: "forced", and flow_chosen set to the forced value. Emit the output contract (Step 7) with FLOW: = forced value, CONFIDENCE: forced, TOTAL: n/a, SCORES: n/a, LEDGER-EVIDENCE: none, DRIFT: none, CRITERIA: and COVERAGE-MAP: populated from the derivation, SCOUT-FINDINGS: (skipped — flow was forced), MICRO-PLAN: (skipped — flow was forced). Done.
Step 3 — Scout probe
Dispatch one read-only Explore agent (search breadth: medium), synchronously. The scout must not write files, commit, or modify anything. Prompt it with the feature description, the current directory as the codebase to probe, and this exact required return format:
AFFECTED: <files/modules expected to change, one per line, each with a short why>
PATTERN: <existing repo pattern that already covers this kind of change, or NONE>
OPEN-QUESTIONS: <questions the description leaves unanswered, one per line, or NONE>
MICRO-PLAN:
<ordered implementation task list, one line per task, with "(depends on N)" notes where a task needs an earlier one, and an "UNKNOWN:" prefix on any task whose approach it could not determine>
If the scout fails, returns nothing at all, or its output lacks the required sections, retry once with the same prompt. If it fails again, degrade: score ambiguity, novelty, and risk from the description alone; set blast_radius, depth, verification_cost, and plan_shape to null; treat the result as gray zone with confidence borderline. Interactive: present what is known and let the user pick the flow. --auto: recommend feature-dev and record the degradation in the output's DRIFT: line as scout unavailable — description-only scoring. In degraded mode the output block (Step 7) uses TOTAL: n/a (scout degraded); per-dimension null entries for blast_radius, depth, verification_cost, and plan_shape, each justified as scout unavailable; SCOUT-FINDINGS: (unavailable — scout degraded); and MICRO-PLAN: (unavailable — scout degraded).
A zero-byte result is its own failure shape — not a malformed one. An agent that signals idle with no payload has not returned a report lacking required sections; it has returned nothing, and a check written against output that is missing or malformed does not cover an empty result at all. Handle it explicitly: send one follow-up message restating the required output format and saying plainly that the reply body is the deliverable — nothing else the agent produced reaches the caller. Measured in a client: six review seats in one run each returned zero bytes, and that single nudge recovered all six on the first attempt.
The nudge is not a remedy, and must never be treated as one. On the same host and the same plugin version eight hours later, the identical nudge was applied to two agents and recovered neither — each returned zero bytes twice. The two conditions are indistinguishable at the moment of failure: both present as a contentless idle, and only the response to the nudge tells them apart. So nudge once, then treat a still-empty result as the failure it is. Never read "the agent went idle" as "the agent finished successfully" — that silently converts a seat that never ran into a clean verdict, which is the one outcome an independent-review seat exists to make impossible.
Distinguish "the scout failed and nothing else is known" from "the scout failed but the caller already holds equivalent findings." The degradation above assumes the first, and applying it to the second degrades the recommendation rather than the evidence. On tickets whose gate involves empirical probing, the caller has often already established the affected files and the relevant precedent by direct reads before dispatching the scout at all — and nulling those dimensions then pushes a well-evidenced ticket into the gray zone on a technicality. So: when the caller holds findings that answer a dimension directly, score that dimension from them, cite the direct finding in its justification exactly as a scout finding would be cited, and record the substitution on the DRIFT: line as scout unavailable — scored from caller's direct findings. Null only the dimensions nothing answers. Measured once in a client, where description-only scoring would have understated a 15/24 ticket. This is not licence to invent evidence: a dimension with no direct finding behind it is still null, and a run with no prior findings at all still takes the full degradation above.
Say what the output block then carries — the degraded-mode rule above is unconditional, and left alone it discards everything this substitution just recovered. That rule emits TOTAL: n/a (scout degraded), borderline, and --auto → feature-dev whatever was scored, so the 15/24 case it was written for still lands on feature-dev. Two states, and only the first is a normal scoring run:
- All seven dimensions answered — nothing is
null, so there is nothing degraded about the score. ComputeTOTALnormally and route it through the ordinary thresholds and confidence rules, exactly as a delivered scout would have.--autorecommends whatever that total selects; it does not fall back tofeature-dev. TheDRIFT:line still records the substitution — the evidence came from the caller rather than the scout, and that is worth disclosing even though the score is complete. - Some answered, some still
null— the score is genuinely incomplete, so the degradation above stands: gray zone, confidenceborderline,TOTAL: n/a (scout degraded — <k>/7 scored from caller findings), and--autorecommendsfeature-dev. Each answered dimension still carries its real value and its cited justification rather thanscout unavailable, so a reader sees what was established; the recommendation is what stays conservative, because one missing dimension can move a total across a threshold.
The distinction is which of the two the run is actually in — never how much evidence it feels like it has.
Step 4 — Score
Score the scout's findings on the seven dimensions in references/scorecard.md, each with a one-line justification citing something concrete the scout found. Compute total = blast_radius + depth + ambiguity + novelty + risk + verification_cost + 2 × plan_shape.
Step 5 — Recommend
Apply the thresholds in references/scorecard.md: ≤9 → feature-dev, confidence clear; ≥14 → superpowers, confidence clear; 10–13 → gray zone, confidence borderline — apply the scorecard's gray-zone rule against the ledger entries read in Step 1 and note which run_ids influenced the lean.
Step 6 — Drift check
Run the scorecard's report-only drift check over the last 5 completed runs. Any warning goes in the DRIFT: output line. Never adjust thresholds or weights.
Step 7 — Confirm, record, output
-
Derive the acceptance criteria. From the feature description alone — not from the scout findings or the micro-plan, which describe what we intend rather than what was asked for — state 3-6 observable criteria: conditions a reader could check against the finished work without trusting the run's own account of it.
Then build the coverage map in the other direction: enumerate every sentence of the feature description and map each one to the criterion covering it. A sentence left unmapped gets an entry saying
not coveredand why. (A sentence cannot be summarised away; a reader can verify the count.)The map is what catches weak criteria. Weakness never shows up as a bad-looking criterion — it shows up as part of the request that no criterion mentions, and only directional coverage makes that visible. The 3-6 cap is binding: a rambling description is explained in the map, never inflated into a dozen criteria.
This runs before any code exists, which is what stops the criteria being reverse-engineered from what was built. In interactive mode the user sees them in the confirmation below — they are the authority on what they asked for, and this is the only point in the flow where they can say so before there is code to defend.
-
Confirm (skip with
--auto): present the full output block below, then ask the user to confirm the recommendation or override to the other flow (AskUserQuestion; onborderlineconfidence, say explicitly that their judgment matters most in the gray zone).flow_chosenis whatever they pick; with--autoit equals the recommendation. -
Record (skip with
--advise-only): ensure the ledger directory exists with its self-ignoring.gitignore(commands inreferences/ledger.md), then append the decision line per the schema there. -
Output — end with exactly this block so callers can parse it:
FLOW: <feature-dev|superpowers>
CONFIDENCE: <clear|borderline|forced>
TOTAL: <n>/24
SCORES:
- blast_radius: <n> — <justification>
- depth: <n> — <justification>
- ambiguity: <n> — <justification>
- novelty: <n> — <justification>
- risk: <n> — <justification>
- verification_cost: <n> — <justification>
- plan_shape: <n> (weight 2) — <justification>
LEDGER-EVIDENCE: <none | which run_ids influenced the decision and how>
DRIFT: <none | warning text>
CRITERIA:
- <observable criterion 1>
- <observable criterion 2>
COVERAGE-MAP:
- "<sentence from feature description>" -> criterion <n>
- "<sentence from feature description>" -> criterion <n>
- "<sentence from feature description>" -> not covered — <why>
SCOUT-FINDINGS:
<the scout's AFFECTED / PATTERN / OPEN-QUESTIONS sections verbatim, or "(skipped — flow was forced)" / "(unavailable — scout degraded)">
MICRO-PLAN:
<the scout's micro-plan sketch verbatim>
FLOW: reflects flow_chosen (post-confirmation), not the raw recommendation. Callers hand the MICRO-PLAN: block to the chosen flow as seed context. CRITERIA: is the run's frozen definition of done — callers write it to a criteria file before the build and never regenerate it afterwards. On --advise-only both blocks are still emitted; on a forced flow they are still emitted, since neither depends on the scout.