← Philip Bergen Case study · AI-assisted engineering

z-nile Pipeline Map

z-nile is the tooling that takes a one-line request to a merged, tested pull request. Work moves through five stages. Each stage is a skill (a Markdown file of rules an agent follows), and each skill drives commands in one engine, ./zn, which keeps the whole tracker as files in git.

  • 7 projects
  • 136 issues and 71 rafts done
  • 24 skills
  • 273 lessons harvested
  • 8.1% rework, target under 20%
  • 43 engine modules, ~20k lines of Luau
  • 2,233 commits since Sep 28

Why I built it. Agents write code quickly. What decides whether that code can be merged is the process around them: who writes the tests, who reviews, what gets measured, and what is learned from each mistake. z-nile is that process, written down as skills and enforced by an engine, and it is built with its own pipeline.

The route

Five stages, one direction

An issue is captured, designed and grouped into a raft. A raft is readied, then run as one branch and one pull request. Pick a stage to jump to its files.

1Capture/z-ticket 2Design/z-design 3Raft/z-raft 4Ready/z-ready 5Run/z-run → zz-execute
Status
→ new
new → preparing → prepared
issue gets raft:
prepared → active
active → done
Design
draft
draft → reviewed → ready
raft: ready
—
—
Works in
capture-<slug>
prep-<slug>
raft-<slug>
main checkout
R0010, ws-<issue>
Review
none
design ×1
coherence ×1
none
stream code ×1
Model
Sonnet 5
Opus 5.5 high
Sonnet 5
script step
Opus 5.5 high
Lands by
./zn land, no PR
prep PR
rafting PR
tracker-only push
the raft's PR
Off-ramp
/z-stop gives a readied or started raft back: active → prepared. Its branch and PR stay for the human.

Worktrees live under .worktree/; the main checkout only fast-forwards.Models are the claude CLI's rows in .models.toml; grok and Cursor's agent have their own.

Stage by stage

What each stage reads, runs and writes

Reads are the Markdown an agent loads. Runs are the commands it calls, with the engine code behind them (paths under z-nile/engine/ unless shown). Writes are the files it leaves in the item's folder.

1

Capture

/z-ticket

Turns a request into a backlog issue: a feature (f), a defect (d, counted as rework) or a chore (c, all tooling and test work). It writes only Problem, Non-goals and Solution ideas, and that text becomes the scope no later stage may narrow.

Reads

  • z-nile/skills/z-ticket/SKILL.md
  • z-ticket/lessons/ 5 lessons
  • z-nile/templates/issue/doc.md

Runs

  • ./zn worktree capture-<slug>
  • ./zn new issue <f|d|c> <slug> → ids.luau, publish.luau numbers it from z-nile/next and pushes the stub to main at once
  • ./zn land → main.luau merges main, runs the full gate, pushes
  • ./zn spend --phase capture → spend.luau

Writes

  • <project>/.tracker/issues/<id>/<id>.md
  • check.sh stub from templates/issue/check.sh
  • spend.md
Landsstraight to main after a local full gateThen/z-design
2

Design

/z-design

Designs one issue until any strong agent could deliver it on the first try. The acceptance specs are written here, before any code, and sequestered: the executor can run them but never edit them.

Reads

  • z-nile/skills/z-design/SKILL.md
  • checklists/feature.md, defect.md, chore.md
  • lessons, searched with ./zn lessons <terms> 86 in z-design's own folder
  • the project's arch skill pickman-arch for the game, zz-arch for tooling
  • pickman/.tracker/game-design.md the user's binding product decisions
  • <project>/.tracker/architecture.md its deps block

Runs

  • ./zn status <id> preparing → status.luau, claims.luau the claim is a push to main
  • ./zn capture <id> the issue as first captured, for the scope check
  • lune run z-nile/affected -- <paths> → z-nile/affected.luau
  • ./zn check <id> → z-nile/lib/ac.sh must fail for the right reason
  • ./zn promote-check <id>
  • ./zn dispatch zz-review <id> --mode design → dispatcher.luau

