specify CLI is pinned exactly in mise.toml (pipx:specify-cli, ADR 0121 D15), and pnpm doctor fails when the installed version differs. It is initialised for both Claude Code (.claude/skills/speckit-*) and OpenCode (.opencode/commands/speckit.*). A Stackra preset wraps the core commands with the spec location and the gates described below, replaces the spec, plan and tasks templates, and replaces /speckit.taskstoissues (tasks become Linear sub-issues through pnpm spec:linear) and /speckit.constitution (generated by pnpm agents:sync). Its only source is .agents/speckit/presets/stackra. pnpm speckit:sync installs it into .specify/presets/stackra, keeps the bundled speckit workflow disabled, and re-renders OpenCode and then Claude, because spec-kit registers preset commands only for the active integration. The spec-gate tripwire fails when the installed copy differs from the source or either assistant’s copy loses a Stackra section. A change that is not specified starts at /speckit.specify: the prompt-hints hook nudges an ad-hoc feature request there.
Where specs live
Every spec lives in.agents/specs/sta-<n>-<slug>/, named by its Linear issue (ADR 0120 D1). spec-kit has no project-wide setting for the folder, so the preset passes it on every run: each wrapped command first runs node tools/scripts/spec-gate.mjs dir, which reads the STA key from the branch name (figentra/sta-<n>-…, or --key STA-n), prints the folder and writes .specify/feature.json. That path is the SPECIFY_FEATURE_DIRECTORY every .specify/scripts call receives. /speckit.specify adds --slug <short-name> to name a new folder. .specify/init-options.json sets feature_numbering to linear-key, so nothing auto-numbers. Root specs/ is not used, and spec-gate ci fails a change that adds to it.
The wrap rests on core spec-kit behaviour an upgrade could change. spec-gate ci therefore runs a tripwire: the gate call in setup-plan.sh, common.sh still resolving SPECIFY_FEATURE_DIRECTORY and then feature.json, feature_numbering still linear-key, core specify still taking the directory as-is, and the Stackra section present in every wrapped command for both assistants. Upgrade spec-kit only in a dedicated pull request: bump mise.toml and speckit_version, run pnpm speckit:sync, then pnpm validate.
Specs are public on GitHub and are not published here (D17). They never hold secrets, customer data or details of an unfixed vulnerability.
The flow
The clarify-before-build gate
Everyspec.md carries three things the template provides:
**Linear**: STA-n, the issue for the spec (team STA). Optionally**Linear project**.**Clarifications status**: pending | complete, and**Status**: Draft, Clarified, Planned, Tasked, Implementing, Done, or Abandoned / Superseded (owner decision OR12). Each change appends a line to## Status history; an amendment appends to## Amendmentsand never renumbers an ID.- An
## Impactquestionnaire, every line answered (noneis an answer): apps, services and packages touched, cross-service events, tenancy and data, secrets (names, never values), whether an ADR is needed, new dependencies, deploy modes (what differs in SaaS, on-prem and air-gapped), the lane (full, lite, bug or chore) and the change risk (R0 to R3), both from ADR 0120.
tools/scripts/spec-gate.mjs plan passes only when the status is complete, a real STA-n is set, every Impact line is answered and no [NEEDS CLARIFICATION marker remains. It is enforced twice:
- Locally.
.specify/scripts/bash/setup-plan.shruns it first, so/speckit.planfails before it writes anything.specify init --forceorspecify integration upgradecan overwrite that script;spec-gate.mjs cifails if the call is gone. - In CI.
pnpm validaterunsspec-gate.mjs ciandspec-trace.mjs ciover every.agents/specs/<folder>/changed sinceorigin/main, at the stage its**Status**claims. A status may lag behind the artifacts but never run ahead of them.
spec-gate ci also checks the lane (a feat commit needs a spec reached through the branch key or its Refs: keys), the bug lane (.specify/bugs/sta-<n>-<slug>/ with a regression test tagged @covers sta-<n>:BUG) but not the branch name: pnpm linear check-pr owns that, with its chore-lane label escape, and the pre-push spec-gate branch only warns. A ready-for-review PR (SPEC_GATE_REQUIRE_READY=1) needs its own spec (the folder keyed by the branch’s STA key) at Tasked or later, so a spec PR stays a draft until then; an edit to another spec only has to pass check. node tools/scripts/spec-trace.mjs trace <dir> prints the FR → task → test matrix.
Answers that raise an ADR go to docs-adr-steward; a new dependency goes through the catalog (dependency-steward).
Linear
Work isn’t done until its Linear issue is in the right state; agents usepnpm linear. That includes subagents and OpenCode executors that have no Linear MCP.
pnpm linear runs tools/scripts/linear.mjs under infisical run --env=dev --path=/agents. It acts as the Stackra Agent Linear app, which becomes the issue’s delegate, never its assignee. The token is LINEAR_AGENT_TOKEN, re-minted from LINEAR_AGENT_CLIENT_ID/LINEAR_AGENT_CLIENT_SECRET when it expires. The personal LINEAR_API_KEY is the fallback. create adds the agent label when an agent runs it.
Stage transitions report themselves. spec-gate.mjs plan comments “clarified” on the spec’s issue when it passes. spec-gate.mjs stage <spec.md> planned|tasked|implementing|done comments the later stages, and implementing also moves the issue to In Progress. pnpm delegate requires --issue STA-n: it starts the issue at dispatch and comments the result. None of these calls can fail a gate. Linear sync never blocks (ADR 0065 rule 7), and STACKRA_LINEAR=off turns them off.
Keys are enforced offline. A feat, fix, refactor or perf commit needs a Refs: STA-n footer, checked by commitlint in lefthook and over the PR range in CI. The Quality Gate also fails a PR whose title and branch both lack an STA key, unless it is labelled chore-lane or comes from renovate/* or changeset-release/*.
The Linear sync workflow (.github/workflows/linear-sync.yml, nightly and on push to main) runs tools/scripts/linear-reconcile.mjs. It moves an issue to In Review when an open PR mentions it (in the title, the branch, Refs/Fixes, or an attachment). It moves an issue to Done only when a merged PR closes it, through the branch name or Fixes/Closes/Resolves. A Refs mention never closes an issue, so an issue that spans several PRs stays open. Fork PRs are ignored. It closes “Decide ADR NNNN” once that ADR is Accepted or Superseded. It labels In Progress issues stale after 3 days without branch or PR activity, and comments on Backlog issues whose ADR is now Accepted. Each comment carries a linear-reconcile (<tag>) marker, so a re-run, or a human reopening an issue, is never overridden. --dry-run writes nothing.
tools/scripts/spec-linear.mjs issue <spec.md> creates the spec’s issue and writes the key into spec.md. tools/scripts/spec-linear.mjs tasks <tasks.md> creates one sub-issue per T### task and appends [STA-n] to the line. Both take --dry-run (no token, no calls) and use the same client and tokens as pnpm linear, so run them as infisical run --env=dev --path=/agents -- node tools/scripts/spec-linear.mjs .... No token is ever stored in a file.
The constitution
.specify/memory/constitution.md is generated from the laws in apps/docs/src/constitution/*.mdx by tools/scripts/agents-sync.mjs. Edit the laws, run pnpm agents:sync, and pnpm agents:check fails on drift. Never edit the generated file or run /speckit.constitution against it.
Review checklist
A spec passes review when every row is PASS or N/A with a reason. Reviewers check against the real tree, not the prose.- Decisions honoured. Each applicable law and ADR is cited where the plan honours it. See Constitution and Architecture decisions.
- Cross-package consistency. Every symbol the plan imports exists in that package’s current barrel. Dependency direction has no cycle. Events a package emits are listed once, and ones it consumes exist in the owner.
- Hygiene. Every requirement is covered by a task. No
TBD,later,for now,temporaryorstub. The plan has no open question at approval. - Impact is true. The Impact answers match the files the plan actually touches.
/speckit.analyze is the mechanical half: it cross-checks spec, plan and tasks. It does not judge whether the requirements were the right ones. That is what the human gate is for.
When a finding becomes a rule
Thefailure-loop skill handles a gate failing in front of you: classify it, route it to the owner, fix the cause, re-run. For most findings that is the end. For some it is not. A finding about the repository rather than the diff will recur on the next spec, raised by a different reviewer, unless something changes.
A finding is durable when any one of three holds:
- It has been raised before. The second occurrence, not the third.
- A check could have caught it, and none exists. This is the highest-value trigger.
- It touches an area where the first occurrence is already too many: secrets, generated artefacts, the CI gate, tier and layer boundaries, a published export surface.
Enforcement beats awareness. If a finding can be stated as a predicate over files, write the check, not a rule asking agents to remember. A decision and its enforcement are two artifacts. An ADR that no check enforces is a standard that only a tired reviewer stands behind, so the ADR ships with its check or names the check and its owner in its follow-up.
The reviewer who raised the finding names the class and the destination. The owning agent writes the artifact. A reviewer who must also write the rule quietly stops raising findings.
Every artifact written by a promotion opens with one provenance line: