From Spec to Release
This segment follows one feature from an idea to the people who use it. First the feature loop: a mockup round, a spec you approve before any code exists, and a build that counts as done only when each acceptance criterion has evidence. Then the release: why deploying and releasing are separate acts, the release skills, the staging twin, release flags, why the release is decided in the spec, and how to choose between a flag and staging. The design tools and the agents that test your app are on The Agent Team Kit; the Kit Reference lists every skill.
The feature loop: mockup, spec, build
The model: the spec is the gate between exploring and building. Edit it while that is cheap, before any code exists; then build until every acceptance criterion has evidence.
Three skills carry a feature from idea to verified code, with one human gate in the middle.
| Skill | What it produces | Then |
|---|---|---|
/mockup |
A throwaway mockup or clickable prototype in .mockups/, frozen from the real app |
You react and it iterates on the same files |
/spec |
.specs/<slug>/spec.md and punchlist.md |
It stops. You read and edit the spec, then approve |
/build-spec |
Code, tests, one commit per punch-list item, and a check sheet | You drive the check sheet; it reports what is closed and what is open |
Editing a spec by hand is the cheapest change in the loop, and it is available only before code exists, which is why /spec and /build-spec are two skills. The installer gitignores .mockups/ and .specs/ as working files.
From a mockup round to a spec
Fast design loops describes the three /mockup modes. For this loop, one thing matters: during a round the agent keeps a running list of every change you agree to, and /spec turns that list into stories and acceptance criteria. The mockup files are thrown away.
/spec: write down what was decided before it is lost
The decisions that make a feature good (the wording, the empty state, the option you turned down) are made while exploring, in a conversation that is lost at compaction and never reaches a worktree agent. /spec turns it into a file and writes no code. Where the exploration said nothing, it writes an open question with a recommended default, because an invented requirement reads as if you agreed to it.
Every acceptance criterion gets a tag naming what will check it. In the skill’s words, “a markdown assertion has no teeth.”
| Tag | The criterion is about | Checked by |
|---|---|---|
walk |
What a user sees, does not see, or is told | A browser check: a line on the check sheet, or a walk step if the project has a walk runner |
unit |
A calculation, ranking, formatter, or parser | A test next to the code |
structure |
How the code is organized: “there is one place that saves a task” | An architecture guard or a grep assertion |
Rules for writing criteria, from the skill:
- If a criterion cannot be tagged, it is too vague to build from. Rewrite it now.
- Name the persona and the screen. “As the workspace admin, on the Members page…” can be checked; “A signed-in user can…” cannot.
- One observable thing per criterion. If it needs “and”, split it.
- Write the negative as its own criterion for anything about who can see or do something: “A member does NOT see the Billing page.” Most defects here are something appearing for someone who should not see it.
Because .specs/ is deleted later, the skill also copies the walk criteria to a tracked file (a walk scaffold, or docs/acceptance/<slug>.md). unit and structure criteria leave committed tests behind; a hand-checked walk criterion would leave nothing.
The spec follows the kit’s template:
# <Feature>, spec
## Intent
## Access and release
### Access: what differs by person, and which lever handles it
### Release
## Stories and acceptance criteria
### S1, <as a ..., I ...>
## Decisions already made
## Considered and rejected
## Open questions
## Constraints in this repoDecisions already made keeps the build from reopening settled wording and states. Considered and rejected, with reasons, keeps the agent from proposing a rejected option again. Access and release is required (below). Constraints in this repo (where the logic lives, any migration, which guards are likely to fire) is filled from your CLAUDE.md.
The punch list is an ordered checklist. Each item names its files and the criteria it closes, and only items that share no files are marked [parallel]:
- [ ] 1. Capability table gains `can.exportReports` (src/access.ts) closes 2.3
- [ ] 2. Export button hidden without it (ReportHeader.tsx, .test.tsx) closes 2.1, 2.2 [parallel]
Then the skill stops and reports what it assumed, what it left open, and how the feature will be released.
/build-spec: done means every criterion has evidence
After you approve, /build-spec works the punch list one item at a time: implement it, write the tests for its unit and structure criteria, add its walk criteria to a check sheet, run the targeted tests, check the box in punchlist.md, and commit that item alone with explicit paths. Progress lives in the file, so a fresh session or a worktree agent picks up where the last one stopped. How the agent checks its own work in the browser is on Agents that test your app.
The check sheet, .specs/<slug>/check.md, has three lines per walk criterion:
### 2.2, a member cannot export reports [criterion 2.2]
Go: http://localhost:5181/reports (signed in as a member)
Do: open the report menu
Expect: Export is NOT in the list.
Go: is a clickable URL on the agent’s own dev server, on the port the dev-port hook assigned. The skill leaves that server running and hands you the link, because in check-sheet mode you are the browser check.
Its hard rules: never reword a criterion to make it pass, never drop a failing check, never claim a browser check it did not run. The final report lists each criterion with the check that closed it, the ones still open and why, and what it assumed.
Deploying and releasing are separate acts
The model: deploying puts code on a server; releasing lets people use it. Keeping them separate makes every release a deliberate act you can schedule, stage, or limit to some users.
RELEASE-PIPELINE.md starts from two definitions. Deploy: code reaches a server. Release: people see it.
Most setups join them: push main and it is live. With several agents committing to main all day, main always holds someone’s half-finished work. The kit separates three moments (deploy is not release):
| You push | CI does | Who sees it |
|---|---|---|
main |
Check, test, build. No deploy | Nobody |
A tag v<x.y.z>-rcN |
The same checks, then deploys staging and smoke-tests it | The team, on staging |
A tag v<x.y.z> |
The same checks, then deploys production and smoke-tests it | Everyone the release flags allow |
The production job refuses any tag containing -rc, so a candidate cannot reach production by a typo. main and the tag go up together with git push --atomic, because otherwise a rejected main still lets the tag land and deploy a commit that is not on main. The smoke test checks the deployed commit, read from a status endpoint.
On a host that deploys every push to main (a default Vercel or Netlify project), the push is the production deploy and there is no staging road until you move to tag-triggered deploys.
The release skills
| Skill | What it does |
|---|---|
/push |
Releases the tip of main to production: applies production migrations, writes customer-facing release notes, bumps the version, tags, pushes atomically, smoke-tests the deployed commit, and reports what skipped staging and which flagged features are still dark |
/stage |
Applies migrations to the staging database through a wrapper that refuses production, checks parity, tags the next release candidate, pushes, and smoke-tests it |
/promote |
Releases to production exactly the commit staging is serving, read from staging’s status endpoint. Refuses if CI was not green on it or a destructive migration is in range |
/remove-flag |
Takes a release flag out of the code once its feature is on for everyone |
Invoking one of these is the authorization; they do not ask “are you sure”. They stop only on real blockers: a destructive migration, a rebase conflict, red CI, a failed smoke test.
Rollback has no skill. The pipeline doc describes it as a manual procedure: redeploy the previous release’s build, then git revert the bad commit and release the revert with /push. For a flagged feature, you turn the flag off instead.
There are two roads to production:
- Road A, straight to production (
/push): small, safe, or urgent work, such as a copy fix, a bug fix with a test, or anything a release flag keeps dark. - Road B, staging first (
/stage, people try it,/promote): changes that take effect for everyone the moment they deploy (migrations, infrastructure, dependency upgrades), and changes whose first audience could cause irreversible harm outside the team.
The rule for road B: promote the exact commit you tested. A release cut from the tip of main carries every commit made after the rehearsal, so /promote reads the staged commit from staging itself and, if main has moved, asks whether to ship the staged build alone or all of main (promote the staged commit).
The staging twin
Staging is worth opening only if a test there predicts production. STAGING-TWIN-SPEC.md specifies a twin:
| Property | What it means |
|---|---|
| Same build and configuration | The same environment variables, migrations, and database size. Each difference (its own database URL and keys) is on a written list with a reason |
| A parity check | A script fails on any unlisted difference, before every staging deploy and nightly. “Could not run” is never a pass |
| A slice of production data | The team’s own accounts and what they own. Everyone else those rows mention becomes a locked placeholder |
| An egress fence | Every email and outbound request goes through one function, which on the twin lets through only the team’s addresses and sandbox services (fence outward writes at one chokepoint) |
| Deliberate deploys | Only a release-candidate tag deploys it, so it does not change under someone mid-test |
The testing agents run on a different copy, the disposable environment (DISPOSABLE-ENVIRONMENT.md), whose seeded data you can wipe. The twin holds real names, so only people use it.
Release flags
The model: a release flag lets one deploy carry a feature that only some people see, so releasing it becomes a setting you change.
RELEASE-FLAGS-SPEC.md is the other half of separating deploy from release. Every commit ships to production switched off (“dark”). A super admin turns the feature on from an admin page, one audience at a time: Off, Team, named users, tenants, Everyone. No deploy is involved, and turning it off is a click.
The design in brief:
- A registry in code lists the flags, so a typo does not compile and removing a flag makes the compiler name every remaining use. A table holds only audiences; a flag with no row is off.
- Check on the server, at one seam. A helper answers 404 when the flag is off, so a dark feature is unreachable by URL. A flag that only hides a button is not a gate.
- With the flag off, behavior is byte-identical to before the feature existed, and a test proves it.
- An environment variable,
FLAG_<KEY>=0or=1, overrides the table: the kill switch in an incident. - Flag debt is capped by a test, and
/remove-flagdeletes a flag once its feature is on for everyone. - Migrations are not flagged. A flagged feature’s schema change goes in steps the old code can still read.
A release flag answers one question, “is this new code on for you yet?”, and is deleted within weeks. The spec lists five permanent levers for other “who sees what” questions: a kill switch, an experiment, a role or relationship, an entitlement (the organization’s plan), and a preference. Keeping a paid tier or an admin-only page in a release flag is the common mistake (pick the lever by lifetime and owner).
Decide the release at spec time
How a feature reaches people shapes its code: where the check sits, what a user without the flag sees, whether a migration must stay readable by the old code. A mockup is too early to decide this and build time is too late, so the spec’s Access and release section is required (decide the release at spec time):
| Field | What it records |
|---|---|
| Kind | Release flag, permanent kill switch, rehearse on staging, or no flag, and why |
| Flag key | The snake_case key, or n/a |
| Seam | The one place the check goes. If none exists, creating it is the first punch-list item |
| Whose flags apply | The viewer, or the resource’s owner (for shared pages and background jobs, which have no viewer) |
| Off path | What a user without the flag sees (it should be: no change) |
| Rollout | Team, then a pilot, then everyone, and who decides each step |
A release flag adds three standard criteria: a walk check that a user without the flag does not see the feature, a unit test that flag-off behaves as before, and a structure check that the server checks the flag. /build-spec’s report then says “shipped dark” with the release step, so nobody mistakes “done” for “live”.
Flag or staging: decided by whose data
The model: a flag controls who the code runs for; staging controls where its effects land. Choose by whose data the feature touches.
A flag controls who the code runs for. It does not undo what the code did: an email sent stays sent. Staging, with its egress fence, controls where the effects land. Two questions decide (flag or staging is whose data):
- Does it take effect for everyone the moment it deploys? Migrations, containers, workers, and dependency upgrades run before any flag is checked. Rehearse on staging and promote that exact build.
- For a change that runs per request, what is the worst case for the first audience, whose data does it land on, and can it be undone? On the team’s own data, or reversible: a flag is enough. Irreversible and outside the team (a customer’s inbox, a payment, someone else’s system): rehearse on the fenced twin first.
| Change | Road |
|---|---|
| A new settings panel that only reads and displays data | Flag, road A |
| A migration that changes data or constraints | Staging, road B |
| An added column for a flagged feature, applied in compatible steps | Road A: old code ignores the new column |
| A feature that emails customers | Staging first, then a flag for the release |
One hidden case: a team member who is also a real customer makes “Team” an audience that reaches real people.
Pick the next feature on your list and, before any code, answer the two questions above in one sentence each. Then ask Claude Code: “Read https://github.com/ai-foundry-byu/agent-team-kit/blob/main/skills/spec/TEMPLATE.md and draft only the Access and release section for this feature: [describe it]. Name the kind, the seam, whose flags apply, and the off path, and list anything you had to assume as an open question.” You will run the full /spec and /build-spec loop in the Lab.