Mark Evans

I build software with coding agents under positive control: every change planned, gated, reviewed and shipped the same way.

Positive control is an air traffic term. No aircraft moves without explicit clearance. Here, no change ships without one. One feature is on the scope now, flying the whole route. Pause it and step through at your own pace.

The thread

Every session starts here. It surfaces ready work in priority order and routes it by shape and board state.

Routing

epic or unshaped
Goes to /feature_plan to be designed and split.
sized Todo issue
Goes to /task_plan.
In Progress
Resumes in /implement from the task file.
Blocked
Shows the blocker instead of starting work.

Checked at the door

sizing
An issue fits when its acceptance criteria fit in five testable bullets. More than that is an epic.

The agent explores the codebase and drafts a design doc. Nothing is an issue yet. The doc is a proposal; issues are a commitment.

Produces

docs/plans/<slug>-design.md
The design doc, with the slice list.
Status: Draft placeholders
Stubs for the system and procedure docs this feature will need.
feature/<slug>
The feature branch name, written into the doc when there is more than one slice.

Valves, off the chain

/grill
Interviews the design in rounds until no question is left open.
/research
Reads primary sources and writes one cited note.
/domain_model
Settles a term doing two jobs. Writes the vocabulary and an ADR.
# Home thread — design

Status: Approved (G1, 2026-10-03)
Date: 2026-10-03
Slug: home-thread
**Feature branch:** feature/home-thread

The home page of markevans.io is one real feature moving through the delivery
chain, played as a self-running, steppable sequence of flight strips on a git
spine, with a radar scope as the console.

A question comes up that would derail the design. I open an isolated thread, settle it there, and bring back only the findings.

The cycle

/segue
Opens the thread and parks the workstream.
/segue_resume, /segue_close
Picks the thread up later, or finishes it.
/segue_merge
Returns the findings to the plan. The discussion itself stays out.
/segue_kill
A dead end. Records why it died and merges nothing.

While designing this page I set it aside for one question: what shows a change moving under control? I looked at the paper flight strips air traffic controllers pass between bays, and at how git draws a branch history. What came back: each step is a strip of ruled cells (Ctl, Bay, Rmk), the board's four states are the strip bay, and the sequence runs down a git graph that forks, merges and loops.

I read the design doc and approve it, send it back, or cut it down.

Gate

blocks
Issue creation.
cleared by
Mark.
then
A parent issue and sized sub-issues land on the board as Todo.

We drew four designs and compared them side by side, then took elements from two of them to agree on the initial design.

One sized issue becomes a task file, an implementation plan and a branch. The issue number threads through all of them.

Produces

.llm/tasks/<n>_<slug>.md
The task file. Any fresh session can resume from it.
prefix/<n>/<slug>
The branch.
Base: feature/<slug>
Recorded so every later command diffs against the right base.
draft PR to main
Opened with the first slice, so CI runs on the feature from day one.
last action: the walkthrough
The steps this change adds to the reviewer walkthrough, or one line on why it has nothing to show.

How rules are selected

CLAUDE.md
States the non-negotiables in short form and points at the index.
docs/rules/INDEX.md
Routes by the paths this task will touch.
frontmatter: applies_to, triggers
Path globs and keywords decide which rules match.
frontmatter: tokens
The read cost of each rule, so the plan loads only what it needs.
frontmatter: modes, current_state
Which kind of app the rule ships to, and whether this app matches it or diverges.
docs/rules/SYMPTOMS.md
Read only when routing by path missed.
rules routed for PR #23's files (rebuilt from docs/rules/INDEX.md)
registries · tailwind · tailwind-build · tokens-only · color-means-role · typeface-means-speaker · content-is-files · no-invented-content · testing

I read the implementation plan before any code exists.

Gate

blocks
Any code.
cleared by
Mark.

