Skip to content
Select themeSelect language

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. Create specs/ with the schemas in specs/_schema/ and the template domain in specs/_template/. If your copy of the schemas has no version lock yet, write the first one with uv run python tools/specs/validate.py --init-lock; from then on --write-lock refreshes it, and a missing lock fails validation.

  2. 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.

  3. Copy the template to your first domain, for example specs/billing/, and replace the domain code TPL in every file and every ID (TPL-R-001 becomes BILL-R-001).

  4. Write the clauses in spec.yaml first — one testable statement each, in EARS form, citing the requirements it satisfies. Put every number into parameters.yaml with an approved range instead of into the clause text.

  5. 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.

  6. Put every open design choice into decisions.yaml with its options, a recommended option and the reason, and every gap into questions.yaml. Leave the status at draft.

  7. Point scope.paths at the code that implements the domain, then validate:

    Terminal window
    uv run python tools/specs/validate.py specs/billing
    uv run python tools/qa/check_specs.py

    Both 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.

  1. 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’s answer block with choice, answered_by, answered_at and source, then set its status to approved. Decision briefs inside SupaCloud are planned (SC-29).

  2. Confirm each recommendation you accept. A pre-selected recommendation that nobody confirmed is not an answer: leave such a decision proposed until you have actually decided it.

  3. Answer or withdraw every open question, then set the spec’s status to design-approved in the same pull request. The spec gate refuses the status while a decision is still proposed or a question still open.

  1. 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 whose verifies names the clauses each entry proves.

  2. Keep each item to at most six clauses and eight acceptance entries; split anything larger.

  3. Give every item a complexity class (T0 to T3). It selects how many independent reviews the change needs before it merges.

  4. Run the work-item gates until they are green. The full rule set is in Spec readiness contract.

  1. Run the spec gate and the work-item gates in your CI on every pull request.

  2. On your main branch, require those checks through branch protection.

  3. 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.

  4. 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).

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.

  1. 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 secret
    uv run python tools/fmctl.py workitems sync -- --dry-run
    uv run python tools/fmctl.py workitems sync
    uv run python tools/fmctl.py workitems sync -- --check

    The check must report no failures. Run the sync again whenever a decision is answered, a spec changes status or an issue closes.

  2. 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.

  1. Launch spec-ready issues yourself for now. SupaCloud’s backlog does not read labels yet: in Backlog mode its classifier would also dispatch open issues that are blocked or that are decisions rather than work. From the web terminal, issues <project> lists them and run <project> <agent> --issue <n> starts one — see Launch a task from an issue. Dispatching exactly the spec-ready issues is planned (SC-2).

  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.

  3. 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’s answer) by pull request; writing it back automatically is planned (SC-10).

  • uv run python tools/qa/check_specs.py and the work-item gates report zero findings on main.
  • uv run python tools/fmctl.py workitems sync -- --check reports no failures, and every open work item carries exactly one of ready or blocked.
  • A pull request that changes code under a domain’s scope.paths without naming one of its clauses in a commit message turns the spec gate red.
  • Deleting specs/project.yaml turns 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.