Internal · AI Handbook

How we build software with Coding agents.

What the agent decides and what you decide, the pipeline a feature travels from an idea to the archive, the guardrails that make agent-written code safe to merge, and the rules we wrote after paying for a mistake.

  • 49 skills
  • 10 hooks, 5 that stop you
  • 1 gate: verify.sh
  • 58% of commits co-authored by Claude
  • ~20 sessions a day
  • Read time ~22 min
01 · Why this exists

AI-native by default, inside the repository.

The team handbook has a value that says: "Any process an agent can run, an agent runs. Humans stay in the loop only where their judgement is irreplaceable." This page is what that value looks like in the code we ship every day.

The agent is exactly as good as what we wrote down. A rule that lives in someone's head does not exist for the agent. A rule that lives only in prose loses to the code sitting next door.

So the real product of our engineering process is the harness around the model: the context files, the skills, the hooks and the one gate. Code is what falls out of it.

02 · The stance

Eight operating rules.

Each one has a consequence you will feel in your first week.

01

The agent finds facts. You take decisions.

It dispatches a read-only sub-agent to learn what the code does instead of asking you, and it never picks an architecture on your behalf. It puts the choice in front of you, with a recommendation.

02

Verify before asserting.

Never state that code is dead, missing or absent from one grep or a partial read. Two independent signals before a claim goes into a PRD, a doc or a Linear issue. Below high confidence, write "unverified".

03

A written rule is step one. An executable guard is step two.

The component-folder rule sat in CLAUDE.md and was still broken in 58 of 80 components, because copying the neighbour got it wrong three times in four. It became a hook that denies the write.

04

One gate, one command.

scripts/verify.sh runs format, typecheck, test and lint, in that order, and stops at the first red. The end-of-turn hook runs the same Nx target, so green from the hook means what green from the gate means.

05

Tracer bullets, not layers.

Every issue is a narrow but complete path through every layer. "A slice can pass its own tests, typecheck clean, and still be wired to nothing." So the verifier runs the behaviour, not only the suite.

06

Green suites are not done.

Three features shipped with green tests and were refuted by one live run. A feature moves to Done when every user story in its PRD passed in a real browser.

07

Do not over-engineer.

DRY is not over-engineering; it is how code is written. Four people, an MVP. Nothing is in production yet, so backwards compatibility is free: no flags whose only job is to protect stored data.

08

Every hard lesson becomes a file.

The agent writes a memory the same day, with a Why and a How to apply. Every guard script carries its own post-mortem in the header: the incident, the measurement, what it cost.

03 · The lifecycle

A feature travels one pipeline.

Every hop is a skill you invoke with a slash, and each skill names the next one at the end of its own instructions.

  1. /grill-mvp

    Think until every branch of the design is decided.

  2. /to-prd

    The PRD becomes a Linear Feature issue on the Platform team. Its description is the PRD.

  3. /grill-prd

    Every factual premise checked against the code by parallel read-only agents; the PRD is rewritten.

  4. /to-issues

    Tracer-bullet sub-issues, created in dependency order.

  5. /implement-feature

    Scouts brief each slice, implementers ship one commit per slice, a verifier reruns everything, then a live E2E smoke.

  6. /code-review --fix

    Review findings applied to the working tree.

  7. /close-feature

    Simplifier pass, one browser acceptance flow per user story, pre-close checks, Feature to Done, PR ready for review.

  8. /archive-completed-feature

    docs/archive/<slug>.md plus one line in docs/HISTORY.md.

Stage by stage

Stage Skills What comes out
Think
grill-me grill-mvp grill-with-docs interview zoom-out prototype
A decision list; sometimes an ADR or a docs/CONTEXT.md entry
Specify
to-spec to-prd grill-prd to-summary
A Linear Feature issue whose description is the PRD
Break down
to-issues triage audit-issue
Sub-issues under the Feature issue, labelled afk or hitl
Implement
implement-feature implement-issue tdd diagnose angular-* (11) resolving-merge-conflicts
Commits on feat/<slug>; sub-issues moved to Done by a verifier
Improve
improve improve-codebase-architecture code-simplifier agent /code-review
Plans in plans/, applied simplifications, review findings
Close
close-feature archive-completed-feature handoff
Feature Done, PR ready, an archive file, a handoff doc
Write
sync-stackbone-wiki sync-stackbone-skills audit-wiki stop-slop humanizer write-a-skill
Wiki pages, published skills, prose that reads human
04 · Think before you build

Design work starts as a conversation the agent runs.

