Development Guardrails
The full development discipline — TDD, standards, QA, refactor, audits — and the machinery that enforces it without costing the developer time.
This is the meta-harness: the rules every project runs under, and the machinery that enforces them so nobody has to remember them. Each rule exists because of a specific failure, not as abstract best practice.
The organising principle: a developer should spend their attention on the problem, never on the process. Everything below is automated, ratcheted, or scheduled. If a rule needs a human to remember it, it is not yet a guardrail.
The loop
Every change, human or agent, follows the same path:
Write the test → make it pass → make it clean → gate → push and deploy.
No step is optional and no step has a skip flag. The last two are one action, not two.
Tests first, behaviour outward
Write the failing test before the code. Not for ceremony — a test written afterwards tests what you built, while a test written first tests what you meant.
Tests name behaviour, not implementation: rejects a token that has expired, not
calls verifyToken. Behaviour-level tests survive refactoring, which is what makes
aggressive refactoring safe. Implementation-level tests have to be rewritten every time
the code moves, so they quietly discourage improving it.
A test that cannot fail is worse than no test, because it converts a visible bug into a green suite. Two real instances from one day:
- A dashboard read
memory_usagefrom an API that returnsram_usage. The page renderedundefined%. The test mock declaredmemory_usagetoo — copied from the UI’s assumption instead of the API’s actual response — so the suite stayed green. - Token minting was replaced with a constant. 26 of 27 tests still passed.
So: derive fixtures from real responses, never from the code under test. And periodically break something on purpose to confirm the suite notices. A suite that has never caught anything has not been proven to work.
Code standards
Terse, well-abstracted, and readable by an AI arriving with no context.
- Files stay small. Past ~500 lines, split. Large files are where duplication and dead code hide.
- A 1–3 line header on every file, written for a machine, not a human:
purpose, key dependencies, non-obvious behaviour. Never restate what the code says.
// Auth middleware. JWT cookie → user on locals. Deps: jwt, db - Comments explain why, never what. The code already says what.
- No dead code. An unimported 2000-line component reads as load-bearing to whoever inherits it. Delete it or wire it up.
- Names must not lie. A name that misdescribes its subject is a defect to fix, not a quirk to document — misinformation costs more than absence.
- Root directory holds config only.
src/code,tests/tests,docs/docs,scripts/utilities,tmp/scratch and gitignored.
The gate is a ratchet
A gate demanding zero problems gets switched off within a day, because real codebases don’t start at zero. So every check produces a count, compared against a recorded baseline, and the rule is: never worse than last time. Improvements rewrite the baseline downward; regressions block.
One exception: a failing test is a hard bar, not a ratchet. Wrong today is wrong regardless of yesterday’s count.
Never bulk re-baseline. Accepting nine new findings in one “re-baseline after merge” commit is how a ratchet becomes a rubber stamp. Raising any number is a deliberate, separately explained commit.
What gets checked
Tests · security (committed secrets, dependency vulnerabilities, dangerous patterns) · dependency currency · elegance (dead code, oversized files, duplication) · lockfile drift · type checking. Web projects add rendered layout, responsive sweep, SEO, and Lighthouse.
Each check is a standalone script, runnable alone. Bundled logic that only the gate can invoke cannot be debugged when it misreports.
Measure the commits, not the working tree
The gate must run against an isolated checkout of exactly what is being pushed, not
the directory as it sits on disk. When several agents share a repo, a working-tree
measurement is contaminated by whoever has work in flight — the same commit measured
298 findings in the shared tree and 302 in a clean checkout, and a regression reached
main behind a green check.
CI gives this for free: a fresh clone can only measure the commits.
Instruments are code, and code has bugs
The checks themselves are the least-tested code in most projects, and a broken check is worse than a missing one because it radiates false confidence.
- Every auditor needs its own tests. If a check has never been run against a known-bad fixture, it is not known to work.
- A check that only ever produces false positives must be fixed or deleted. One check flagged three items in its lifetime; all three were ordinary prose. Checks that cry wolf train people to ignore the whole gate.
- Passing a measurement is not passing the thing measured. Pure green on near-black scores ~15:1 contrast — it passes every checker and is genuinely fatiguing to read. A wordmark masked with a 1px stripe pattern passes contrast checks while half of each glyph is literally unpainted. Automated checks bound the floor; they do not certify the ceiling.
- Verify against the authority. Never report a problem measured from a local or partial artifact. Ask production, or say you couldn’t.
Refactor continuously
Refactoring is part of every change, not a scheduled event. The make it clean step
happens while the tests are green and the context is loaded — a refactor deferred is a
refactor that needs its context rebuilt from scratch.
Refactoring targets, in priority order: names that lie, duplicated logic, files past the size limit, dead code, and abstractions that leak their implementation. Behaviour-level tests are what make this safe to do aggressively.
A rule can be the thing that breaks the page
The elegance check’s missing-header rule required a comment on an .astro file’s
literal first line, and accepted a leading <!-- ... --> HTML comment there as
satisfying it. That comment sits above the --- frontmatter fence. It compiles
clean and passes astro check — but it breaks Astro’s SSR component-import resolution
at runtime: every import inside the frontmatter silently becomes undefined. On
Cloudflare Workers that surfaces as a raw crash with no error page, which the
production site returned as a completely blank 500, 0 bytes, for every signed-in user
hitting the page. Nothing said a page had died; Chad found out by clicking the link.
An agent complied with the finding exactly as written and the compliance caused the
outage. That’s a different failure than a check missing a real problem — the check
produced one, and 67 other .astro files carried the same accepted finding at the
time, each one an invitation to reproduce it. The fix: for .astro files, “header
comment on the first line” now means the first line inside the frontmatter fence —
immediately after the opening ---, a plain // comment, the shape every other page
in this codebase already used — not literally byte one of the file. A rule that can
only be satisfied by breaking the thing it’s attached to isn’t a smaller version of a
correct rule; it’s the same class of defect as a check that can’t run and reports a
pass anyway, just discovered by someone obeying it instead of someone reading its
output.
End-to-end and visual QA
E2E covers the paths a user actually takes, not every path that exists. Each runs against a real deployment, never a mock.
The responsive sweep renders every route across the full width range (320px upward) and fails on overflow, overlap, and truncated text. Most layout bugs are width bugs, and they are invisible at the width you happen to be developing at.
Three states, always distinct. Empty, loading, and error must never render the same way. When they collapse into one flat nothing, a user cannot tell “no data yet” from “this is broken” from “still fetching” — and every one of those needs a different response from them. This single defect produced four separate complaints on one product.
Audit schedule
| Audit | When | Blocks? |
|---|---|---|
| Tests, elegance, lockfile, types | Every push | Yes |
| Security (secrets, dependencies, patterns) | Every push | Yes |
| Responsive + visual regression | Every deploy | Yes, rolls back |
| Lighthouse, SEO, performance | Every deploy, post-artifact | Yes, rolls back |
| Dependency currency | Weekly | Ratchet only |
| Fleet-wide standards sweep | Weekly | Reports |
| Usability audit | Per significant UI change | Reports |
| Full accessibility pass | Quarterly | Reports |
Pre-deploy checks measure the code. Post-deploy checks measure the artifact, and roll back automatically on failure.
Never audit production as a precondition for pushing. Making a push conditional on production’s health means a broken production blocks its own fix — that deadlock once held 69 commits hostage.
Push and deploy are one action
When the gate is green, the change is pushed and deployed. Never one without the other. Every deploy links to a pushed commit and increments the version, so the deployed artifact is always traceable to source.
There is no manual deploy path. A deploy that doesn’t correspond to a pushed, tagged commit is unauditable, and the pipeline makes that state unreachable rather than merely discouraged.
Post-deploy verification is mandatory and rollback is automatic. A build that silently fails to apply a patch can return HTTP 200 while serving broken output on every route — so verification asserts the artifact’s actual content, not merely that the deploy command exited zero.
Why this costs less time than it saves
Every rule above removes a decision. Nobody argues about coverage targets, remembers to run the responsive sweep, or debates whether a change is worth deploying. The gate answers, the pipeline ships, and the developer’s attention stays on the problem.
The measure of the harness is not how much it catches. It is how little anyone thinks about it.