Skip to content

The SDLC, end to end

steer is an opinionated software development life cycle. Every managed repo inherits the same path from "rough idea" to "validated, shipped, traceable work", and the same set of gates along the way. This page is the map; the other Concepts pages zoom into one region of it.

One invariant holds the whole thing together:

The spine

/spec is durable product truth. The issue tracker is the work/decision layer. The human PR review is the gate. Everything below hangs off that split — neither layer silently overwrites the other, and steer never auto-crosses the review gate.

flowchart LR
    B["0 · Bootstrap<br/>setup → init/adopt/sync"] --> S["1 · Shape<br/>spec · questions · adr · roadmap"]
    S --> P["2 · Plan<br/>issues backlog"]
    P --> W["3 · Build<br/>work (--reviewed)"]
    W --> V["4 · Verify<br/>Definition of Done · drift gates"]
    V --> D["5 · Deliver<br/>merge → deploy · protect"]
    D --> M["6 · Maintain<br/>audit · next · sync · tidy · loop · report"]
    M -.re-enters.-> P

The seven phases

Phase Skills Produces Gate that closes it
0 · Bootstrap /steer:setupinit (greenfield) / adopt (brownfield) / sync (steady-state); doctor for prerequisites /spec spine + bundled scaffold (mise, compose, CI, PR template, policy) + pinned toolchain — (enablement, not a gate)
1 · Shape /steer:spec, questions, adr, roadmap intent.md, contract.md, ADRs, a release timeline /steer:spec approve — blocked while a blocking question gated at intent-approval is unresolved (later-gated questions block their own gate)
2 · Plan /steer:issues A triaged, decomposed backlog of issues Issue-first: every implementation-affecting mutation has an issue before the first change — except a Tiny change, /spec edits, docs, generated output, lockfiles, and /steer:sync
3 · Build /steer:work (and work --reviewed) A branch, the implementation, tests, progress on the issue, a PR Commit autonomy + change-size + high-risk scoping; merge/deploy never implied
4 · Verify Definition of Done + drift gates A reviewed, drift-flagged PR with CI green A human dev approves the PR — "review is productionization"
5 · Deliver merge → deploy; /steer:protect A deployed change; an enforced branch-protection gate Branch protection + (at graduation) the PR flow
6 · Maintain /steer:audit (code/spec), next, sync, tidy, loop, report Findings routed back into the backlog; plugin kept current — (re-enters Plan)

Non-technical owners enter through /steer:build, which folds Bootstrap + Shape into one guided interview and hands a working local app to a dev for review.

The incident fast-path

A production incident is high-risk and time-critical at once — the one case where the phases above genuinely conflict with the clock. /steer:work --hotfix is the only sanctioned speed lever (rule 62-hotfix): it opens only for a real incident on a deployed system, relaxes ceremony and ordering (issue filed after-the-fact on a hotfix/<n> branch, single-reviewer) while keeping every human authority gate, and requires a mandatory post-incident follow-up — backfill the issue, write the spec/ADR, add a /spec/history/ entry — so the phases skipped under fire are restored, not waived.

Change size

The phases above describe the full path. How much of it a given change actually walks is set by its size — rule 80-change-size is authoritative for per-change ceremony, and both Issue-first and the Definition of Done take their thresholds from it. When a change is arguable between two classes, it takes the larger one.

Class Roughly Ceremony it earns
Tiny ≈<20 lines, no behavior change — copy, typo, formatting, comment Open a PR and stop: no issue, no spec, no ADR, no plan. The PR is the evidence anchor.
Small ≈<200 lines, contained behavior change Confirm intent; update contract.md if behavior changed.
Medium A new screen, feature, or capability intent.md first, PO approval, then implement with contract.md. Starts in plan mode (or a posted plan).
Large Crosses areas, touches infra, or a choice costly to reverse — a first-time pattern is not itself Large An ADR in /spec/decisions/ first, agree with the team, ship in small PRs. Starts in plan mode (or a posted plan).
Risky Any high-risk area, regardless of line count High-risk handling — never Tiny.

