Configuration & rules¶
The always-on rules under plugins/steer/rules/ are the plugin's operating
manual. They are injected into every managed session by inject-standards.sh
(Hooks) and concatenate in lexical order by numeric prefix.
Numbering has intentional gaps
Prefixes are spaced (e.g. 20 → 22 → 30) so new rules can slot between
existing ones. Gaps are headroom — files are never renumbered to make the
sequence contiguous.
The ruleset¶
| Rule | Topic |
|---|---|
00-router.md |
Operating-manual entry point. |
05-roles.md |
Who you are working with. |
10-stack.md |
Stack defaults (app / service profile). |
12-stack-infra.md |
Stack — infrastructure / IaC (injected when the repo does IaC). |
15-commands.md |
Useful commands. |
20-layout.md |
Where things live. |
22-housekeeping.md |
Keep the repo tidy. |
24-worktrees.md |
Parallel worktrees — isolate runtime, clean up after. |
26-context-hygiene.md |
Context hygiene — delegate heavy runs, keep state in files. |
30-spec-workflow.md |
Spec workflow. |
31-decision-capture.md |
Durable decisions land in the spine, not in side-channels. |
32-living-docs.md |
Document in parallel, not after. |
35-issue-tracker.md |
Issue-tracker integration (client-agnostic). |
36-issue-first.md |
Issue-first (GitHub-adopted repos). |
40-testing.md |
Testing rules. |
41-coverage.md |
Coverage as a signal — cover what you touch; no vanity threshold. |
45-commit-autonomy.md |
Commit autonomy (see Authorization model). |
50-definition-of-done.md |
Definition of Done. |
51-verify-loop.md |
Verify loop — turn a task into a verifiable end state, iterate against the harness until green with a bounded loop, stop-and-report when blocked, never loop on uncheckable/long-compute work. |
52-deployment.md |
Deployment & environments — branch-driven promotion, review apps, observability baseline, rollback (see Deployment & environments). |
53-autonomous-loops.md |
Autonomous loops — automate the navigation, never the authority; a loop may discover, triage, draft, push its own branch, and open a draft PR, but stops at every human gate (merge, deploy, ADR ratification, secrets). |
55-drift-gates.md |
Surface drift before merge. |
60-high-risk.md |
High-risk areas. |
61-gate-prompts.md |
Answering a human gate in-session — a gate needs the deciding human's answer, not a particular channel, so where that human is present it is collected by an Approve · Reject · Decide later prompt and recorded with its ratifier, date, and channel. Covers ADR Proposed → Accepted, intent draft → approved, and --reviewed plan sign-off; merge, deploy, real secrets, /infra, and protected-branch pushes are never promptable. Full protocol in the gates reference. |
62-hotfix.md |
Hotfix / incident fast-path — the one sanctioned speed lever for a production incident (/steer:work --hotfix); relaxes ceremony, keeps every human authority gate, requires a mandatory post-incident follow-up. |
70-secrets.md |
Secrets handling. |
75-compliance.md |
Audit-aligned delivery (SOC 2 / ISO 27001). |
80-change-size.md |
Change-size model — authoritative for per-change ceremony; Issue-first and Definition of Done take their thresholds from it. Tiny (≈<20 lines, no behavior change) needs no issue, spec, ADR, or plan; any behavior change is Small at minimum; a high-risk area is Risky at any line count; an arguable class takes the larger one. |
85-practices.md |
Baseline patterns — typed by default, schema-validated boundaries (incl. JSON/YAML config & data files), parameterized data access, server-first, nothing silenced, every import resolves to a declared dependency, ASCII in code and values. |
87-output-discipline.md |
Earn every line — tight responses, comments the exception, least code that does the job, lean durable prose. |
88-artifacts.md |
Shareable views → Claude Artifacts — a derived, temp-only, on-demand page with a Markdown fallback; styled to the product's DESIGN.md tokens (house default otherwise); fillable pages return data only via their exported, machine-keyed document. Full discipline in the artifacts reference. |
90-design-sources.md |
Design sources & UI. |
92-user-facing-copy.md |
Internal ids stay out of end-user surfaces — ADR ids, tracker refs, Q-NNN ids, feature slugs and spec/** paths never reach app UI copy or /spec/app/ guide copy and release notes; the /spec/app/ runbook is dev-facing and keeps its refs, and the guide's spec/glossary.md cross-link is a link, not copy. Third-register prose in the traceability reference. |
95-not-the-gate.md |
You are not the gate — the dev is. |
97-self-report.md |
When steer itself misbehaves, file it upstream with /steer:report, which auto-files after scrubbing and deduping — no confirmation step. |
99-end-of-session.md |
End-of-session checklist. |
Conditional injection
Some rules carry a first-line <!-- steer:inject-when=… --> marker and are
injected only when their scope applies (see
inject-standards.sh). The code-loop rules — 10-stack,
15-commands, 20-layout, 22-housekeeping, 24-worktrees, 35-issue-tracker,
40-testing, 41-coverage, 45-commit-autonomy, 50-definition-of-done,
51-verify-loop, 53-autonomous-loops, 55-drift-gates, 62-hotfix,
75-compliance, 80-change-size, 85-practices, 90-design-sources,
92-user-facing-copy, 99-end-of-session — are marked
code-project, so they are skipped in knowledge-work mode (a confidently
non-code folder, e.g. a Claude Cowork product-owner workspace). 12-stack-infra,
36-issue-first, and 52-deployment are likewise scoped — respectively to
repos that do IaC (has-iac), use GitHub as the tracker (tracker-github), and
those that do IaC or ship an app (has-iac|has-apps, where has-apps is
an apps/ directory, a package.json, or a pnpm-workspace.yaml — so
52-deployment injects in any Node repo, not only one that deploys today).
Polyrepo topology is deliberately not an
always-on rule — the ruleset is capped on its on-disk total, which a scoped
rule pays in full for every consumer. It is delivered instead by a
spec/workspace.yml / spec/PRODUCT.md-gated note inside
orient-session.sh — the hook itself speaks in every managed repo; only the
topology block is marker-gated. That block is registered on the same
startup|resume|clear|compact matcher as the ruleset, so it survives a
/clear, a resume and auto-compaction. The router, context-hygiene, spec-workflow,
decision-capture, living-docs, roles, gate-prompts (61), high-risk,
not-the-gate, self-report, secrets, output, and artifacts rules carry no
inject-when marker and so stay always-on.
Tooling knobs¶
policy/versions.yml— version floors;check-version-pins.shblocks pins that violate it.policy/branch-protection.yml— the branch-protection ruleset/steer:protectverifies the live GitHub settings against, and applies on explicit confirmation.STEER_CLAUDE_CODE_VERSION(inmise.toml) — the pinned Claude Code version CI installs, for reproducibleclaude plugin validate.
Rules are kept lean and imperative on purpose. Long-form prose lives in
plugins/steer/templates/reference/ and is surfaced through a skill, never
added to rules/. That leanness is enforced, not aspirational: CI's
check_context_budget.py gate holds hard ceilings over three context surfaces.
Two are always-on and ratcheted — the total rules/*.md bytes (the SessionStart
injection payload) and the total skill-listing description + when_to_use
characters — re-armed at each reduction, so always-on weight normally only
shrinks or holds. The default answer to "this rule doesn't fit" is therefore
trade prose out first: relocate rationale into
plugins/steer/templates/reference/, or deliver a scoped rule through a hook
instead of rules/ (the polyrepo precedent above).
These two are policy numbers, not harness limits, so they can be raised —
which is why each raise carries a recorded reason in the gate script rather than
happening quietly. The rules ceiling has been raised five times. First from
62,500 to 65,200, to fund rule 61-gate-prompts: the ratchet had drifted to 32
bytes of headroom, so the only way to add the rule was compressing unrelated gate
rules, and that trade deleted ~1 KB of rationale prose that existed nowhere else in
the repo. Paying the bytes was judged cheaper than losing the prose. Then from
65,200 to 65,300, because the polyrepo work landed in the same cycle and consumed
that new headroom down to 7 bytes — leaving three factual corrections to always-on
rules (a wrong /steer:doctor routing claim, a missing scripts/ entry in the
root allowlist, a mis-cited rule heading) with nothing to spend. Then from 65,300
to 66,500, to fund the worktree-trust step in rule 24-worktrees: a worktree
created with git worktree add mid-session is the one case no hook can reach — the
check-worktree-trust session check covers a session started in a worktree — so
the instruction has to be always-on to exist when it is needed. That raise also
re-armed at measured + ~1% rather than the 5-to-7-byte margins that had made each
previous raise inevitable. Then from 66,500 to 67,300, to fund six
surface-scoping corrections: rules 00, 05 and 97 told the agent a SessionStart
hook would flag a condition, which is true in Claude Code but not on Copilot
(whose sessionStart ignores stdout), and rule 10 promised a hard deny that is
only an ask on the Copilot CLI and absent in VS Code — in each case a rule
asserting a safety net that would not be there. Rules 24 and 99 named
docker:up/docker:clean, which the workspace profile renamed to ws:*, so the
cleanup command those rules mandate did not exist in a spine host. Rule 15 now
carries the workspace task vocabulary once and rule 24 cross-references it, paying
back ~120 B of the cost. Finally from 67,300 to 68,400, to fund the Tiny
ceremony exemption in rule 80-change-size and its two consumers — unlike the
fourth raise this is new capability rather than a correction, so it took an
explicit decision. Making the size class actually govern needs three always-on
statements (the exemption, the authority claim, the size-gated markers) and cannot
be expressed by cross-reference alone, because the rules being exempted are the
ones a session reads. Trades were made first, as the default requires — the same
change that shrinks a per-change duty paid part of its own cost. Which rules paid,
and how much, is recorded only in the ratchet note in
scripts/check_context_budget.py, for the reason given below. Net +511 B, re-armed
at the measured total plus ~1%. The target deliberately
stays at the old 62,500, below the ceiling, so the budget report keeps showing
the gap as work to reclaim.
Then, for the first time, the ratchet turned the other way: 68,400 → 67,500.
That fifth raise's ~1% headroom had been consumed back down to 178 bytes,
which made the ceiling load-bearing on the next rule edit of any kind. 1,632 B
were reclaimed across nine rules — 00, 10, 24, 30, 36, 45, 50, 62 and 99 — mostly
by removing prose a templates/reference/ file already carried, or by compressing
wording in place. The per-rule attribution is recorded in one place only, the
ratchet note in scripts/check_context_budget.py; it is deliberately not restated
here, because a second copy of it has twice drifted from the first.
One imperative did leave the always-on rules: rule 45's "don't retry a
declined push — graduate instead" is no longer in any rule. It survives in
GATES.md and in the trunk-push hook's own repeat reminder, so a Claude session
still meets it at the moment it matters — but the Copilot CLI, where that repeat is
a silent allow, now reads it on demand rather than every session. A deliberate
trade, and the reason "no rule lost an imperative" is too strong a claim to repeat.
The ceiling came down by 900 B — deliberately less than was reclaimed — so
headroom grew roughly 5x in the same change that tightened the ratchet, and rule
22's absorbed-source correction then spent 360 B of that (it had been projected at
~150 B). Rule 92-user-facing-copy then spent what was left, so the
ceiling became load-bearing again. In 5.3.0 that choice was closed by
re-arming, 67,500 → 68,200: a correctness fix to rule 92 cost 17 B against a
7 B margin, so the ceiling was dictating the fix's wording rather than bounding
its cost — the exact failure the 900 B lowering existed to end. Sized at
measured + 1%, restoring a ~690-byte margin. The reclaim half of the choice is
still owed, against the unchanged 62,500 target. For the total on any given tree, run
uv run python scripts/check_context_budget.py --report — a figure pinned in
prose goes stale on the next rule edit.
The skill-listing ratchet has moved twice. The first, in 3.23.0, 11,500 → 11,900
chars, for a different reason than the rules ceiling: not a budget concession but a
measurement correction.
/steer:work's when_to_use was an unquoted YAML scalar containing `("work on
123", so #` opened a comment and the value silently truncated at 75 of 546¶
characters. The ratchet had been calibrated against that truncated value, reading
22 chars of headroom while the intended payload was ~450 over. Fixing the YAML
necessarily exposed the real total; work's entry was first trimmed 932 → 747
chars so the raise paid what it could. LISTING_TOTAL_TARGET_CHARS stays at
10,000, again below the ceiling.
The second, 11,900 → 12,400, is a deliberate re-arming rather than payment for
any specific edit. The correction above landed at 11,879 of 11,900 — 21 chars — so
the next factual fix to any description or when_to_use could not be paid for in
place at all. A pre-release audit hit exactly that: three description corrections
had to be engineered as a length-neutral set, which is the ratchet dictating the
wording of a correctness fix instead of merely bounding its cost. 12,400 buys ~521
chars — about one mean listing entry — so trading prose out stays a real choice
rather than the only physically available move. The policy is unchanged: trimming
first remains the default, check_plugin.py's per-skill 1,536-char cap is untouched
so no single skill can absorb the new headroom, and the target stays 10,000.
The listing ceiling was then deliberately held at 12,400 while 232 chars were
reclaimed alongside the rules trim above (/steer:reference stopped
parenthesising each topic its own when_to_use already explains in question form;
work, spec and intake dropped restatement). Seven literal subtopic tokens
went with it — commit style, spec routing, audit evidence, subagents,
durable state, Mermaid, LikeC4 — and they survive nowhere else in the
measured surface; the topics stay reachable through reference's eight doc-name
arguments, so this was a deliberate trade, not a lossless one. Lowering the
ceiling would contradict this block's own basis: 12,400 was chosen to buy ~521
chars, and no reduction from the resulting 11,978 leaves that much. Reclaim more
first, then the ceiling can move.
The third surface is per-skill and not a ratchet: each SKILL.md body is capped at
17,500 bytes. That number is the harness's compaction re-attach cap — after
auto-compaction Claude Code re-attaches an invoked skill but keeps only the
first ~5,000 tokens of it, so anything past that point is silently dropped
mid-run. An oversized skill therefore loses its own guardrails exactly when a
run has gone on long enough to compact. steer's skills keep guardrails,
coupling rules, and output contracts near the top of SKILL.md and factor
per-mode or per-phase procedure into sibling files (modes/<mode>.md,
OPERATIONS.md, PROCEDURE.md, …) that the skill reads just-in-time for
the one path it is executing — a file read that way is a tool result, not skill
content, so it never competes for the re-attach budget. Because this ceiling is
derived from harness behaviour rather than a budget target, it does not move
down as bodies shrink and is not raised to fit new prose.
A companion routing-fixture net
(tests/fixtures/routing/asks.yml) pins the vocabulary plain-language routing
depends on, so trimming can never silently break "just say what you want".