Run a spec-driven project
This guide sets up a project for spec-driven delivery: specs and work-item contracts in your repository, the gates that hold every change to them, readiness published on your tracker, and a SupaCloud project that works on the result. For the idea behind it, read Spec-driven delivery first.
1. Add the spec layer to your repository
Section titled “1. Add the spec layer to your repository”-
Create
specs/with the schemas inspecs/_schema/and the template domain inspecs/_template/. If your copy of the schemas has no version lock yet, write the first one withuv run python tools/specs/validate.py --init-lock; from then on--write-lockrefreshes it, and a missing lock fails validation. -
Write
specs/project.yaml, the project profile, before anything else. It is what makes the format fit your project: where your requirement catalogue is, which of its lists hold requirements, acceptance criteria and decision records, one pattern with an example per requirement-ID shape you use, and every unit your parameters use with its meaning. There is no default — without this file the validator, the spec gate and the work-item checks all fail and name the missing field. The fields are listed in Delivery spec format. -
Copy the template to your first domain, for example
specs/billing/, and replace the domain codeTPLin every file and every ID (TPL-R-001becomesBILL-R-001). -
Write the clauses in
spec.yamlfirst — one testable statement each, in EARS form, citing the requirements it satisfies. Put every number intoparameters.yamlwith an approved range instead of into the clause text. -
Write one scenario per behaviour in
scenarios/, with concrete values. The scenarios are the test oracle, so write the expected values yourself rather than leaving them to the agent that will implement the clause. -
Put every open design choice into
decisions.yamlwith its options, a recommended option and the reason, and every gap intoquestions.yaml. Leave the status atdraft. -
Point
scope.pathsat the code that implements the domain, then validate:Terminal window uv run python tools/specs/validate.py specs/billinguv run python tools/qa/check_specs.pyBoth must report zero findings. When a domain takes on catalogue IDs, the spec gate’s coverage baseline has to be rewritten in the same change (
--write-baseline): that baseline must equal the count of catalogue IDs no domain owns, so slack in it cannot hide the next regression. The format, field by field, is in Delivery spec format.
2. Take the design decisions
Section titled “2. Take the design decisions”-
Answer every decision in
decisions.yaml. Today you do this outside SupaCloud — in a questionnaire, a review of the spec pull request, or by hand — and record each answer in the decision’sanswerblock withchoice,answered_by,answered_atandsource, then set its status toapproved. Decision briefs inside SupaCloud are planned (SC-29). -
Confirm each recommendation you accept. A pre-selected recommendation that nobody confirmed is not an answer: leave such a decision
proposeduntil you have actually decided it. -
Answer or withdraw every open question, then set the spec’s status to
design-approvedin the same pull request. The spec gate refuses the status while a decision is stillproposedor a question stillopen.
3. Write work items that bind to clauses
Section titled “3. Write work items that bind to clauses”-
For each slice, write a work-item contract with
spec_refs(the clauses it implements),decision_refs(the decisions it depends on) and acceptance entries whoseverifiesnames the clauses each entry proves. -
Keep each item to at most six clauses and eight acceptance entries; split anything larger.
-
Give every item a complexity class (
T0toT3). It selects how many independent reviews the change needs before it merges. -
Run the work-item gates until they are green. The full rule set is in Spec readiness contract.
4. Enforce the gates in your repository
Section titled “4. Enforce the gates in your repository”-
Run the spec gate and the work-item gates in your CI on every pull request.
-
On your main branch, require those checks through branch protection.
-
Add a required status for the gate classes that only the class’s approver turns green, and require it too: you for G1 to G5 and for the gate map itself; for the security-review classes (S1 new dependencies, S2 CI workflows, S3 security tooling and gate configuration), the automated security reviewer once it exists (planned) and you until then. The reference implementation’s version of this status is
gate-class, and it is required on its main branch alongside its three CI contexts — copy that shape:- The base branch judges. The job checks out the class map and the checker from the base branch and reads the pull request head only as data, so a pull request cannot relax the rules it is judged by. Put the map, the checker and the workflow in an owner class themselves.
- Run it on every event that can change the verdict: a new or retargeted head, a submitted or edited review, and a created, edited or deleted approval comment.
- Bind the approval to the head commit, so a new push needs a new approval and a dismissed or stale review counts for nothing.
- If your forge refuses self-approval, as Forgejo does, accept a comment naming the commit
(
/approve-gates <sha>) as your approval of a pull request you authored — but never count an edited comment, because anyone with write access can edit anyone’s comment. - A reviewer’s request for changes escalates the security-review classes to you.
- It needs no secret and no bot account: the forge’s own CI job token reads the pull request, its reviews and its comments and posts the status.
- Know the limits: an instance admin can force-merge past branch protection, and any writer can post a status with any context. The gate reliably stops the autonomous merge path; it is not a defence against a deliberate forgery.
Until you have such a status, keep changes of those classes behind a merge you do yourself.
-
Allow merge commits on the main branch, or expect SupaCloud’s merges to be refused: SupaCloud merges with a merge commit today, so a squash-only repository refuses every merge it attempts. Per-project merge styles are planned (SC-24).
5. Publish readiness on your tracker
Section titled “5. Publish readiness on your tracker”A work item is ready when its dependencies are closed, its decisions approved and its spec
design-approved or further; a ready item with spec_refs is spec-ready.
-
Today, run the reference sync from your own session with your own forge token. Look at the planned writes first, then apply them, then check:
Terminal window export FORGEJO_TOKEN=… # your token; never commit it or store it as a CI secretuv run python tools/fmctl.py workitems sync -- --dry-runuv run python tools/fmctl.py workitems syncuv run python tools/fmctl.py workitems sync -- --checkThe check must report no failures. Run the sync again whenever a decision is answered, a spec changes status or an issue closes.
-
Do not add a CI job, a bot account or a repository secret for this. When SupaCloud computes readiness itself (planned, SC-2 with SC-20), it becomes the only writer of these labels through the forge connection your project already has, and you stop running the sync.
6. Connect the work to SupaCloud
Section titled “6. Connect the work to SupaCloud”-
Launch
spec-readyissues yourself for now. SupaCloud’s backlog does not read labels yet: in Backlog mode its classifier would also dispatch open issues that areblockedor that are decisions rather than work. From the web terminal,issues <project>lists them andrun <project> <agent> --issue <n>starts one — see Launch a task from an issue. Dispatching exactly thespec-readyissues is planned (SC-2). -
If you do run the backlog, set its Merge policy explicitly. Choose PR only (manual merge) while you merge yourself, or Auto-merge when green with the branch protection from step 4 as the backstop. Never choose Fully autonomous for a spec-driven project: it has no fallback for sensitive changes. The autonomy slider only ever lowers the merge policy you set, so an explicit Auto-merge when green stays in force even at the highest autonomy level — see Set the autonomy level.
-
Let agents ask instead of guess. An agent that hits a gap raises a question with
question.ask; you answer it in the inbox or on the chat card — see Let an agent ask a question. Today, record the answer in the spec yourself (a decision, or the question’sanswer) by pull request; writing it back automatically is planned (SC-10).
Check that it works
Section titled “Check that it works”uv run python tools/qa/check_specs.pyand the work-item gates report zero findings on main.uv run python tools/fmctl.py workitems sync -- --checkreports no failures, and every open work item carries exactly one ofreadyorblocked.- A pull request that changes code under a domain’s
scope.pathswithout naming one of its clauses in a commit message turns the spec gate red. - Deleting
specs/project.yamlturns the validator, the spec gate and the work-item checks red and names the file — nothing falls back to someone else’s ID shapes. - A pull request with a failing required check cannot be merged — by you or by SupaCloud.
- A pull request that answers a decision or moves a parameter’s approved range shows the gate status as pending until you approve that exact commit, and a new push makes it pending again.