Tiny is guarded on both sides, which is what keeps it from becoming a loophole: it requires zero behavior change (any behavior difference is Small at minimum, however few the lines), and anything in a high-risk area is Risky at any line count. The two Definition-of-Done items that a Tiny change is excused from are marked (size-gated) in rule 50: tests and the GitHub issue with its steer:state. (Rule 50's tests bullet also covers the regression test a bug fix requires, but that clause never reaches a Tiny change — a bug fix corrects behavior, so it is Small at minimum.) The spec-update item is not size-gated — it is simply moot for a Tiny change, which by definition changes no behavior.

One lifecycle store, two questions

Progress lives in one place. The spec stores only what the tracker cannot say.

The issue is the single record of where a unit of work stands (the canonical set; see Lifecycle for the full state diagram and per-kind paths):

inbox → exploring → ready-for-spec → ready-for-dev → in-progress → validate → done

The spec records two facts about the product that no issue state implies:

draft → approved → live

approved is the owner's sign-off on intent, not a technical guarantee — the build is vetted by a human dev at PR review (the Verify gate). live means released to users, which an accepted close (done) does not imply. See Spec approval for why an approved spec is a vetted target, not a vetted build.

So the two answer different questions, and neither mirrors the other:

Question Read
Is it built? Being worked on? Merged? the issue steer:state
Did the owner approve this scope? the spec Status:
Can users see it? the spec Status: live

A feature whose spec says approved while its issue says done is correct, not drift — the spec is not tracking delivery. Status: moves at exactly two human events, approval and release, so a merge, close, or reopen cannot leave it stale and there is no derived value to reconcile.

This is deliberate. The spec used to carry implemented and validated too, mirroring the issue's validate/done — a derived value stored in a second file and updated by hand, so every merge had to be replayed into the spec and reconcile existed largely to repair what that missed. Those two values were retired; what remains of reconciliation is pointer and question consistency, plus /steer:audit spec, which compares the as-built /spec spine (reverse-engineered from the code by /steer:adopt, standing in for it) against the tracker spec — a genuinely different comparison from syncing two status fields.

The two pairings that are worth a look

Because there is no derived value to keep in step, almost every spec/issue combination is legitimate — including approved + done. Only two pairings still need a human: a live intent whose issue never went terminal, and an approved feature with no tracker ref. When in doubt, the spine is product truth and the issue is the workflow that got there.

Drift gates

Whenever a change crosses one of these classes, it is flagged in the PR description and the flag blocks merge until the reviewer explicitly resolves it (you may not waive your own flag):

intent drift · contract drift · undocumented behavior change · security-sensitive · compliance-impacting · operational (deploy/CI/infra) · local setup or deployment changed · app docs invalidated · architecture/stack drift

Periodic sweeps with /steer:audit catch what slips past the per-PR flag.

The shipped CI scaffold also carries an advisory spec-drift job as a machine backstop for the undocumented behavior change class: pure shell + git (no stack, no Python), it warns — never blocks — when a change touches application behavior (apps/, packages/, src/, …) without updating the owning feature contract.md / intent.md. (A dated spec/history/ entry also clears the job's filter, and spec/HISTORY.md still does so for a repo mid-migration — but updating the owning contract.md / intent.md is the routine path for a behavior change, and an ordinary change writes no history entry at all.) It runs on PRs and on push to main, so it is the only spec-drift signal in solo-trunk mode, which has no PR. The warning prompts you to update the spec or confirm "no behavior change" via the PR template — it does not replace the human-resolved flag.

Solo-trunk has no reviewer, so the scaffold also runs a thin Definition-of-Done floor on push to main: the changed-line coverage gate self-gates on the delivery-mode marker and enforces "cover what you touch" against the previous commit (it skips post-merge pushes in pr-flow, where the PR already gated those lines). A returning session is also nudged to graduate out of solo-trunk once a prod branch, a deploy target, or an infra/ tree appears. All three are local, offline signals — a second contributor joining is equally a reason to graduate, but no hook can see it, so that one is caught on demand by /steer:protect or /steer:audit, never at push time.

What steer never decides for you

steer is advisory in the local session — it proposes, surfaces, and flags, but the hard gates are human. These are never auto-crossed by routing:

  • Creating an issue beyond an explicit capture/implement request
  • Ratifying an ADR (it stays Proposed until a human ratifies)
  • Merge and deploy (pushing a branch and opening the PR are autonomous delivery steps — the merge review is the gate)
  • Writing real secrets or repo settings

See the Authorization model for the full authority table, and Known limitations for where the advisory boundary means a control is not machine-enforced.