/grilling walks a design tree in rounds. It asks the whole open frontier at once, numbered, each question with a recommended answer already attached, and it dispatches a sub-agent to read the code whenever the answer is a fact rather than a preference. /interview is the same tree one question at a time. /grill-mvp adds an anti-over-engineering lens; /grill-with-docs writes the outcome into ADRs and the glossary; /zoom-out maps the modules and callers before you decide anything.

The output is never prose. It is a numbered list of decisions with file and line. The architecture grilling of 2026-07-15 closed with 9 decisions; the Studio Copilot grilling of 2026-08-06 closed with 15 plus an ADR.

How the agent is allowed to ask you

These rules came from corrections, and they hold in every skill.

  • Questions go through the AskUserQuestion tool, never as plain text.
  • Decision questions carry no code jargon: three to five plain sentences with a domain example first, options described by their observable consequence. Evidence with file:line goes in a findings table before the question, not inside it.
  • Never cite a section number or a decision code back at us. "The wrapper doc says X, but the starter doc says Y", never "PRD_1 §3.7 contradicts PRD_2 §2.1".
  • An architectural choice arrives as a pros and cons table per option first, then the question.
  • The agent gives the design that is best on the merits even when it contradicts our own written rules, says where it diverges, recommends one, and lets us choose.
  • If you interrupt with a clarifying question, the agent answers it in prose and ends the turn. Re-asking in the same message reads as being railroaded.
  • Explain like an engineer: image versus container, bind mount, HMAC. An analogy is at most one line.
Prototypes

/prototype builds something you throw away: a runnable terminal app when the question is about state or business logic, or several radically different UI variants toggleable from one route when the question is visual. You capture the answer, then delete it.

Where decisions land

docs/CONTEXT.md is a glossary and nothing else. An ADR is written only when all three hold: hard to reverse, surprising without context, a real trade-off. ADRs live in docs/adr/, in present tense as the current state; when a topic accretes we fuse it into one ADR and delete the dead ones. That consolidation took the folder from 40 files and 10,200 lines to 20 and 3,500 in June 2026, so the agents read less and hit fewer contradictions.

05 · From PRD to issues

The PRD is a Linear issue. Then it gets grilled against the code.

/to-prd

Synthesises the conversation into one issue on the Platform team with the Feature label; the description is the PRD. It never writes it to disk and it does not interview you (that already happened). Before creating anything it asks for status and priority, and never offers Done or Canceled.

/grill-prd

Extracts every factual premise from the PRD, fans out parallel read-only agents to check each one, and reports premise, verdict, evidence and impact before rewriting the issue. Its own rule: "Never assert code is missing without two independent signals." This step earns its cost. On one PRD, 66 premises were audited: 41 held and 17 changed scope. On the browser-deployment design, six of the ADR's claims fell before a line was written.

/to-issues

Cuts the PRD into tracer-bullet vertical slices: each one a narrow, complete path through schema, service, contract and UI. Wide refactors are the exception and go out as expand and contract batches. It creates the sub-issues in dependency order so blocked-by links use real identifiers, labels each afk (an agent can run it alone) or hitl (a human decides something inside it), and quizzes you on granularity before publishing.

Before implementing, triage

Given an issue id, the agent first checks whether it is a parent PRD with sub-issues, whether it carries the Feature label, and whether it is blocked by unmerged work. If so it reports that in one message with the sub-issue list or the blocker, and proposes a default next action instead of opening a question prompt.

For readers who are not engineers: /to-summary renders a PRD as a self-contained HTML brief and /eval-me scopes a client job into three buckets: already in the platform, to build, third-party to configure.

06 · Implementing

One worktree per feature. Two workflows that do not improvise.

Never git worktree add by hand. scripts/wt-new.sh <issue-id> branches off a freshly fetched origin/next, prints the absolute toplevel to cd into, clears the sparse-checkout a plugin leaks into every new worktree, copies the git-ignored .env, and reports the port offset. Each worktree gets its own Postgres database, its own Redis logical DB and its own api/web/studio ports on the one shared docker infra, so two features run side by side.

The implement workflows

/implement-feature and /implement-issue each drive a fixed script in .claude/workflows/.

