Decision-free refactor specs
Produce specs/<target>/spec.md + plan.md pairs that make implementation
mechanical for the receiving coding model. The receiver's identity, family, and
parameter count are not part of the contract. Resolve as many decisions as
possible up front — architecture, interfaces, file placement, sequencing,
recovery, and verification — so even suitable small coding models can implement
the work reliably. The expensive part of planning — knowing exactly what is in
each file — is a script, not a reading job. The spec author spends effort only on
judgment: pattern choice, interface tables, and trap-spotting.
The workflow below is language-agnostic. Everything language-specific (inventory tooling, extraction procedures, recovery rules, traps) is a plug-in:
| Language | Status | Tooling |
|---|---|---|
| TypeScript / TSX | available | languages/typescript/ |
| Python | available | languages/python/ |
| Rust | available | languages/rust/ |
| Flutter / Dart | available | languages/dart/ |
| Go | available | languages/go/ |
Before planning, read languages/<lang>/NOTES.md for the target language. The
Python/Rust/Dart tools are fixture-validated (plus a CPython stdlib file for
Python). The Go stdlib-AST tool is CLI/golden validated and checked against real
Go compiler sources. On first production use in a new codebase, hand-check one
real file against the inventory output. For a language with no plug-in, build the inventory
tool first (contract and procedure in languages/README.md) — do not fall back to
reading files manually; that recreates the cost this skill exists to remove.
Step 0 — program bootstrap (first use in a repo only)
If the repo has no specs/README.md + specs/conventions.md, create them from
templates/program-readme-example.md and templates/conventions-example.md,
adapting: the verification commands for THIS repo, the import/module-path table for
THIS language (from the language NOTES), and the executor prompt template's repo
root. The program README owns the executor contract (prompt template, sampling
settings, one-step-per-session rule, failure protocol) — per-spec folders never
restate it.
Step 1 — inventory by script (do NOT read the files first)
Run the target language's inventory tool from the repo root; it emits the contract
sections (languages/README.md): imports, the exhaustive top-level declarations
table (kind/name/lines/exported), language-specific signals (e.g. hooks, API call
sites, dynamic string-built identifiers), and — critically — which other files
import the module and what names they import (load-bearing exports needing
compatibility re-exports; this is how a test file importing three helpers was
caught in the first production run).
Only read source directly for the specific blocks you intend to extract into new units (to write accurate interface tables) — never for symbol discovery.
Step 2 — decide the architecture (this is YOUR job, record every decision)
Pattern menu, in order of preference:
- Model extraction (always): pure types/constants/helpers → a model module.
- Leaf unit moves: nested functions/components/widgets → own files. Verbatim.
- Block extraction (UI panels, large match arms): only with a full interface table (props/params); the language's compiler-driven recovery rule covers misses mechanically.
- Strategy registry: ONLY for ≥2-way behavioral variance (different fields, different validation, different request shapes). Labels and styling variants are NOT behavioral variance.
- Shared extraction: if the inventory shows the same symbol duplicated across files, dedupe it in the FIRST plan that runs and mark the dependency in the program table.
Record negative decisions explicitly in spec.md ("No Strategy registry — X is the only variance; do not create one", "the drawers stay inline by decision"). An unrecorded non-decision is a decision the executor will make badly.
Step 3 — write the documents
Per target folder:
- spec.md: source file + size, pattern + why, target file tree, exhaustive symbol map table (symbol → destination → exported), interface tables for any block extraction, dependency notes (exact import/module paths per the language NOTES), acceptance criteria. If the inventory's "Imported by" section is non-empty for moved exports, specify a compatibility re-export and name the importer.
- plan.md: prerequisites (cross-plan dependencies by name + step), numbered
steps each small enough for one fresh-context session, exact commit message per
step. Steps only state what differs from the repo's
specs/conventions.md.
Hard rules learned in production:
- Order steps: pure moves first, unit moves second, block extraction last — every step independently committable, repo green between steps.
- Title every step
STEP k of Nand state N in the plan's preamble. In the first production run the executor stopped after step 1 of 3 on the hardest target and reported the whole plan complete; the step header is what makes "there are more steps" visible inside a fresh-context session. - End every move-step with a leftover check: the exact count of symbols moved, plus a mechanical assertion that none of their declarations remain in the source file (the language NOTES give the grep shape). A 27-symbol move list silently lost one symbol in production; the compiler can't catch a type that still compiles where it was left.
- Anchor on symbol names, never line numbers (they drift).
- Every "if X then ask/choose" in a draft plan is a bug. Replace it with either a decision made now, or a mechanical rule ("copy version A and report the difference — do not reconcile").
- Verification gates per step come from the repo's conventions (format → typecheck/build → any co-located tests). If the target has a co-located test file, name it in the plan — its imports are exactly the compatibility surface.
Step 4 — update the program and commit
- Add rows to the program README's table (size, difficulty, dependencies).
- Spot-check load-bearing claims the specs rely on (duplicated symbols, importer lists) — the script reports them, but confirm anything a plan's correctness hinges on.
- Commit per the repo's conventions.
Step 5 — audit the executor's output (do not trust "all done")
The exact commit messages in the plans double as a completion manifest. When the executor (or its operator) reports the program finished, diff expectation against reality before believing it:
grep -A2 "Commit message" specs/*/plan.md # expected messages
git log --format=%s <baseline>..HEAD # actual messages
Every expected message must appear, one commit each. Production failure modes this catches: steps silently skipped (the executor stopped early on the hardest target), and two steps squashed into one commit (the work existed but the per-step verification gates never ran for the squashed step — re-verify those by hand). Then run the full verification suite once at program level, including test files near refactored code that the per-step gates didn't name.
Substrate semantic completion
When this skill runs as a Substrate runner or workflow job, any skill- or
workflow-specific status block comes first. The final non-empty assistant line
must be exactly one SUBSTRATE_OUTCOME_V1= declaration.
Use outcome: "succeeded" only when the requested spec/plan is decision-free,
the expected files exist, all required edits are committed (or the current tree
was already proven to satisfy the request), and the stated verification passed.
Use outcome: "failed" for failed, blocked, or needs-human work. Report honest
Git evidence with changes.status: "committed" plus commit IDs, or
"alreadySatisfied"; report the commands that actually passed. Never infer
semantic success from a normal process exit.
SUBSTRATE_OUTCOME_V1={"version":1,"outcome":"succeeded","summary":"Authored and verified the decision-free spec program","evidence":{"changes":{"status":"committed","commitIds":["<commit>"]},"verification":{"status":"passed","commands":["<verification command>"]}}}
Emit no second declaration and nothing after it. Outside a Substrate job, do not add this platform-specific line.
Executor contract (defaults; the program README is authoritative per repo)
Model identity and size are deliberately unspecified. Use any coding model suitable for the target language, repository tools, and one step's working set. The spec and plan must not depend on a particular model's branding, parameter count, context window, or advertised strengths. If sampling controls are available, prefer deterministic settings; temperature 0.2 and top_p 0.9 are reasonable starting defaults. Use one fresh-context session per step. Two failures on a step → reset the working tree, report FAILED with the error, stop. Never improvise around a failing step.