The first slice forecast about 1,600 lines across 33 files, past the 25-file limit. I split it: tokens, loader and gates first (#14), the static page second (#21), so nothing user-facing changed until the foundation was in.

The agent loads the task file and builds. Tests first, one commit per logical unit.

Rules in play

path routing
Each file edited routes to its rules through the index, the same way planning did.
right and wrong pairs
Every rule shows both versions side by side, so the agent sees the specific mistake to avoid.

Enforced while writing

bin/hooks/post_edit
RuboCop on Ruby files; a bracket-class check on Slim templates.
.claude/settings.json deny list
Refuses force-push, reset --hard, clean -fd and reading secrets.
bin/hooks/session_end
Flags draft doc placeholders left behind at the end of a turn.

Valves

/diagnose
A bug with no failing test. Builds a tight loop first, then hunts.
/test_fix
The suite is red. Triage and a local fix.
scope escape
Forecast passes 1,500 added lines or 25 files: stop, split a sub-issue, land this slice clean.
19 files changed, 1,040 lines added, 35 removed

Before any PR exists, an agent with a new context reviews the branch. It has not seen the implementation conversation, so it reads the diff cold.

Rounds

round 1
Full review of the diff. The implementer verifies each finding.
disputes
I rule on them.
fixes
Each lands with a test that failed first.
round 2
Reviews only the fixes from round 1. There is no round 3.

Also here

/rails_code_review
Reviews the branch against this stack’s conventions. Posts nothing.
/run_lint
RuboCop and bin/flay on the diff, fixed locally.

I invoke this one myself. Opening a PR has an audience.

In order

bin/rails db:queries
Regenerates the query ledger. Unreviewed queries get their plan printed for review.
local suite
Must be green before anything is pushed.
docs
Every placeholder is completed or deleted. The index is checked for duplicates and dead links.
/qa_walkthrough
A user-facing branch carries a reviewer walkthrough: a script in script/qa/ that builds the data, drives a browser to each page the branch touched and spotlights what changed. Skipping it is my call, and the PR body says why.
PR
Title carries the issue number. Body carries Closes #n.

An agent will eventually miss a rule, so the rules a machine can check are checked. None of these depend on the agent cooperating.

bin/gates · pre-push hook, again in CI

rejected patterns
Banned tokens in app/ and the Gemfile.
app/ directory budget
No new directory under app/ without an ADR.
command mirror
The Cursor commands match the Claude commands.
foreign keys
Every foreign key leads an index.

Their own CI jobs

bin/flay
Fails on Ruby this PR copied that its base did not have.
db:queries:check
Every SQL shape the suite emits has a reviewed line in db/queries.yml.
ci.yml
Ruby and JS security scans, RuboCop, the test suite.
bin/lint-docs
Doc checks, including real hosts or IPs leaked into a doc.
api:contract:check
API mode only: the OpenAPI contract agrees with the request tests.

Inside the test run

Bullet
Raises on an N+1 query.
Lighthouse CI
Accessibility.
Slowpoke
Suite speed.

Gate

blocks
The push, and the merge under branch protection.
scan_ruby ✓ · scan_js ✓ · lint ✓ · gates ✓ · test ✓

A person reads the PR. On a feature built in slices, each slice is read by /pr_review from a fresh session, and the feature PR is read by a human against the design doc.

Tools

/pr_review
Full-context diff review. Posts comments.
/pr_qa
A guided manual QA pass with a structured report. It starts from the walkthrough's screenshots, and the hands-on part begins where the walkthrough stops.
/pr_fix_ci
Reads a failed run and fixes it locally. Pushing the fix is mine.

The review shown here is slice #23's: eight comments from /pr_review, run by the agent in a fresh session. Each was answered with a fix commit. One added content:check, the gate that held this page until its content was filled.

Each review comment gets a fix or a reply, then its conversation is resolved. Other people are reading those threads, so I choose when this runs.

The cycle

fix or reply
One per comment.
changes requested
The board returns to In Progress until they are addressed.
/resolve_conflicts
Finishes a conflicted merge and regenerates what must not be hand-merged.

Nothing merges with an open conversation.

Gate

blocks
Merge.
cleared by
The reviewer and Mark.

One click. Everything after it is automation.

Automation

Closes #n
Closes the issue on merge.
board
The closed item moves to Done.
branch
Deleted automatically.
feature branch
After each slice, main is merged back in so the final merge stays small.

Merged to main in #22 on 2026-10-04, then held: the production build runs `content:check`, and this page's own placeholders failed it until each one was filled. Then `kamal deploy` to markevans.io.

After

deploy
Kamal 2, as the template configures it.
/update_docs
An on-demand deep pass over the docs that keeps the index honest.