Devplane

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 the specification with Spec Kit, OpenSpec, Kiro, or a folder of Markdown. Devplane runs the agent, watches it and decides whether the result is done, and adopts no spec format.

devplane change start "password reset" --spec specs/001-password-reset
devplane change show <id>

What Devplane reads

  • the spec is Markdown, committed to the repository;
  • the unit is a folder;
  • the outline is the headings;
  • progress is - [ ] / - [x] in the folder’s tasks.md only (a checklists/ folder is not progress).

No section name is recognised. Layouts found automatically:

ToolDetected bySpec folders
Spec Kit.specify/specs/NNN-name/
OpenSpecopenspec/config.yamlopenspec/changes/<id>/ (not archive/)
Kiro.kiro/.kiro/specs/<feature>/

Any other layout is one line of configuration:

# devplane.toml
[spec]
plans          = "docs/plans"  # where this repository keeps its specs
open_questions = ["NEEDS CLARIFICATION", "TBD"]

open_questions are the words that mark an unanswered question, matched per line, case-insensitively; there is no default. The Specifications view in the workbench lists each project’s spec folders, task counts, and the lines still carrying one of those words.

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 relative to the repository and must exist in the base the worktree branches from; an uncommitted spec folder is not in the agent’s checkout. Every gate run stamps a fingerprint of the folder’s Markdown onto the change, so the certificate says checked against specs/001-password-reset at 3f9a….

$ devplane change show c-3f9a
  spec       specs/001-password-reset at 3f9a1c40b7e2d518 over 4 documents
  tasks      11 ticked · 9 seen by a passing check

Sending chosen tasks

--task picks which lines of the task list go to the agent. Repeat it, or mix the two 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

The selected lines travel in the prompt and on the run’s record. A selector that matches nothing refuses before anything is created.

Requirement tokens are a prefix followed by digits: by default FR-, NFR-, SC-, REQ-, US- and AC-; [spec] tokens replaces the list. A token in a heading and in a task line is an edge; a token on only one side is reported as a gap. Spec Kit’s [US1] marker and Kiro’s _Requirements: 1.1, 2.3_ line are read as citations.

Ticked is not verified

A ticked box is the agent’s claim about its own work, so a change that names a spec reports two counts and never combines them. Neither is verified: only a change is.

  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 of this change with --task, and that run finished before a check that passed, so the check ran over its work. It stays true if a later check fails, which is why it is never called verified.
  • A box ticked by hand is listed as ticked, sent to nobody. With no gates declared the line reads 11 ticked · no gates declared, never 0 seen. No percentage is computed anywhere.

When the spec moves under a run

If the spec folder is edited while an agent works from it, the inbox says so when the run ends (the specification changed 18m into run r-9f2 and the run never saw it), with two answers:

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 saw

--tell prompts the run with the changed files named; --accept makes the change work to what the run saw. Either is recorded as your decision.

A spec tool’s CLI is already a gate

If your spec tool validates from the command line, put it beside your tests:

# devplane.toml
[gates]
check = ["openspec validate --strict", "pnpm test -- --run"]

That checks the specs against each other; your tests compare spec and code.

The gate inside Spec Kit’s workflow

Spec Kit’s commands look in .specify/extensions.yml for hooks, and run and wait for a hook marked optional: false. Devplane’s hook names the skill devplane-gate, which runs devplane gate and reports what it exited with. The agent has to find that skill as a file, so copy it into the project (or into ~/.claude/skills/ for every project) first:

mkdir -p .claude/skills/devplane-gate
curl -LsSf -o .claude/skills/devplane-gate/SKILL.md   https://raw.githubusercontent.com/hupe1980/devplane/main/plugin/skills/devplane-gate/SKILL.md

devplane speckit              # writes .specify/extensions.yml
devplane speckit --dry-run    # print it, write nothing
devplane gate                 # what the skill runs

The Claude Code plugin ships the same skill, but devplane speckit does not look inside a plugin install, so it still refuses until the file is at .claude/skills/devplane-gate/SKILL.md in the repository or your home.

speckit needs Spec Kit initialised here (.specify/). If .specify/extensions.yml already exists, it prints the entry and where to add it instead of editing the file. The hook goes on after_implement unless you pass --event. It refuses when the repository declares no gate (declare [gates] check first) and when the skill file is missing; --anyway writes it regardless, and --dry-run applies the same refusals.

devplane gate runs [gates] check and exits 0 only when it passed; no gates declared and an unreadable devplane.toml exit non-zero. It records nothing, and the hook never waits on a person. States and flags: devplane gate.

Next

Something wrong or out of date? Edit this page.