Preflight Branch feat/<slug>, clean tree, record baseSha and the absolute worktree root, merge next first. Two silent drifts get checked by hand because there is no textual conflict to warn anyone: a migration prefix collision and a recreated named DDL constraint. The skill confirms the agent count with you ("6 slices = 14 agents").
Explore One read-only scout per sub-issue, in parallel, on a smaller model, anchored on the worktree root. Each returns a brief: files (repo-relative only), what to reuse, approach, risks.
Implement Sub-issues run in sequence. Per slice: an implementer and a verifier at xhigh effort, up to three attempts. Every attempt starts with git reset --hard to the last verified commit, so a failed try leaves nothing behind. A failed implementer skips its verifier.
Verify Clean tree; exactly one conventional commit since the last verified one; typecheck and the full suite per touched project, one project at a time; then run the behaviour (a scratch script under /tmp, or curl), never only the tests. Only the verifier flips the Linear sub-issue to Done.
E2E Two agents on purpose: stack-runner (no browser tools) brings the local stack up and leaves it running; a second agent drives Playwright against it.
Review /code-review --fix over the branch. Then you run close-feature.

Cheap eyes, expensive hands: scouts run on a smaller model at medium effort, implementers and verifiers on the session model at the highest. Nothing pushes. Nothing uses --no-verify. The workflows throw a sentence if invoked without the skill's preflight args, so a bare slash-autocomplete fails loudly instead of running unprepared.

TDD, the vertical way

/tdd is passed verbatim into both workflows. Its named anti-pattern is horizontal slices: "do not write all tests first, then all implementation". The inner loop runs one spec file; the whole project runs once at the end. State only moves forward.

The inner loop is t.sh

nx test <project> -- <pattern> does not filter; the whole project runs, silently (studio still ran all 92 files, 1,233 tests). scripts/t.sh takes one spec from 30.1 s to 2.2 s, and every run lands in tmp/t/, so you grep the log instead of re-running a suite to read its own failure.

Diagnosis has a skill

/diagnose refuses to fix anything until it has a feedback loop it believes in: "This is the skill. Everything else is mechanical." Then three to five falsifiable hypotheses before testing any, tagged debug logs, a regression test and a post-mortem.

Frontend has skills, backend has rules. Every Angular change goes through the eleven angular-* skills. Every HTTP-contract change goes through orval-api-flow: the generated client under libs/contract/ is never hand-edited, and a hook denies the write. UI changes are verified in a real browser through the Playwright MCP tools, logged in as the seeded demo user.

07 · The gate and the guardrails

scripts/verify.sh before every commit.