Writes

  • <id>.md Design, Touched modules, Unknowns & experiments, Regression guard, Test plan, Rules and mutants, Acceptance criteria (Checked, Judged, Observed), Implementation steps, size:
  • check.sh one check "ACn: …" per Checked criterion
  • acceptance/*.spec.luau red until built
  • design-review-NNN.md, design-refute-NNN.md, harvest.md
  • new files in <skill>/lessons/
Reviewzz-review design mode: one fresh reviewer with no shared context, on Opus 5.5 or grok-4.7 xhigh; one answer, then anything refuted or still open goes to the human under ## Open to the humanLandsprep PR
3

Raft

/z-raft

Groups prepared issues into rafts. A raft is one branch and one PR: one type, one project, about 1,500 changed lines at most. Features group only when their touched paths overlap.

Reads

  • z-nile/skills/z-raft/SKILL.md
  • z-raft/lessons/ 11 lessons
  • zz-rules: Sync and claim, Rafting
  • z-nile/templates/raft/doc.md

Runs

  • ./zn raft-plan [--apply] → raftplan.luau the fixed grouping policy
  • ./zn new raft, fold, unfold, recombine, release → main.luau, raftcheck.luau
  • ./zn status <R> prepared refuses without a Theme, an Order, or a review of the current grouping
  • ./zn dispatch zz-review <R> --mode raft

Writes

  • <root>/rafts/<R>/<R>.md Theme, Whole-raft design, Order & dependencies, Risks & rollback
  • check.sh RC0 runs the project's gate, then RCn checks
  • each member's raft: line, and a grouped: hash of each member's design
  • design-review-NNN.md, harvest.md
Reviewone coherence round per grouping, for rafts of two or more issuesLandsrafting PR
4

Ready

/z-ready

Claims the raft and freezes everything an executor needs into one self-contained prompt, so any agent (Claude, Grok, Cursor) can deliver it without the conversation that produced it.

Reads

  • z-nile/skills/z-ready/SKILL.md
  • z-nile/templates/prompt.md

Runs

  • ./zn sync fast-forwards the main checkout
  • ./zn ready <R> → main.luau, publish.luau checks every member is design-ready with a check, and every outside dependency is done; moves the items to active/
  • ./zn workstreams <R> plans parallel waves: parallel: yes|no

Writes

  • <root>/rafts/active/<R>/prompt.md git-ignored: the raft's theme, order and interfaces, and every other file by path (issue documents, checks, specs, the architecture map, the skills to follow)
  • one tracker-only commit on main the move to active/, with check.sh paths rewritten
Modelnone: the dispatcher runs it as a script stepThen/z-run <R>
5

Run

/z-run → zz-execute

Delivers the raft as one PR. z-run is 24 lines: read the prompt, enter the worktree, hand off. The protocol itself is zz-execute, in seven phases.

  1. Claim
  2. Experiments
  3. Implement, tests first (2B: parallel Sonnet streams)
  4. Whole-raft verification
  5. Adversarial code review
  6. Done
  7. Merge

Reads

  • z-nile/skills/z-run/SKILL.md
  • rafts/active/<R>/prompt.md
  • z-nile/skills/zz-execute/SKILL.md phases, stop conditions, final report
  • zz-rules, zz-arch, zz-test, zz-review and the project's skills named by path in the prompt

Runs

  • ./zn start <R> branch R0010-<slug>, empty commit, draft PR
  • ./zn focus, ./zn commit → spend.luau, ledger.luau spend rows and Issue: trailers in the same commit
  • z-nile/check.sh --affected while iterating
  • ./zn check <id>, the raft's check.sh
  • ./zn dispatch zz-review <R> --mode code
  • ./zn done <R> checks every finding is answered, promotes the specs to tests/acceptance/<id>/, moves items to done/, marks the PR ready
  • z-nile/check.sh --full once, in a clean clone, before the merge

Writes

  • code and specs in the project's src/ and tests/
  • a Verification log in each issue
  • review-NNN.md, refute-NNN.md each answer: fixed <sha> with consequences, refuted with evidence, or deferred
  • stream-review-<issue>-NNN.md for parallel streams
  • harvest.md, spend.md, PR comments the PR is the visible record
Reviewcode mode on Opus 5.5, never the author's cheaper model; one round and one answer, then anything refuted or unclosed goes to the humanLandsthe agent merges when the gate is green and nothing is open to the human
↺

Stop

/z-stop

The off-ramp from stages 4 and 5. ./zn stop <R> moves the raft and its issues back to prepared and deletes the prompt, and leaves the branch and PR for the human to close or keep. Rules: z-nile/skills/z-stop/SKILL.md.

Called from inside a step

The zz- skills behind each stage

The z- skills are the stages. The zz- skills hold rules several stages share; a stage loads them as it needs them.

SkillHoldsSizeCaptureDesignRaftReadyRun
zz-rulesIDs, lifecycle folders, worktrees, landing and merging, spend, dispatch, claimsevery tracker move203 lines · 7 lessons●●●●●
zz-testthe gate and its modes, the spec runner, check.sh, sequestration, repository guards, bounded commands162 lines · 77 lessons●●●●●
zz-reviewthe clean adversarial reviewer, in design, raft, stream and code modes117 lines · 8 lessons●●●
zz-executethe delivery protocol: phases 0 to 6, stop conditions, final report152 lines · 12 lessons●
zz-archcode rules for the tooling itself: strict Luau on Lune, pure modules, guards that fail closed47 lines · 53 lessons◐◐
<project>-arch, -testeach project's code structure and spec tierspickman, arcade, portal, snake, invaders, asteroids6 pairs◐◐
zz-statscost per issue and the rework rate, from spend.md32 lineson demand, outside the stages

● always◐ for that kind of work only: tooling issues read zz-arch, a game's issues read its own arch and test skills

Under every stage

The machinery

Six parts the stages share. Each names its code.

The tracker, in git

No server. A claim is a push to origin/main; every status write is stamped <state>-<three words>, so a lost race shows up as a git conflict.

  • zn → engine/main.luau the CLI, ~6,000 lines
  • store, status, claims, publish, ids, audit, projects.luau
  • <project>/.tracker/{issues,rafts}/ with active/ and done/
  • z-nile/projects.toml, z-nile/next

The gate

Format, lint, types, Rojo build and specs. --affected while iterating, --full once at the merge.

  • z-nile/check.sh each project's check.sh calls it
  • z-nile/affected.luau require graph → which specs run
  • z-nile/tests/run.luau the shared runner
  • z-nile/lib/ac.sh the check.sh helpers
  • .claude/.znile_tests.sqlite skips specs whose inputs already passed

Guards and hooks

Wired in .claude/settings.json. They catch honest mistakes; the docs say plainly they are not a security boundary.

  • PreToolUse scripts/hooks/pre-bash.sh → boundcmd.luau refuses unbounded commands
  • PostToolUse post-edit.sh stylua and selene on every .luau edit
  • SessionEnd session-end.sh → reap.luau
  • ./zn run → z-nile/lib/bounded.pl
  • lib/fence.sh, lib/git-hooks/, a macOS jail specs can't touch the real repo

The dispatcher

./zn dispatch <id> runs the chain headless. It stops at a merge gate, at a questions.md or at a failure, and falls back to the next CLI on quota.

  • .models.toml → models.luau model, rank, token cap per step and CLI
  • dispatcher.luau ~2,200 lines, dispatch, runner, adapters.luau
  • z-nile/bin/git, gh → wrapper.luau agents can't push main or merge
  • broker, credential, trackerstate.luau publish broker, bot credential, watermark

The learning loop

Every review finding is mapped to a lesson or marked one-off. Agents search them with ./zn lessons <terms> before work a lesson might cover.

  • <skill>/lessons/*.md title, when, tags, check question
  • harvest.md in each item
  • lessons.luau ← ./zn lessons
  • --uncovered <id> and --check must pass before done

Spend and the board

Each commit records minutes, lines, tokens and model. The board shows every project's pipeline on one page.

  • spend.md ← spend.luau, ledger.luau
  • ./zn metrics → stats.luau cost per issue, rework rate
  • ./zn board → board.luau, boardview.luau, chart.luau
  • output: .cache/board.html, a tab per project
A worked example

One piece of work, as it sits in git

The dispatcher itself went through the pipeline: issue c0117, delivered in raft R0052 as PR #125 on Nov 6.

c0117 · Stack model files and dispatch runtimes

z-nile/.tracker/issues/done/c0117/
  • c0117.mdthe issue: the captured Problem, then the Design and criteria prep added
  • check.shone AC line per Checked criterion
  • design-review-001…010ten rounds of clean-context design review, under the old multi-round rule
  • design-refute-001…010the author's answer to every finding
  • harvest.mdeach finding mapped to a lesson
  • prototype.patchthe prototype the mutation checks ran against
  • spend.mdminutes, tokens and model per phase

Its 9 acceptance specs now live in z-nile/tests/acceptance/c0117/, promoted by ./zn done.

R0052 · Models and dispatcher

z-nile/.tracker/rafts/done/R0052/
  • R0052.mdTheme, Order, Risks, and what was addressed during execution
  • check.shRC0, the project's gate, then raft checks
  • review-001…010ten rounds of code review, under the old multi-round rule
  • refute-001…010each finding fixed with consequences, or refuted
  • harvest.mdcode-review findings mapped to lessons
  • spend.mdthe raft's spend summary

The prompt that drove it, prompt.md, was git-ignored and is gone; everything else is history.

Instruction layering

How an agent finds its rules

From the file Claude Code loads first to the hook that enforces what the text asks.

  1. CLAUDE.mdOne line, @AGENTS.md, so every AI CLI reads the same file.
  2. AGENTS.md36 lines: the non-negotiables (no code without an issue, every change in a worktree and through a PR, no check may change the real repository) and a table of which skill to read when.
  3. .claude/skills/<name>24 committed links into z-nile/skills/ and each <project>/skills/, written by ./zn skills and checked by the gate.
  4. SKILL.md + lessons/The rules for one step. Each skill reads its own lessons before it starts, and has a Headless section for runs under the dispatcher.
  5. templates/prompt.mdFor execution, z-ready writes one prompt that names the work items and the skills by path, so the executor needs no chat history.
  6. .claude/settings.jsonHooks and permissions that enforce the text: bounded commands, lint on edit, reaping at session end, no fork subagents.
  7. auto-memoryStanding preferences carried across sessions, such as merge when green and run the full gate only at the merge.
For the conversation

Design choices worth explaining

  • Tests come before code, and the coder can't edit them.Acceptance specs are written at design time. Code review checks that the PR changes no check.sh or acceptance spec beyond the move to active/.
  • One clean review, then the human.A fresh agent with no shared context, on a stronger model, reviews once. Every finding gets a written answer, and anything refuted or still open goes to the human to rule on.
  • Every finding becomes a lesson.273 so far, each with a check question. Agents search them before work a lesson might cover.
  • The tracker is plain files in git.No database or queue. Status, claims and spend are commits, so every host and every CLI sees the same state.
  • Scope is frozen at capture.Design may add detail, never cut it. Only the user narrows scope, recorded as a dated Scope decision line.
  • Model per job, any CLI.Sonnet captures and rafts, Opus designs, builds and reviews. .models.toml maps each step to Claude, Grok or Cursor and falls back on quota.