The problem: the last mile
Part 1 · The problem
You built something real with an AI coding agent. The first 80% flew: a working prototype in a weekend, features landing faster than you could test them.[2] Then the last 20% did not arrive.
The pattern is common enough that ApexYard's README opens with it: the context slipped, the codebase turned into a pile nobody could review, decisions vanished into chat history, and the thing that was so close to shipping never reached production.[2] The agent is fast; nothing around it enforces the discipline that makes speed safe.
A real engineering org has that discipline. Every change moves through a ticket. An independent reviewer reads every pull request. A merge gate stays shut until a named human says "ship it". Risky work — a database migration, a launch — passes extra checks.
ApexYard is that discipline, packaged: an open-source framework that wraps your AI coding agent in the same gates, reviews, and receipts a real engineering team runs on.[1][2] It is built for Claude Code first, and it is plain markdown and shell — no runtime, no service, no SaaS, MIT licensed.[1][2] Its rules are not policy documents the agent is asked to remember; each rule is a small script that runs at the exact moment it matters and refuses to let the risky thing happen.[1]
One more claim, and it is checkable: ApexYard governs its own development. Every change to the framework goes through its own review gates, and the project publishes real numbers from that history — hundreds of PRs reviewed and merged, dozens of releases, every decision on record.[1]
"The framework" always means ApexYard, the me2resh/apexyard repo.[2] "The agent" means Claude Code by default; opencode, pi, and Codex run the same enforcement through a small adapter.[2] A slash command like /inbox is a skill you run inside the agent. This tutorial is a companion to our earlier one on Matt Pocock's skills; the two systems solve different layers — that one gives the agent good habits, this one builds the org around the agent.
The project: LinkLoft goes to production
Part 2 · The project
We keep the running project from the previous tutorial: LinkLoft, a small TypeScript URL shortener. A user shortens a URL into a slug, visitors get redirected, and the owner sees hits per link.
LinkLoft now has paying users on a waitlist, which changes the game. "Works in the demo" is no longer enough; the next bug or botched database change costs real customers. You are also not building one product anymore: alongside LinkLoft sit a billing API and a marketing site, and each lives in its own repo.
"Done" for this tutorial means: LinkLoft ships a real feature through a governed pipeline — ticket, independent review, human sign-off, QA — a risky migration passes its own gate, a launch-readiness check runs before customers see anything, and all three products report into one inbox.
As before, you do not need to build along. The project is a lens: watch what each part of the framework does to it, then apply the same move to your own repos.
Setup: fork, clone, /setup
Part 3 · Setup
ApexYard is not installed into a project. Instead, you fork the framework itself, and the fork becomes your ops repo: the one repository that governs your whole portfolio.[1][2] There are no nested installs and no symlinks — the fork is the ops repo.[1]
gh repo fork me2resh/apexyard --clone
cd apexyard
git remote add upstream https://github.com/me2resh/apexyard.git
The upstream remote matters later: when the framework ships a new version, a session-start check tells you the moment your fork is behind, and the /update skill syncs you on a dedicated branch.[1][2] You can rename the fork to your-org/ops if you prefer; GitHub handles the rename cleanly.[1]
Now open the agent inside the fork and run the first skill.
cd apexyard
claude
/setup
Everything loads automatically from CLAUDE.md when the agent starts.[1] /setup then configures the framework in three exchanges: describe your stack, review the proposed defaults, accept or tweak.[1][2] Your answers land in onboarding.yaml, which is gitignored — it stays local, and a commit-time guard blocks it if you ever try to add it.[2]
The fork you now hold contains plain-text primitives the agent reads directly: 20 role definitions across 6 departments, 18 rule files, about 50 enforcement hooks, 23 sub-agents, 66 slash-command skills, document templates, and 8 ready-made CI pipelines.[2][3] The two published hook counts disagree slightly — the site says 51, the repo README says 49 — because the number moves as releases land.[1][2] Part 11 tours each layer.
Naive v1: the agent ships straight to main
Part 4 · Naive v1
Before the gates, we ship the way v1-era LinkLoft always shipped. The waitlist wants CSV export of link analytics, so we type one prompt.
Add CSV export to LinkLoft's reports page. Ship it.
The agent writes an exporter, an endpoint, and a download button. It commits to main, the pipeline deploys, and the feature is live in twenty minutes. No ticket, no review, no sign-off — and it works.
Celebrate that honestly. For a demo, this speed is the whole point. The trouble starts because LinkLoft now has customers, and the same twenty-minute path is open to every future change, good or bad.
Where v1 fails
Part 5 · The failures
A month of v1-style shipping produces five concrete failures. Each maps to a piece of the framework, and the rest of the tutorial removes them in order.
Failure 1: the self-approved merge. The agent that wrote the CSV exporter also judged it fine. No independent eyes read the diff, so a subtle escaping bug reached customers. Author and reviewer were the same context.
Failure 2: the vanished decision. The agent chose a CSV library silently. Two months later, a second product needs the same choice, and nobody can say why the first one was made. The decision lived in chat history, and the chat is gone.[2]
Failure 3: the Friday migration. "Just drop a column, can't be that hard." The column fed a nightly analytics job. There was no rollback plan, because nothing demanded one, and the 2am Saturday incident followed.[1]
Failure 4: the invisible portfolio. Three products, three GitHub tabs, three mental models. A stale PR sat unnoticed for two weeks, and a P1 bug sat unassigned, because no single screen showed everything waiting on you.[1]
Failure 5: the unlaunchable launch. LinkLoft's public launch slipped a week, because security, performance, and accessibility problems surfaced from user emails instead of from any check you ran first.[1]
Hold these five. Every upgrade below closes one of them — and closes it with a script, not a reminder.
Upgrade 1 — The ops repo: one fork to govern them all
Part 6 · The ops repo
The first upgrade wires your real projects into the fork. Two skills are the entry points, depending on what you walk in with.[1]
/handover: adopt an existing project
LinkLoft already exists, so it enters through /handover. The skill clones the repo, scores its "harnessability" across five dimensions, seeds the project's docs, generates a starter architecture diagram, and registers it in apexyard.projects.yaml — the one-file registry that lists every product under management.[1][2] You run it once per repo:
/handover https://github.com/you/linkloft
/handover https://github.com/you/billing-api
/handover https://github.com/you/marketing-site
You rarely touch the registry by hand; skills maintain it.[2] Live clones of managed projects sit under workspace/ (gitignored), while each project's committed docs — readme, roadmap, decision records, updates — live under projects/<name>/.[3]
/idea: capture a new concept before it evaporates
The other entry point is for products that do not exist yet. /idea captures a new concept to a shared ideas backlog, optionally as a GitHub issue, so a good thought survives the session it occurred in.[1]
The fork is now your ops repo. LinkLoft, the billing API, and the marketing site are registered in apexyard.projects.yaml, each with seeded docs and an architecture stub. Nothing is governed yet — that starts next.
Upgrade 2 — The governed ticket: phases and gates
Part 7 · The SDLC
ApexYard's core is a strict software development lifecycle (SDLC): every change moves through named phases, and a gate — a mechanical check — sits between phases.[4][5] We rebuild the CSV export properly, this time for the billing API too, to walk the spine.
flowchart LR
P["Planning"] --> D["Design"] --> B["Build"] --> R["Review"] --> Q["QA"] --> DE["Deploy"] --> M["Monitor"]
Ticket-first: no edit without a ticket
The foundational rule is ticket-first: every edit needs a real ticket behind it, and a hook — require-active-ticket.sh — blocks file writes until one exists.[1][5] The hook watches Edit, Write, and even Bash commands that write through redirection, so there is no side door.[5] You attach a session to its ticket with /start-ticket, which labels everything that follows.[1]
Planning skills create the tickets: /feature for a feature, /bug for a defect, /task for a chore, /tickets-batch to break a plan into many, and /write-spec plus /plan-initiative for the larger documents above them.[3] Every ticket carries acceptance criteria, and later phases verify against exactly those criteria.[1]
Decisions get records, mechanically
Failure 2 dies here. When the agent makes a significant choice — a library, a pattern, an architecture — the decision is captured as an AgDR (Agent Decision Record: the AI-era counterpart of an Architecture Decision Record) via /decide or /agdr.[2][3] This is not a suggestion: a hook flags architectural PRs that carry no decision record, so the reasoning is inherited along with the code.[1][2]
The gates, end to end
The workflow-gates rule file names six gates and their checks.[5] Gate 2 blocks Design→Build until the design is approved and key decisions have AgDRs. Gate 4 blocks PR creation until tests pass and coverage exceeds 80%. Gate 5 blocks the merge until two reviews exist — agent and human — and the reviewed commit SHA matches what is actually merging. Gate 6 keeps the ticket out of "Done" until QA verifies the result in a separate pass.[5] One more rule rides along: one ticket at a time, one ticket per PR.[5]
For the billing API's CSV export, the flow reads: /idea → /write-spec → /decide → /start-ticket → build → PR → review → QA → Done.[1] On merge the ticket moves to QA state, not Done — a QA pass on staging checks each acceptance criterion before anything closes.[1]
CSV export shipped again — this time behind a ticket with acceptance criteria, an AgDR recording the library choice, an 80%-coverage gate, and a QA pass that verified the escaping bug case explicitly. Failures 1 and 2 are wounded; the next part finishes them.
The first week under the gates feels slower, because it is: the framework refuses shortcuts you used to take. The trade is deliberate — minutes at the gate against the hours the Saturday incident costs. If a gate fails, the rule is one line: stop, complete the missing step first.[5]
Upgrade 3 — The review layer: Rex, roles, and the two-marker gate
Part 8 · Review
Failure 1 was author-as-reviewer. ApexYard separates the two by construction: the agent that writes code and the agent that reviews it run in separate contexts, so an author cannot quietly approve its own work.[1]
Rex: a real review on every PR
Rex is the code-reviewer sub-agent that runs on every pull request.[2][3] Rex reads what changed and how the rest of the system reacts to it — not just the diff — flags missing tests, surfaces decisions made silently, and refuses to approve work that bypasses the team's standards.[1] The approval Rex writes is bound to a commit SHA, so pushing new code invalidates the previous approval; nothing slips in after the review is done.[1][2]
The two-marker merge gate
A merge needs two markers, and a hook — block-unreviewed-merge.sh — keeps the gate shut until both exist:[1][6]
- Rex's approval, bound to the exact SHA being merged.
- A named human's per-PR approval, given through
/approve-merge. A plan-level "go" does not count; saying yes to a six-step plan does not authorize the merge buried inside step four.[1]
The framework demonstrated this gate on itself, in public. PR #787 — a change to ApexYard's own trust chain — was blocked at first merge attempt for missing Rex approval, then triggered a mandatory security review because the diff touched the hook wiring, and merged only after both reviews plus the maintainer's by-name approval.[1][7] Every line of that replay links to a checkable commit.[1]
Roles that activate on triggers
Reviewers change with the territory. ApexYard ships 20 role definitions across 6 departments — engineering, architecture, product, design, security, data — and a role activates when its trigger condition fires, rather than sitting in context all day.[3][8] A PR touching auth code fires the Security Auditor (the sub-agent Hakim); a design artifact ready for review fires the Solution Architect (Tariq), whose sign-off is itself a gate before Build; a ticket entering QA state fires the QA Engineer.[3][8] Loading all 20 roles constantly would cost roughly 23k tokens, so activation-on-trigger is also a context-efficiency decision.[8]
Alongside the reviews sit the review skills you can invoke directly: /code-review, /security-review, /design-review, and /challenge, which asks the contrarian agent (Naqid) to argue against your plan.[3]
Hooks are the enforcement, not the suggestion
Everything above holds because rules ship as code. Four named examples, each a small script in .claude/hooks/ with public source:[1] block-main-push.sh stops any direct push to main, human or agent; warn-review-marker-write.sh catches a build agent trying to forge its own approval marker; block-unreviewed-merge.sh is the two-marker gate itself; require-agdr-for-arch-pr.sh flags architectural changes with no decision record.[1]
Every LinkLoft PR now gets an independent, SHA-bound review; merges need Rex plus your named approval; auth-touching diffs pull in a security review automatically. Failure 1 is gone — mechanically, not culturally.
Upgrade 4 — High-blast-radius work: migrations, audits, and throwaways
Part 9 · Risky work
Some work carries more blast radius than a normal feature. The framework treats three kinds specially: database migrations, launches, and deliberately throwaway code.
The migration gate
Failure 3 was the Friday column drop. Under ApexYard, the edit itself is blocked: any change to migration paths — prisma/schema.prisma, **/migrations/**, and similar — requires a ticket labelled migration plus a matching migration AgDR that documents the rollback plan, estimated downtime, cross-service consumers, data volume, testing plan, and observability.[4][5] The /migration skill produces both artefacts in one guided flow, asking each question in turn; the require-migration-ticket.sh hook refuses schema edits until they exist.[4][5] In the site's own walkthrough, the reviewer then catches the nightly analytics job that reads the doomed column — the downstream system you forgot.[1]
/launch-check and the audit family
Failure 5 was the surprise-email launch. /launch-check is one command that reports what is still missing before customers see the product — security, performance, accessibility, the lot.[1] It heads a family of focused audits you can run any time: /threat-model, /accessibility-audit, /performance-audit, /monitoring-audit, /compliance-check, /seo-audit, /analytics-audit, /docs-audit, /audit-deps, and /mutation-test.[3]
Spikes, prototypes, and walking skeletons
Early-phase work gets its own taxonomy, so a throwaway never quietly becomes production.[4] /spike answers "will this technically work?" in one to three days, throwaway by default, closed with /spike-close to either promote a fresh feature ticket or write a memo so future-you does not re-explore the same ground.[4] /prototype answers "what should it look and feel like?", equally throwaway, closed with /prototype-close.[4] /walking-skeleton is the exception: the thinnest end-to-end slice through every layer, built under the full SDLC with no exemptions, because it is kept — you grow the product on top of it.[4] Spikes and prototypes are exempt from the AgDR and coverage gates; Rex and the security auditor still apply.[4]
The billing API's schema change shipped with a rollback plan, a downtime estimate, and its consumers listed — before the edit was even allowed. LinkLoft's launch ran /launch-check first, and the findings arrived from a command, not a customer. Failures 3 and 5 are gone.
Upgrade 5 — The portfolio: one inbox, many products
Part 10 · Portfolio
Failure 4 was three products in three tabs. The registry from Upgrade 1 now pays off: a set of portfolio skills reads it and aggregates across everything you manage.[1][2]
/inbox is the Monday-morning command: one screen listing, per project, the PRs awaiting review, approvals pending on you, stale PRs, and unassigned P1 bugs.[1] /status, /tasks, and /projects slice the same registry by health, by open work, and by product.[2][3] /roadmap plans across the portfolio, and /fan-out pushes one change across many projects at once.[3]
Two more close the loop with the humans around you. /stakeholder-update weekly drafts the Friday rollup from the week's actual activity — real merges and real tickets, not what you remember.[1] And /update keeps the fork itself current: the session-start banner flags the moment you are behind upstream/main, and the skill previews the incoming diff, merges on a dedicated sync branch instead of hand-patching your main, then walks each release's migrations in order.[1][2]
Monday starts with /inbox, not with tab archaeology. The stale PR and the unassigned P1 surfaced on day one. Friday's stakeholder update writes itself, and the fork syncs upstream with one governed command. Failure 4 is gone — all five are.
The layers underneath: roles, rules, hooks, agents, skills
Part 11 · The layers
Everything above runs on five plain-text layers the agent reads directly. There is no runtime and no service; hooks fire on your git and gh commands.[3] Knowing the layers tells you where to look when you want to tune the framework to your team.
flowchart TD
S["66 skills
slash-command workflows"] --> R["18 rule files
imported from CLAUDE.md"]
S --> A["23 sub-agents
Rex, Hakim, Tariq, Naqid, personas"]
A --> RO["20 roles / 6 departments
activate on triggers"]
R --> H["~50 hooks
mechanical enforcement on git/gh"]
Roles (20, across 6 departments) define identities the agent adopts on trigger: seven engineering roles from Backend Engineer to SRE, plus architecture, product, design, security, and data departments, each role with its own responsibilities and CAN/CANNOT constraints.[3][8]
Rules (18 modular files under .claude/rules/) are imported from CLAUDE.md and cover everything from git conventions and PR quality to role triggers and workflow gates.[2][3]
Hooks (about 50 shell scripts under .claude/hooks/) are the mechanical layer: ticket-first edits, the migration gate, the two-marker merge gate, a red-CI block, secrets scanning, branch and PR-title validation, leak protection, and the upstream-drift banner.[2][3]
Agents (23 sub-agents under .claude/agents/) include the named utility agents — Rex the code reviewer, Hakim the security auditor, Tariq the solution architect, Naqid the contrarian, a dependency auditor — plus one persona per department role.[3]
Skills (66 slash commands under .claude/skills/) are the workflows you actually type, from /setup to /stakeholder-update; the full one-line-per-skill list lives in CLAUDE.md.[2][3]
Two supporting layers round out the box. Templates cover the PRD, technical design, ADR, AgDR, migration AgDR, and C4 architecture diagrams (Context and Container levels, as Mermaid; the /c4 skill generates stubs from a codebase).[2][3] Golden paths ship 8 drop-in GitHub Actions pipelines — combined CI, code quality, Swift CI, security scanning, dependency audit, PR-title check, review check, and SEO check — you copy into any project's workflows folder.[3]
Situation → command: the quick map
Part 12 · The map
The framework ships 66 skills; this table maps the situations this tutorial covered to the commands that handle them.[1][2][3]
| Situation | Command |
|---|---|
| Fresh fork, first run | /setup |
| Adopt an existing repo into the portfolio | /handover |
| Capture a product idea before it evaporates | /idea |
| Start work on a ticket | /start-ticket |
| Create a feature / bug / chore ticket | /feature · /bug · /task |
| Break a plan into many tickets | /tickets-batch |
| Draft the spec or the initiative above it | /write-spec · /plan-initiative |
| Record a significant decision | /decide · /agdr |
| Review code / security / design | /code-review · /security-review · /design-review |
| Argue against your own plan | /challenge |
| Give the named human merge approval | /approve-merge |
| Sign off a design or architecture artifact | /approve-design · /approve-architecture |
| Touch the database schema | /migration |
| Check readiness before customers see it | /launch-check |
| Run a focused audit | /threat-model, /performance-audit, /accessibility-audit, … |
| Answer "will this technically work?" | /spike, then /spike-close |
| Answer "what should it look like?" | /prototype, then /prototype-close |
| Wire the thinnest kept end-to-end slice | /walking-skeleton |
| Monday morning, where do I look? | /inbox |
| Portfolio health, open work, product list | /status · /tasks · /projects |
| Plan or push change across products | /roadmap · /fan-out |
| Friday rollup for stakeholders | /stakeholder-update weekly |
| Fork behind upstream | /update |
| Generate architecture diagrams | /c4 · /dfd |
Checklist and where to go next
Part 13 · Conclusion
Your AI-built code travels the last mile safely when all of this is true:
- Your fork of ApexYard is the ops repo, with
upstreamwired and/setuprun once.[1][2] - Every product you manage is registered via
/handoverand shows in/inbox.[1] - No edit happens without a ticket, and no ticket closes without QA.[1][5]
- Every merge carries two markers: Rex's SHA-bound approval and your named
/approve-merge.[1][6] - Significant decisions land as AgDRs, enforced on architectural PRs.[2][5]
- Schema edits are impossible without a migration ticket and a rollback-documented AgDR.[4][5]
- Launches run
/launch-checkbefore customers run the product.[1] - Throwaway work is labelled a spike or prototype and closed with a disposition.[4]
- The fork syncs upstream through
/update, never by hand-merging main.[1][2]
Read next, in this order:
References
- ApexYard — official site and homepage walkthroughs.
- me2resh/apexyard — the source repository README (MIT).
- What's Inside ApexYard — full component breakdown.
- Software Development Lifecycle — workflows/sdlc.md.
- Workflow Gates — .claude/rules/workflow-gates.md.
- block-unreviewed-merge.sh — the two-marker merge gate hook.
- PR #787 — a trust-chain change merged under the framework's own gates.
- Role Triggers — .claude/rules/role-triggers.md.
- How it works — a day with ApexYard, in plain English.