Phase Command Why it sits here
format pnpm format:check Cheapest phase (~35 s against test's ~140 s) and the only one whose fix is mechanical: pnpm format, nothing to decide.
typecheck pnpm typecheck Most informative diagnosis. Angular projects run ngc, not tsc, because tsc never reads an .html template and one merge proved it.
test pnpm test A red test phase is re-run project by project in isolation before it is believed; isolated green means the parallel run was flaky.
lint pnpm lint Closes the gate. ESLint already ran on every edited file through a hook, so it rarely says anything new.

Four things the script does that typing the commands does not: it runs from the toplevel of the checkout it lives in and prints it, so a stray cd cannot verify the wrong checkout; it never mixes Nx targets in one run, because that starves the vitest workers and studio's specs die on timeout; it retries a red test project alone before reporting; and it scans every phase log for a "failed" line, because Nx can print that and still exit 0.

The hooks

Nine scripts in .claude/hooks/ plus one guard inline in settings.json. Every deny is a permission decision, never a crash, and the message names the correct path.

Hook Fires on Verdict Protects
orval guard edit of a generated file denies libs/contract/** is rewritten by pnpm api-client:generate; the barrel src/index.ts stays hand-edited.
guard-migrations.sh edit of a committed .sql denies The migrator hashes each body; a changed one trips MIGRATIONS_DIGEST_MISMATCH on every database that ran it. Untracked migrations stay editable.
guard-component-folder.sh write of a *.component.* denies new / warns A new component outside a folder named after it. Existing debt only gets a reminder; it migrates when touched.
guard-git-push.sh any git push asks A push is outward-facing. "Leaving that to the model means it holds only as long as the instruction stays in context; a hook holds always."
contract-dirty.sh edit of a controller or DTO warns Tells you the generated client is now stale, right after the edit while the change is still in view.
lint-changed.sh every .ts edit warns ESLint on the file, cached (~1.2 s).
record-touched.sh every .ts edit records Notes the path per session and per sub-agent for the end-of-turn gate.
typecheck-touched.sh end of turn, end of sub-agent blocks One nx affected -t typecheck over what the turn touched. Per-edit it cost ~32 s a pop and fired inside every sub-agent, so it moved to per-turn.
always-on.js, repo-context.sh session start injects context Prints the resolved toplevel, worktree-or-main, branch and dirty-file count, so a linked worktree is never mistaken for the main one.
Commits and pull requests

Conventional commits enforced by commitlint, lowercase, with a behaviour sentence: fix(studio): make Deprovision read as destructive in the overflow menu. Husky runs Prettier and eslint --fix on staged files. Commits made with the assistant carry Co-Authored-By: Claude. PR bodies lead with a Verification section. The agent never pushes without an explicit yes; that is both a global rule and a hook.

CI

The pipeline runs a dead-reference guard, nx affected -t lint test build, the wiki e2e suite, one browser suite against a real Postgres, Redis, MinIO and Mailpit, and a Docker smoke build of the api. Two more workflows are AI: the same /code-review skill runs unattended on every pull request, and any issue or PR comment that mentions @claude gets answered, with permission to read CI results.

08 · Closing a feature

Nothing moves to Done on a feeling.

/close-feature

First it runs the code-simplifier agent over the whole diff, before QA, so what gets tested is what gets merged. Then it brings the local stack up and walks one Playwright acceptance flow per user story in the PRD. A failure is triaged with you as a bug or a gap: a bug enters a fix loop of at most three attempts, and if the same error appears twice in a row the loop exits early instead of spending the third. Gaps accumulate. No bypasses: if a test fails because of a guard, the guard stays. Pre-close checks run test, lint and typecheck plus the web e2e suite. If everything passes, the Feature issue moves to Done, the PR opens ready for review (the push happens only after you confirm) and the archive skill runs. If gaps remain, the Feature stays open and /to-issues creates the gap sub-issues.

/archive-completed-feature

Refuses unless the issue is Done. It writes docs/archive/<slug>.md, a dense historical summary of what shipped and what was decided, and appends one line to docs/HISTORY.md, which is append-only and ascending by close date.

Tell the business

When a feature ships or a bug with an issue is fixed, a Linear activity update goes out in English, short, written for a business manager, with links to the issue or PR. Technical detail stays in the archive file.

Hand off

/handoff compacts the conversation into a document for the next session, referencing artefacts by path or URL instead of duplicating them, and suggests which skills to run next.

09 · Context is the product

The model changes; the context files stay.

This is where a new rule goes, in escalating order.

Level Where When to use it
A lesson or a fact The agent's memory dir (feedback_* and project_* files, indexed in MEMORY.md); the daily .remember/ log Written the same day, with Why and How to apply. Pruned too: 238 to 153 files in the 2026-08-13 audit.
A rule for this repo Root CLAUDE.md, plus 11 scoped ones (apps/web, apps/wiki, apps/landing, libs/ui/components, libs/ui/modals, libs/ui/toast, five demo/*) Anything every session must know: library layout and Nx scope tags, the design-system index, the modals footer rule, the wiki audience rules.
A rule for every repo ~/.claude/CLAUDE.md Spanish in chat, English in code and docs; never push without confirmation; questions through AskUserQuestion; no section IDs; the barrel-file policy.
A repeatable process .claude/skills/<name>/SKILL.md, configured through docs/agents/*.md Written with /write-a-skill. Skills read their paths and labels from docs/agents/ instead of hard-coding them.
A rule that keeps breaking .claude/hooks/*.sh The incident and its measurement go in the header comment. hookify can draft one from a conversation.
An invariant ESLint (module boundaries with six scope tags, no cycles, no export *) and the gate Things that must hold with nobody watching.

Two more pieces of context every session gets. docs/CONTEXT.md is the glossary and product model, and skills speak in its vocabulary. And the assistant runs in the Explanatory output style, so it explains the why of what it does while it works.

10 · Writing with AI

Most of what the agent produces in a day is prose.

Wiki pages, PRDs, Linear updates, ADRs, commit messages. The rules:

No slop

stop-slop is loaded for anything a human will read. No "delve", "leverage", "robust", "seamless"; no bold-lead bullet lists as filler; no rule-of-three padding; no em dashes; active voice; two items beat three; score five dimensions and revise below 35 of 50. /humanizer runs a second pass.

The wiki is for readers with no clone

A page that mentions pnpm, nx or a repo path is drift to fix, not a style nit. Once a PRD is accepted, its page is written in present tense as the finished state, no "coming soon". The whole site "should feel like one careful engineer wrote it by hand, talking to a peer who is smart but new to Stackbone".

Sync and audit

/sync-stackbone-wiki diffs every CLI command and SDK surface against the pages and applies the edits. /audit-wiki is a multi-agent workflow: an audit agent per batch, a stop-slop agent per batch with headings frozen, then five validation gates that stop on the first red.

Screenshots come from a real stack

A wiki page's images are captured with Playwright scripts against a populated local demo workspace, not mocked. Some need tricks the memory files record, like a fake TLS box for a wizard that rejects anything not https.

11 · Lessons we paid for

Each of these is a memory file. The method matters more than the bug.

  1. 01

    A defect only the live smoke saw.

    An optional handoff was dropped by a conditional spread, so no type named it and no test fired. Every chat turn got blocked, including "what is the capital of Portugal?". Types green, suites green. An optional cross-module handoff needs its own test.

  2. 02

    Five green slices, zero rows in the database.

    The spec hand-fabricated metadata the runtime never emits. Without the E2E phase the feature would have been called done, broken.

  3. 03

    Two PRD decisions, each true alone, false together.

    Publishing a prompt never reached a live agent. Measured, not deduced: published "answer exactly BANANA", the agent answered "Hello!".

  4. 04

    A green test that measured other people's traffic.

    The pool-isolation test counted lock waits across the whole shared database. CI removed the noise and it failed five of six runs. Ask which database a concurrency test runs against and who else writes to it.

  5. 05

    The mock spent itself between passes.

    A 37-hour mock server mutated fixtures in memory and never wrote to disk, so each eval pass consumed the world. The proof: the only intact record was the one the mock protects from mutation.

  6. 06

    Filter by what a thing is, not by why it happened.

    A reconciliation sweep keyed on trigger='workflow' silently stopped closing eval runs: 141 done versus 0, and each case burned its full ten-minute timeout.

  7. 07

    A field added in four of five hand-written places writes NULL and returns 200.

    Miss one and there is no red anywhere; only reading the database afterwards shows it.

  8. 08

    Specs can assert a shape the browser never sends.

    Nineteen Studio specs threw errors without a status field, so the mapper skipped every status branch. Injecting the real status kept eighteen green and turned one red: a real bug.

  9. 09

    A sed ate the space between two Tailwind classes.

    In 35 places across 14 files, and Prettier, ESLint and the build stayed silent for months. Mass moves rewrite imports by resolving each specifier, never by sed; unused imports are decided from the ESLint report, never by searching the name in the text.

  10. 10

    Do not believe a red that came out of run-many.

    Studio's specs starve of CPU next to typecheck and lint and fail on timeout. verify.sh now retries in isolation for you.

  11. 11

    A four-way tie at 75 beat the threshold.

    Four independent review lenses converged on the same finding and none scored it 80. Convergence beat the number; the multi-agent review cut moved to 60, and the top two survivors were real production bugs.

  12. 12

    Migration immutability includes the comments.

    A review commit rewrote three lines of prose in a migration header and the emulator stopped booting with "previously applied with a different checksum". Reverted byte for byte; now a hook denies the edit.

12 · House rules

The short list.

Everything above, compressed.

  1. 01 Grill before you build. Decisions come out as a numbered list with evidence.
  2. 02 The PRD is a Linear Feature issue on Platform. Sub-issues are tracer-bullet slices in dependency order.
  3. 03 Two independent signals before you say something is missing. Otherwise write "unverified".
  4. 04 One worktree per feature via scripts/wt-new.sh. Repo-relative paths in every brief.
  5. 05 scripts/t.sh while you work; scripts/verify.sh before you commit. Never the four commands by hand.
  6. 06 Do not re-run a suite to read its own failure. Grep tmp/t/ and tmp/verify/.
  7. 07 A written rule beats the neighbouring files. If the local pattern contradicts the rule, say so.
  8. 08 Never push without an explicit yes. Never --no-verify. Never comment out a guard to make a test pass.
  9. 09 Never hand-edit libs/contract/** or a committed migration. A hook will stop you; the message tells you the right path.
  10. 10 New Angular component: its own folder, .html template, CVA, DaisyUI semantic tokens, a row in libs/ui/components/CLAUDE.md.
  11. 11 Verify UI in a real browser (Playwright MCP), logged in as placeholder user in Stackbone Demo.
  12. 12 Questions through AskUserQuestion, in plain language, with the trade-off table first and no section IDs.
  13. 13 Answer a clarifying question, then stop.
  14. 14 Chat in Spanish; code, docs and commits in English; Linear updates in English for a business reader.
  15. 15 No slop in anything a human reads. No em dashes.
  16. 16 Done means every user story passed in a browser. Then archive it and tell the business.
  17. 17 Write the lesson down the same day, with a Why. If it keeps breaking, make it a hook.