speckit-size
Route a piece of work to the amount of process it actually needs — trivial, one-session, feature, or project — and record the choice so the gates and /speckit-auto read it instead of assuming. Run it
SKILL.md.claude/skills/speckit-size/SKILL.md
GitHubUser Input
$ARGUMENTS
Phase 1 — classify, cheaply
node .claude/scripts/level.mjs suggest "<the work, one sentence>" --set
It sizes only the text it is given and reads no tracker: a bare ST-<n> or
issue URL is sized as those words, which is almost always unsure. For a
story, pass its title and the gist of its Build brief (gh issue view <n> -R george-hutanu/motor-fix-specs); boards, a missing Build brief section or a
large story are reasons to answer 2 yourself when the classifier is unsure.
A local classifier answers first and costs nothing. It decides only the clear
cases: an edit to text or to a name only the code reads is 0, anything touching
a contract, data, identity, money or a new surface is 2, and an epic is 3. A
risky word always outranks a trivial one, and a word that adds behaviour makes
it unsure, never 0. Jev answers next if it has a key. Then:
- It printed a level and
recorded for …: done, go to Phase 3. Do not re-argue a confident answer, except a 0 the description plainly contradicts (it adds or changes behaviour): thensetthe level it deserves. - It printed
unsure, orlow confidence: answer the one question below in one or two sentences, thennode .claude/scripts/level.mjs set <0-3>.
Is the intent already well defined? It says what is true when done, what must not change and what is out of scope, completely enough that someone else could build it without guessing.
If you cannot say what "done" means without inventing a requirement, it is at
least 2. Size the intent, not the diff: a one-line change to what --json
emits is a contract change; a 400-line mechanical rename is not. Level 3 only
when more than one feature or epic must not diverge.
| Level | Name | When | Owes | Phases |
|---|---|---|---|---|
| 0 | trivial | obvious, low-risk, reversible | nothing | edit, verify |
| 1 | one-session | one coherent unit, intent already clear | spec.md, tasks.md |
specify → tests → implement → review |
| 2 | feature | the default: intent needs settling, design has choices | spec.md, plan.md, tasks.md |
the full chain |
| 3 | project | several features that must not diverge | level 2, plus a shared brief | the full chain, per feature |
When unsure between two levels, take the higher one. A spare plan.md costs a few minutes; a missing one costs a rewrite.
A level 0 or 1 is checked after phases 2, 4 and 7 by level.mjs check, which
raises it to 2 on any of four wires: fr-count (more than 10 FRs),
clarification (a [NEEDS CLARIFICATION] marker left), contract (a contract, schema or migration
file in the diff) and projects (more than one Nx project in the diff). A bug
is exempt from projects: record it from the issue's type: bug label with
node .claude/scripts/level.mjs set <n> --type bug.
At level 3, or wherever it fires: a story too wide is split: more than two Nx projects (web-e2e and the
generated data-access not counted), or more than one of worker, api and
web. At sizing the evidence is the surfaces the story names; from then on
level.mjs check reads the diff and the apps/, libs/ paths in tasks.md and prints a split: <projects> line until spec.md
carries a ## Split section with two titles, the first mergeable alone. The
second is filed with node .claude/scripts/tracker-sync.mjs file --type story --title "<second>" --body-file <f>, its body carrying
Depends on: ST-<first>; tracker-sync adds the native blocked-by link.
Phase 2 — where it lands
set binds the level to the work it was sized for (level_for in
.specify/feature.json). On the feature's own branch that is the current
feature. Anywhere else it is next: it applies to no feature that exists, and
/speckit-specify hands it to the directory it creates (level.mjs point),
once, if that happens within 30 minutes. After that, or for a feature HEAD
already holds, the default applies and point says so: size again with
--current. Level 0 is never carried, since a trivial change creates no
feature. A level sized for another feature is ignored, so an old "trivial"
never shrinks new work. Force the target with --current or --next; for one
command only, use SPECKIT_FEATURE_LEVEL=1 <command>.
node .claude/scripts/level.mjs reads it back with its source and what waits.
Phase 3 — say what it changed
One line: the level, and the phases it skips. For example: "level 1: no
plan.md, research.md or contracts/; /speckit-tests still runs first."
What a level never does
It chooses how much planning a change carries, never whether it is tested.
pre:edit:red-first reads the spec and tasks, not the level; spec-drift still
wants specs/ beside a feat/fix/perf commit to apps/*/src or
libs/*/src; trace-matrix.mjs --check still fails on untested requirements.
If a level could turn those off, 0 would be the way out of every gate.
artifact-lint is the only check that reads it, and only to stop asking for a
plan at levels 0 and 1.