Phase 2 (Development), step 5 of product-playbook — the per-feature build loop: ONE feature at a time, a definition-of-done that INCLUDES security, reuse before writing, verify the LIVE path, review the diff, write the feature doc. Use to implement features, or run /build "build feature X", "implement", "add the feature". Appends to PRODUCT.md#Build log + writes docs/features/<feature>.md. Composes /run and /code-review. Run /dev-check when all core-scope features are done.
Installs into .claude/skills of the current project.
Are you the author of Build?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/kish21-build)
---
name: build
description: >
Phase 2 (Development), step 5 of product-playbook — the per-feature build loop: ONE feature at a
time, a definition-of-done that INCLUDES security, reuse before writing, verify the LIVE path,
review the diff, write the feature doc. Use to implement features, or run /build "build feature X",
"implement", "add the feature". Appends to PRODUCT.md#Build log + writes docs/features/<feature>.md.
Composes /run and /code-review. Run /dev-check when all core-scope features are done.
---
# `/build` — Phase 2 · Development ⑤ · run as an **engineer**
> Part of **product-playbook**. Reads + updates the project spine (`PRODUCT.md`, or the project's existing docs — resolve per MECHANISMS.md §Spine resolution).
> **Rule files — open by path, never search:** `${CLAUDE_PLUGIN_ROOT}/PRINCIPLES.md` · MECHANISMS.md = `${CLAUDE_PLUGIN_ROOT}/references/mechanisms.md` · MECHANISMS-ON-DEMAND.md = `${CLAUDE_PLUGIN_ROOT}/references/mechanisms-on-demand.md` · STATE-MODEL.md = `${CLAUDE_PLUGIN_ROOT}/docs/state-model.md` (a path still starting with `$`: the same names in `.claude/product-playbook/`, else `~/.claude/product-playbook/`).
> **This skill's OWN reference files are a DIFFERENT folder** — they live in `references/` **beside this SKILL.md** (`commands/build/references/`), not in the plugin-root `references/` named above. Both install routes put them there. Below, `feature-archetypes.md` · `live-path-checks.md` each mean that folder: open them by that path, and never look for them next to MECHANISMS.md.
> **Open `PRINCIPLES.md` and `MECHANISMS.md` before the first step** (paths above; a situational companion only when a rule points into it), then apply them — load-bearing: **per-feature contract (security in the DoD)**,
> **secure-by-construction**, **prompts→`prompts/` YAML**, **doc↔code reconciled**, **measure before
> fixing**, **no swallowed errors**, **reuse-before-write**, **trace callers (live-path)**,
> **generic-not-domain-specific**.
> War stories: `${CLAUDE_PLUGIN_ROOT}/references/case-files-build.md` (plugin root, beside MECHANISMS.md — not this skill's `references/`).
> **Companions — opened on demand, never up front:** `references/feature-archetypes.md` (the rules that apply only to gates · async jobs · latency fixes · trust boundaries) · `references/live-path-checks.md` (proving a change is really wired in).
## Contract
- **Purpose:** implement one feature to a verified, secure, documented definition-of-done.
- **Reads:** `PRODUCT.md#Scope`, `#Plan`, `#Contracts`, `#Structure` — **plus `DESIGN.md` + `#Design` for any feature with a user-facing screen** (UI products).
- **Writes:** a row in `PRODUCT.md#Build log` + `docs/features/<feature>.md` — or, in its place, the one per-ticket record `#Project policy` names.
- **Gate type:** `derivation` — computable from the ticket + `#Contracts`. Batchable - a `derivation` run may chain with its neighbours and end in ONE review. (`STATE-MODEL.md` §2d)
- **State model** (`STATE-MODEL.md` §2c): writes `#Build log` · `declined` ✓ · `override` ✓ · `superseded` n/a — append-only log: one row per feature
- **Exit criteria (per feature):**
- [ ] A written **definition-of-done that includes security** (input validation, authz/tenant-isolation; for AI: prompt-injection defence).
- [ ] Reused existing helpers where possible (no reinvented utilities).
- [ ] Code **runs and the LIVE path is verified** (not just an isolated unit) — traced to its real callers, **on the runtime the USER actually runs**: register a new endpoint/route on EVERY serving surface (case file: Wrong serving surface).
- [ ] Diff self-reviewed (`/code-review`); no swallowed errors; prompts in `prompts/` YAML, not inline.
- [ ] **`docs/features/<feature>.md` (or the per-ticket record `#Project policy` names) written and matches the code** (what · contract · exit criteria · how verified · code links).
- [ ] **No secret in any code file** (secrets→`.env`; tests use fake placeholder keys).
- [ ] **Single-responsibility kept** — a file growing large/multi-concern is split into modules (no god-files); long/blocking work stays off the async event loop.
- [ ] **(UI products) The feature's screen(s) are built to `DESIGN.md`** — §5 layout, token look, `/new-component` parts — and **`/frontend-audit` is clean** — 0 errors; on existing files, 0 NEW errors against the ticket's base, the pre-existing count quoted.
## Step 0 — Context + prior-gate check
- Read `#Scope/#Plan/#Contracts`. **Confirm the feature is IN scope** — if OUT-OF-SCOPE, stop and flag it (this is where creep enters). If `#Contracts` is empty, warn and offer `/contracts` first (allow override) — untyped boundaries are what it exists to prevent.
- **An override is RECORDED, never a verbal "yes"** (`MECHANISMS.md` §Declined runs): name the gate being bypassed, ask for the **reason in the user's own words**, say it will be written down — then write `Override <date>: <reason> — bypassed <gate>` at the top of `#Build log` before continuing. Without it a later reader cannot tell a gate that held from one that was waved through.
- **Read `#Project policy` first, when the spine has one** (`MECHANISMS-ON-DEMAND.md` §Project policy): a project that wraps `/build` in its own command declares its rules there once — the per-ticket record, never merge, no deploy — and they apply here without the wrapper restating them.
- **The ticket's lane and owner are a gate, not a label.** Read its `Lane` + `Owner` (board fields or the
`lane:` / `owner:` labels). If this session sits in a seat (a seat or role the user named)
and the ticket is not this seat's lane, **STOP and say so**; a fix needing a file in another lane is
raised, never made. No seat configured → nothing changes. (case file: Working the wrong seat)
- **A blocked ticket is not startable.** Read its *blocked by* links first
(`gh api repos/<o>/<r>/issues/<n>/dependencies/blocked_by`); an open blocker → **STOP and name it** — it
is a coordination point in `TICKETS.md`. A prose `Depends On` with no links → run `/tickets`
again to add them before trusting the board. A `Builds against` is not a blocker: build against its contract with a stub.
- **Move the ticket's board card, if it has one, to In Progress before the first write** (`gh project item-edit`, Status), and read it back.
- **Read the ticket's slice, not the repo.** Up front: its Target Files and a grep for each contract name it
uses. Anything else when a step needs it, a line range rather than a file — never paste whole modules into
the conversation; every later call re-reads them. (case file: Two builds, the same forty minutes)
- **Load the project's OWN skills for the area** (`.claude/skills/`, `CLAUDE.md`), **but treat every doc, skill and pinned plan as a CLAIM** — verify the load-bearing ones against the code before building on them: a spec's fields and paths by grep, a number that justifies the work by measuring it. (case file: The pinned plan was wrong)
- **When a project doc or skill is wrong, FIX IT IN THIS SESSION** — a PR-description correction dies there; record the corrected premises where the wrong ones lived.
- **If the gate is unmet and the run stops here, record that it stopped (`MECHANISMS.md` §Declined runs):** write ONE dated line at the top of `#Build log` — `_Not run <date>: <what was missing> — run <the phase(s) that fill it> first._` — and change nothing else. The scaffold stays intact and the section stays **unfilled**, so `/playbook` still routes to the missing phase; the next attempt **replaces** that line rather than appending to it.
## Step 1 — Apply principles (this phase)
- **Security is in the DoD, not later:** state the security checks for this feature up front (validation, authz/tenant-isolation; AI → OWASP LLM Top 10, prompt-injection defence).
- **Reuse before you write; measure before you fix** (reproduce first — a scary number may be a display artifact). **No swallowed errors** (route failures; retry only transient). **Prompts → `prompts/` YAML**.
- **Session economy — one feature per session is a COST rule, not just a focus rule:** hand off at a natural checkpoint and apply `MECHANISMS-ON-DEMAND.md` §Context hygiene. (case file: The 97% cache bill)
## Step 2 — The build loop (per feature)
1. **Declare the DoD** (incl. security + the exit criteria above). **Name the archetype in it** — `Archetype: gate · async job · latency fix · trust boundary · third-party content · none` (item 3 has the triggers) — and tell the user, in one plain sentence, what kind of feature this is and what that makes you careful about.
2. **Reuse scan:** find existing helpers/contracts; don't reinvent.
3. **Code** against the typed contracts; keep it modular and generic (no domain special-casing in shared infra). **Write code and tests with the edit tool, not with a patch script** — a script puts the code in the conversation twice and breaks on quoting; a cut-the-wire proof is the one exception.
- **If the feature has a user-facing screen (UI products):** build to **`DESIGN.md`** — §5 layout, token look, `/new-component` parts (Law 15) — then run **`/frontend-audit`** — `python "${CLAUDE_PLUGIN_ROOT}/commands/frontend-audit/audit.py" DESIGN.md <ui-dir>`, the installed engine; **never search the plugin cache** (a `$` left in the path: `/frontend-audit` §Which engine runs) — fix every ERROR, and repeat its `engine copy:` line. *(No `DESIGN.md`? `/design-system` first.)*
- **On a ticket that edits EXISTING UI files, add `--baseline <the ticket's base commit>`** (`git merge-base HEAD <default branch>`): it reports only the errors this change ADDS and prints the pre-existing count on its own line. Fix every new error; quote the pre-existing count, and if no open issue tracks those errors, file ONE issue for them — never fixed here, never dropped. A new file or a first screen: the plain run, 0 errors. (case file: Eighty-seven errors, none of them new)
- **A "use a token" fix counts only when the token's class exists in the built CSS** — search the build output for it: a utility class with no mapping to the token (Tailwind v4 without an `@theme` entry) emits nothing, and the element ships invisible. (case file: The token class that compiled to nothing)
- **Some features carry rules that apply only to THEM. If this feature is one, open `references/feature-archetypes.md` and apply that cluster BEFORE you write:**
- a **GATE** — a validator, quality check, policy engine, anything whose job is to say "no" → **§Gates** (10 rules)
- an **ASYNC JOB** — work that outlives the request (spawn + poll, queue + callback) → **§Async jobs** (7 rules)
- a **LATENCY / CONCURRENCY fix** → **§Latency and concurrency** (expect more than one serializer)
- anything **shared across tenants, cached, AI-suggested, or keyed by a client-supplied selector** → **§Trust boundaries and shared state**
- **third-party content shipped to your users** → **§Third-party content** (the licence is a feasibility gate)
- The feature doc carries the line — `Archetype: <kind> → §<cluster> opened`, or `Archetype: none`. A cluster applied from memory is a cluster not applied. (case file: The companions nobody opened)
4. **Run + verify the LIVE path** — compose `/run` to exercise the path the product actually runs, **then check the observable result yourself** (the response, the row, the rendered page — not the exit code), then **trace your change to its real callers** (green unit tests ≠ wired in).
- **Walk the checks in `references/live-path-checks.md` whose trigger matches this feature** — the ones that separate *the code exists* from *the product runs it*. **Open the file on EVERY build** — a trigger you have not read cannot match — and list the ones that matched, by name, in the feature doc: `Live-path checks walked: …`. (case file: The companions nobody opened)
- **Tests: the affected files while you iterate, the full suite once at the gate.**
- **A test that fails, then passes with no change, is FLAKY — record it (name · error · N of M runs failed) and file it with `/tickets "<bug>"` in the lane that owns it.** Outside what you changed → carry on and name the issue in the close; inside it and small → fix it with a proof. **Never re-run until green.** **Attribution is one traceback, not an investigation** — it fails in a file this ticket did not change, or before any test runs → it is OUTSIDE; at most ONE re-run with your change stashed when the traceback cannot say. Never run the suite again to characterise it. (case file: The flake nobody wrote down)
5. **Review the diff — one round, then the user decides.**
- **Commit the ticket's work LOCALLY before round 1** — on the ticket branch (on the default branch, create one first), after `MECHANISMS.md` §Commit the work's repo check; never pushed, no PR — and tell the user in one line, with the undo (`git reset --soft HEAD~1`). **`/security-review` reads only COMMITTED changes:** on an uncommitted tree its files, commits and diff come back empty, and the review falls back to a pass by hand. (case file: The review that read an empty diff)
- **Round 1.** Compose `/code-review` and, on any auth/data surface, `/security-review` — in parallel, **both BEFORE the push, the PR and the close**, so their verdicts land in the record. Fix every finding inside the files this ticket changed, each with its proof, and **commit the fixes locally** before any round 2 — for the same reason.
- **Findings that are not this ticket's.** **A finding that leaves a DoD line unmet is never "outside"** — whichever file its fix lives in, fix it now or STOP and ask the user; it is never deferred. **Any other finding outside the ticket's files is filed IN THIS RUN**, never handed to the user as a list to file — **MEDIUM or above: one issue each** (`/tickets "<finding>"`); **LOW and unrated: ONE issue for the run**, every finding on its own line with its file and what goes wrong (a board of one-line tickets buries the ones that matter; grouped, none is lost) — **and named in the close, never fixed here** (Step 4's flake rule, applied to reviews). **A security finding on a PUBLIC repo is never filed publicly** — never into an issue or a committed file (the spine of a public repo is public too): put it to the user in the close. (case file: The findings handed back)
- **Then show the user what the reviews caught** — before anything else runs: how many, and the ones that mattered most, each in ONE plain sentence saying what would have gone wrong for a user (*"a second shop could have read this shop's photos"*, never *"missing tenant filter"*); that they are fixed and tested; and the time and cost so far, measured (`MECHANISMS-ON-DEMAND.md` §Context hygiene, item 4) or "not measured". Say it again in the close. (case file: The review nobody saw)
- **Round 2 is the user's call, never automatic.** **The reviews are checks too** — a review fix is code no review has seen — so ask ONE question: review again, over **the files round 1's fixes touched, only** (`/security-review` too when they touch an auth/data surface)? Give your recommendation AND its reason: **recommend YES when the fixes touched an auth/data surface**, when round 1 found a HIGH or MEDIUM, or when the fixes were more than small edits; otherwise recommend NO — each fix carries its own proof and the close gate runs over the final tree. It is the one question a build asks mid-run — it depends on what round 1 found — and the tree is green and reviewed once, so it is a safe place to wait. The user chose up front (*"one round"*, *"two rounds"*) → do not ask; nobody can answer (headless, a batch) → follow your own recommendation and say so. On yes: run it, fix what it finds. **A skipped round 2 is RECORDED** in the `#Build log` row with its reason — never silent.
- **No round 3.** A round-2 fix is covered by the deterministic gate at the close (tests · lint · audit · secret-scan · live path, once over the final tree); if round 2 found anything that would fail the DoD, **STOP and tell the user** — the finding and the fix — never a silent third round. (case file: The audit the review fix outran)
- **Record each round's SCOPE** in the `#Build log` row — `/code-review → R1 8 findings · R2 (3 files) 1 finding · <sha> · <date>`, or `· R2 skipped by the user: <reason>` — so `/ship` can tell whether the diff changed since.
- **Run `/security-review` inside a subagent** (prompt: *run /security-review on this branch and return
the findings*) — invoked inline it takes over the turn and ends it on its report. **Its report is
input: record the verdict, then step 6, Step 3, 3b and the close.** (case file: The review that ended the run again)
- **A review that never returns has found nothing** — killed, rate-limited or timed out, its findings are never guessed: re-run it once, same scope; if it dies again, STOP and tell the user. (case file: The reviewer that never came back)
- ⚠️ **If you cannot invoke it, ASK the user to run it, or do the deep pass by hand and say which you did** — see `PRINCIPLES.md`, *Composed skills*.
6. **Document** — once, after Step 5's last round — write/update `docs/features/<feature>.md`; reconcile it with the code. **`#Project policy` names a per-ticket record** → write that content THERE, once, and nothing into `docs/features/`; the `#Build log` row stays, the one-line index `/ship` and `/dev-check` read. **Copy every `evidence:` number from the command's captured output, never ahead of it** — a figure pencilled in while CI runs reads exactly like a measured one. (case file: The pencilled bundle size)
## Step 3 — Write back to `PRODUCT.md`
Append ONE table row to `#Build log` — `| feature | DoD-incl-security met? | how verified: command → result, plus Step 5's round scope | docs/features/<feature>.md |`. **A row is one line, and `#Build log` holds table rows and nothing else** — besides the dated one-line `Override` / `_Not run_` records §Declined runs puts at its top; no paragraph under the table, which the next build copies. Findings, defects, round detail and **Step 3c's result go in the feature doc**, never in the log (`MECHANISMS-ON-DEMAND.md` §Section is a record). **Find your place with `grep -n` — the section heading, your ticket's id — and never load the log**: a log every build reads makes each ticket cost more than the last. (case file: The log every build read)
## Step 3b — Principle-gate: verify each principle is ACTUALLY implemented (not just claimed)
Walk **this phase's load-bearing principles (Step 1)** and confirm each is real, **citing the evidence Step 2 already captured** (command · result · commit). (case file: The audit the review fix outran)
- **Cite, or re-run?** **re-run a check when any code, config or test file it reads changed after its evidence was captured; a review fix counts, a doc-only change does not.** Uncommitted and scripted edits count. A file a check reads is never doc-only for that check (`DESIGN.md` for `/frontend-audit`), and **when unsure whether anything changed or whether a change is doc-only, re-run** — a stale citation passes a broken feature.
- **Scope by what a check reads** — a backend-only fix does not re-run the UI audit — but **when you cannot name the files a check reads, re-run it**, and **the whole gate runs once more at the close**, whatever the scoping said: scoping removes repetition inside the loop, never coverage of the committed tree.
- **This gate runs ONCE, after Step 5's last round** — never after each round, and it never re-opens the reviews: Step 5 bounds them.
**Evidence, per principle:**
- security-in-DoD → **`/security-review`** (or the equivalent pass) passed — name which.
- no secret in code / no-hardcoding → secret-scan clean.
- live-path-works → **`/run`** exercised the real path **and the observable result was checked** — name the command **and what you saw**. An exit code is not the observable result.
- reuse · no-swallowed-errors · single-responsibility → confirmed in the diff's **`/code-review`**.
- (UI features) built-to-the-design → **`/frontend-audit`** 0 errors against `DESIGN.md` — or 0 new with `--baseline`, the pre-existing count quoted.
**If any named principle is only claimed, not evidenced, STOP — the feature is not done.** Record the *how-verified* per principle in `#Build log` (evidence, not "done"). (Deterministic checks also run via the commit hooks + CI from `/foundation`; this gate is the judgment layer.)
**Close the loop (`MECHANISMS.md` §Step 3b):** update the `Stage:`/`Last updated:` header, reconcile any number this phase introduced against `#Vision` (surface a contradiction, never write over it), and **offer to commit the change** (`MECHANISMS.md` §Commit the work — check the repo exists, name the branch, offer the message, push only if a remote exists and the user says so). Then **run the transition guard** (`MECHANISMS.md` §Step 3b, item 4): re-run this phase's own `evidence:` lines and report a verdict for every exit criterion — `UNVERIFIED` is a normal outcome, silence is not — and check the transition is legal. **Close in plain language** (`MECHANISMS.md` §Plain-language close): two or three sentences of *what just happened* with no playbook dialect, then a numbered *what YOU do next* — the user's own actions, dated where they are time-bound, or "Nothing — you're done". **The close is the run's last message** — a composed skill's report is input to it, never the close itself. (case file: The report that became the close) After the PR merges, set the card to **Done** and read it back; merged later → the close gives the user that command.
## Step 3c — Contradiction check (before the gate closes)
Per `MECHANISMS.md` §Step 3c, check what this phase just produced against decisions **already recorded** — here: `#Contracts` (types crossing boundaries), `#Architecture` (adapters — no vendor SDK in logic), `#Scope` (non-goals) and `DESIGN.md` (UI tokens) — the richest surface for contradiction, because this is the phase that writes real code. On a conflict, **name both sides, ask which wins, and update the loser** (fix the artefact, or add a dated `superseded by` line to the earlier section) — never leave it standing in two places. Adding detail to an earlier decision is not a contradiction. **Step 3c's result goes in the feature doc** — never a paragraph in `#Build log`, which holds table rows and nothing else.
## Step 4 — Handoff
"Feature done, verified, and documented. Build the next core-scope feature with `/build`, or when the
core scope is complete run **`/dev-check`** — the checkpoint that verifies everything before testing."