Skip to content
Development
Command

/v-triage

Classify one change request before any work starts — resolve, score, and write plus COMMIT the pre-eval triage record, then print the tier (DIRECT | SCOPED | FULL) with the predicates that decided it. DIRECT means the human implements in place, runs the test floor, and commits

From plugin
superpowers-v
3615 skills7 agents15 commands7 hooks
Install
> /plugin marketplace add procoders/superpowers-v
> /plugin install superpowers-v@procoders

How it fires

How this command gets triggered: by you, by Claude, or both.

  • Fires itselfClaude auto-loads it when your prompt matches the work.
  • You can call itInvoke it directly when you want it.
  • Slash command/v-triage

Context preview

What this command does when you run it.

Classify one change request before any work starts — resolve, score, and write plus COMMIT the pre-eval triage record, then print the tier (DIRECT | SCOPED | FULL) with the predicates that decided it. DIRECT means the human implements in place, runs the test floor, and commits

Command definition

v-triage.md
description: Classify one change request before any work starts — resolve, score, and write plus COMMIT the pre-eval triage record, then print the tier (DIRECT | SCOPED | FULL) with the predicates that decided it. DIRECT means the human implements in place, runs the test floor, and commits the change together with its record.

You are running **`/v:triage`** — the **entry point** to Compound V's sizing engine.

`scripts/compound-v-preeval.py` and its five siblings are ~10,400 lines of deterministic scoring that, until this command existed, **nothing ever called**: `docs/superpowers/pre-eval/` had never appeared in this repository's git history and no run had ever been bound to a record. Every other v3.0 mechanism — the validator's `triage` block, the Stop-hook coverage gate, the outcome stream, the circuit breaker — consumes a record that only Phase T produces.

The argument is `{{args}}`.

Resolving the plugin root

The `scripts/` this command calls ship with the plugin — they are not files in your own repository. Resolve the plugin root once per session before calling any of them:

CV="${CLAUDE_PLUGIN_ROOT:-$(ls -d "$HOME"/.claude/plugins/cache/*/superpowers-v/*/ 2>/dev/null | sort -V | tail -1)}"
CV="${CV:-$PWD}"; CV="${CV%/}"

`CLAUDE_PLUGIN_ROOT` is set for hooks but is not set in this Bash environment, so treat it as a hint, never the whole answer — the fallback line covers an installed plugin cache or a checkout of this repo.

You are not the only producer any more (v3.4)

`hooks/triage-prompt-nudge.sh` fires on `UserPromptSubmit` and runs **the same subcommand Phase T runs below**, so a change request that arrives as an ordinary prompt is usually already sized and already has a record — written, bound to the session, and **uncommitted**, because a hook must not run git. Before scoring, check whether that record exists and covers this work:

ls -t docs/superpowers/pre-eval/*.json 2>/dev/null | head -5

If one carries this session's id, **do not mint a second**: commit it (step T3) and go. A record per prompt is how the outcome stream the circuit breaker reads stops meaning anything. Run Phase T when the hook did not fire — a slash-command invocation, a second request in the same session, or any session the hook was not registered in.

As of **v3.4.1 a request needing T3 is no longer one of those cases**: the hook finishes the classify itself with the headless one-shot (see T2) and records a real tier. It falls back to its reminder only when no classifier could be RUN at all — no `claude`/`codex` CLI on the machine, or one that hung past the cap — and that reminder is the signal to run Phase T here.

One phase

`/v:triage <request>` is **Phase T** — resolve localization, classify, score, write **and commit** the record, then print the tier and predicates 1-6. Run it before any work starts, when no record already covers the request.

A DIRECT change is an ordinary commit

DIRECT means "implement in place, run the floor, commit on the branch". That commit is an **ordinary `git commit`**: make the edit, run the test floor, commit it together with the triage record, and let review and the human be the enforcement they already are. There is no separate landing gate — the triage record and the Stop-hook coverage gate are what bind the change to its decision.

SCOPED and FULL are unchanged: they still require a human offer and acceptance, then `/v:orchestrate` and `/v:dispatch`.

The vocabulary rule

**Never re-spell a decision string or a tier token.** `DECISION_FASTPATH` / `DECISION_SCOPED` / `DECISION_FULL` and the `DECISION_TO_TIER` map live in `scripts/compound-v-preeval.py`; Phase T reads them by calling the engine's own `triage` subcommand. A duplicated wire vocabulary is how the two halves of this release drift apart, and the one ratified exception (a sibling analyser consuming the value as JSON off a record, with a selftest asserting equality) does not apply here.

The same rule governs the `sensitive` set: it comes from the taxonomy via `compound-v-taxonomy.match_auto_route()`, **never** from a list written here. `MANDATORY_SENSITIVE` in that module is the code-level floor that keeps a taxonomy which forgets the two policy files from re-opening the self-widening hole; this command adds nothing to it.

---

The six auto-route predicates (spec §A4), and where each is evaluated

| # | Predicate | Evaluated | |---|---|---| | 1 | Tier is `DIRECT` and **no override fired** | Phase T, from the record's `decision` + `override_fired` | | 2 | Exactly one resolved path, and it is a **literal** | Phase T, via the engine's own `_is_single_literal_path` | | 3 | Taxonomy present and **digest-matched**; never a fail-closed `unknown` band | Phase T, against the record's pinned snapshot | | 4 | Path matches the taxonomy's `auto_route_allow` | Phase T, via `match_auto_route` | | 5 | Path matches **no** entry in the `sensitive` set | Phase T, via `match_auto_route` (taxonomy + mandatory floor) | | 6 | **No test file touched** | Phase T, on the resolved path |

Predicates 1-6 decide *membership*, and Phase T reports them for every record — a DIRECT change gets the six as evidence, and then commits ordinarily.

---

Phase T — decide

T1. Preconditions

git rev-parse --show-toplevel
printf '%s\n' "${CLAUDE_CODE_SESSION_ID:-}"

Read what that prints before running T2 — it is what the record binds, and its consequences are under "On the session id" below.

T2. Score, bind, write the record — one call

The whole of Phase T is a subcommand of the engine. Put the request text in `V_TRIAGE_REQUEST` (never on argv: a request is arbitrary text, it has to survive shell quoting, and argv is visible to every process on the machine) and run this from the repo root:

V_TRIAGE_REQUEST='<the request text>' python3 "$CV/scripts/compound-v-preeval.py" triage \
  --request-env V_TRIAGE_REQUEST --repo . \
  --session-id "${CLA
Read more
Ships withsuperpowers-v

Compound V — a multi-model coding sidekick for Superpowers, running on Claude Code. You describe a feature. Claude sizes the request, plans it, splits it into non-overlapping pieces, and hands each piece to a worker in its own isolated worktree.

Get the whole plugin

Other commands on superpowers-v.