Working to a specification
Name the spec folder a change answers, send an agent the tasks you choose, and make your own gates the verdict inside Spec Kit's workflow.
Write specs with Spec Kit, OpenSpec, Kiro, GSD or plain Markdown; Devplane adopts no spec format.
devplane change start "password reset" --spec specs/001-password-reset
devplane change show <id>What Devplane reads
Committed Markdown, one folder per spec, headings as outline, and progress as the boxes in the tool’s task file, counted the tool’s way (checklists/ excluded). Each layout is detected by its tool’s own marker:
| Tool | Detected by | Spec folders | Progress |
|---|---|---|---|
| Spec Kit | .specify/ | specs/NNN-name/ | - [ ] / - [x] in tasks.md |
| OpenSpec | openspec/config.yaml (or config.yml, or openspec/project.md) | openspec/changes/<id>/ (not archive/) | tasks.md, as openspec list counts it |
| Kiro | .kiro/specs/ | .kiro/specs/<feature>/ | - [ ] / - [x] in tasks.md |
| GSD | .planning/STATE.md | .planning/phases/<phase>/ | none claimed |
Several in one repository is fine: each folder is read by the rules of the tool it sits under. devplane config check lists every layout found and how many folders each holds.
- OpenSpec counts equal
openspec list --jsonon the same folder: every line oftasks.mdis read,xorXin a box is done,[~],[-]and[]are open. A folder that only wraps other change folders is not a change. A change under a schema other thanspec-drivenshows no count and says so. Whenopenspec/config.yamlnames astore:and the project holds nospecs/orchanges/, Devplane says the changes live in that store and reads nothing outside the project. - GSD keeps no task file, so every phase folder is listed with no count and the sentence this layout keeps no task file, so Devplane claims no progress for it. Workstreams under
.planning/workstreams/are not listed. If.planning/is gitignored, name it in.worktreeincludeso the change’s worktree has it. - Kiro is recognised by
.kiro/specs/, not.kiro/.
Any other layout is one line:
# devplane.toml
[spec]
plans = "docs/plans" # where this repository keeps its specs
open_questions = ["NEEDS CLARIFICATION", "TBD"]open_questions has no default. Each line carrying one of those words in an open change’s spec is its own inbox item. Every key: [spec].
Naming the spec a change answers
devplane change start "password reset" --spec specs/001-password-reset
devplane change adopt feat/password-reset --spec specs/001-password-reset- The path is repository-relative and must be committed on the base; an uncommitted folder is not in the agent’s checkout.
- Each gate run stamps a fingerprint of the folder’s Markdown on the change, so the certificate says which version was checked.
$ devplane change show c-019a
spec specs/001-password-reset at 3f9a1c40b7e2d518 over 4 documents
tasks 11 ticked · 9 seen by a passing checkDrafting a spec with the project’s own tool
Devplane runs the drafting command your project already uses, through the agent you choose, in the change’s worktree. It writes none of the spec.
devplane change draft rate-limit --agent claude -- \
Limit login attempts per account. Lock the account after five tries in a row.
devplane change review <id> # the drafted folder, file by file
devplane change draft-accept <id> # or: devplane change draft-reject <id>- What is sent: the tool’s own command, then your words verbatim, nothing else.
--dry-runprints it.- Spec Kit: spelled the way that agent invokes it, from
.specify/integration.json—/speckit-specify,/speckit.specify,$speckit-specify(Codex) or/skill:speckit-specify(Kimi).SPECIFY_FEATURE_DIRECTORYnamesspecs/<next number>-<name>, numbered the project’s way and never reusing another open change’s number;SPECIFY_FEATURE_NO_PERSIST=1keeps Spec Kit (1.0.11 or later) from recording an active feature. - OpenSpec: the propose workflow installed for that agent —
/openspec-propose($openspec-proposefor Codex), else/opsx:proposeor/opsx-propose. It names its own change folder.
- Spec Kit: spelled the way that agent invokes it, from
- Refused before anything starts, saying what to do: no words; no Spec Kit integration for that agent (
specify integration install <agent>); no OpenSpec workflow for it (openspec init --tools <agent>); both tools and no--methodology; a project whose only layout is Kiro or GSD. - A driven run: every permission reaches you or a rule you wrote, and is recorded. While the draft waits for you, no gate, base check or drift check runs on it.
- Reviewed as a diff: other files the run touched are listed under not asked for. Accept makes the folder the change’s spec and commits nothing. Reject puts the worktree back as it was at dispatch and removes the folder; the run and its decisions stay on the record. One draft per change at a time.
Answering an open question
Each line carrying one of your open_questions words is its own inbox item, quoting the file and line, and gone when the line is. Lines in checklists/ are not questions.
devplane change clarify <id> specs/002-rate-limit/spec.md:42 -- fifteen minutes- Bracketed (
[NEEDS CLARIFICATION: which timeout applies?]): your words replace the bracket. The one-line diff is shown first and written only on--yes, ayat the terminal, or write it in the inbox; a line that changed since it was read is refused. - Unbracketed: Devplane does not edit it. Send your answer to the agent (
devplane change prompt <id> -- <answer>, or send to the agent in the inbox). - No words declared: after a Spec Kit draft the change offers
open_questions = ["NEEDS CLARIFICATION"]as a diff todevplane.toml, written only when you accept. OpenSpec has no such marker, so none is offered.
Sending chosen tasks
--task picks task lines to send; repeat it and mix forms:
# every task line that cites the requirement token REQ-3
devplane change start "reset: token expiry" \
--spec specs/001-password-reset --task REQ-3
# one exact line, file:line relative to the spec folder
devplane change start "reset: email copy" \
--spec specs/001-password-reset --task tasks.md:14- Selected lines go in the prompt and on the run’s record. A selector that matches nothing is refused.
- Requirement tokens are a prefix plus digits (
FR-,NFR-,SC-,REQ-,US-,AC-unless[spec] tokenssays otherwise). A token in both a heading and a task line is an edge; on one side only, a gap. Spec Kit’s[US1]and Kiro’s_Requirements: 1.1, 2.3_count as citations.
Ticked is not verified
A ticked box is the agent’s claim. Two counts are reported, never combined, and neither is verified:
tasks 11 ticked · 9 seen by a passing check
ticked, sent to nobody: tasks.md:31, tasks.md:32
REQ-3 3 tasks · 2 ticked · 0 seen by a passing check- ticked:
- [x]in the task file, read in the change’s worktree. - seen by a passing check: ticked, sent to a run with
--task, and that run finished before a passingcheck. It stays true after a later failure. - A box ticked without being sent is ticked, sent to nobody. With no gates: 11 ticked · no gates declared.
When the spec moves under a run
An edit to the spec folder during a run is raised when the run ends:
devplane change drift <id> --run r-9f2 --tell # tell it what changed
devplane change drift <id> --run r-9f2 --accept # work to what the run sawEither is recorded as your decision.
A spec tool’s validator is a gate
# devplane.toml
[gates]
check = ["openspec validate --strict", "pnpm test -- --run"]The gate inside Spec Kit’s workflow
Devplane ships a Spec Kit extension, devplane, with one command, speckit.devplane.gate (/speckit-devplane-gate in Claude Code). It runs devplane gate and reports what your checks exited with. It is registered as a mandatory after_implement hook, so an agent following /speckit-implement runs your gates before its completion report.
devplane gate install --speckit --dry-run # what Spec Kit will be asked to install
devplane gate install --speckit # runs `specify extension add` for you
specify extension list # devplane is listed- Needs
.specify/and a[gates] check(--anywayskips the second).--eventpicks another hook point. - With
specifyonPATH, Spec Kit installs the extension and writes.specify/extensions.yml. Without it, Devplane createsextensions.ymlwhen absent or prints the entry to paste, and the hook names thedevplane-gateskill, which must be in.claude/skills/(the plugin’s copy does not count). devplane gateexits 0 only on a pass, records nothing, and never waits on a person (flags).
When the agent skips the hook
A hook runs only if the agent follows Spec Kit’s text. The extension also carries a workflow overlay: a shell step after implement that runs devplane gate --json. Spec Kit’s workflow engine runs it, not the model:
specify workflow overlay add .specify/extensions/devplane/overlays/devplane-gate.yml
specify workflow resolve speckit # devplane-gate now follows implement
specify workflow run speckit -i spec="..."A red gate fails the run at devplane-gate (exit 1), with the gate’s JSON under steps.devplane-gate.output in .specify/workflows/runs/<run id>/state.json. Remove it with specify workflow overlay remove speckit devplane-gate and specify extension remove devplane.
Something wrong or out of date? Edit this page.