Spec-driven development on OpenSpec, with mechanical spec-as-source enforcement: a custom 'spec-as-source' OpenSpec schema adds file-ownership (targets) and test-verification ([@test]) metadata to every capability spec, three scripts (link check, ownership check, manifest build) keep code and specs from drifting apart, plus requirement-gathering, spec-writer, work-review, and a session-handoff skill with a proactive context-warning hook.
68
85%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
Reproduce the shape of a plan-mode session one level above the change: explore
read-only, write a plan toward the goal, hand control back to the human for
approval, and only then let work start. The difference from a conversational
plan is that this one lives in the repo — openspec/PLAN.md — and is checked by
a script rather than remembered.
tasks.md plans the inside of one change and is archived with it. This plans
which changes are needed at all, in what order, to reach the goal.
openspec/. If it does not, this is a spec-as-source-setup
job first — the plan has nothing to sequence yet.rules/plan-mode.md before writing anything. It is the normative source
for the location, the fields, the states and the approval discipline; this
skill is only the procedure for producing them.Investigate before proposing a sequence. Write no file in this phase except the plan itself — no changes created, no specs, no code.
Run the deterministic inspector before reading any product document and before
modifying openspec/PLAN.md:
python3 skills/plan-mode/scripts/product_context.py --project-root "$PWD"If the command exits non-zero or reports INVALID-PRODUCT-CONTEXT, report the
diagnostic and stop. Do not read a product source, do not attempt conventional
fallback, and leave openspec/PLAN.md byte-identical.
Treat the successful JSON inventory as the exclusive allowlist for product context. Do not read any path absent from that inventory, do not scan the repository or home directory for alternatives, and do not invoke a skill to generate a missing product document.
Handle each source bucket from the inventory without guessing:
fresh or stale, read only the listed, existing file items from a
non-conflicting bucket. A stale file is read at its current content so the
next draft can update the recorded digest.missing or not configured, read nothing and record the state as a gap;
neither state blocks the existing draft-and-approval workflow.conflict, preserve every candidate path but do not choose or read any
candidate. Record the path conflict as unresolved for the human.Synthesize only material that can change the planned work:
Every sourced claim uses exactly path § heading; a line number may accompany
the heading but never replace it. Never dump a whole product document, copy
unrelated backlog items, or invent content for an absent source. When admitted
sources disagree semantically, cite both sides, label the conflict unresolved,
and leave the decision to the human — never reconcile it silently.
Product context is evidence, not authority. It must not approve an entry,
change its state, or authorize implementation, and it never creates a second
gate beside openspec/PLAN.md and scripts/check-plan-gate.sh.
What to establish, with evidence rather than assumption:
openspec/PLAN.md exists, its goal: is the goal — do not
restate it in new words. If not, derive it from the user, from
.handoff/HANDOFF-NNN.md 🎯 Goal, and from the repo, then confirm it.openspec/specs/ (what capabilities exist),
openspec/changes/ (what is active), openspec/changes/archive/ (what was
already done — read the proposals, not just the names), git log..handoff/HANDOFF-NNN.md Next
Steps and any "inherited" items are exactly the medium-term intentions that
have had nowhere to live; they are candidate entries.Ask when the goal or the ordering is genuinely ambiguous. A plan built on a guessed goal sequences the wrong work confidently.
Create openspec/PLAN.md from templates/PLAN.md if it does not exist, then
write entries.
For each drafted or amended entry, write or update the entry's Product context
block from the Phase 1 inventory and synthesis. Carry the source state, admitted
path, current SHA-256, verification command, headings actually read,
decision-relevant summary and unresolved gaps or conflicts. Keep this block
outside the entry's contract fields; it does not alter approval by itself. Do
not discover or read additional product files during drafting.
Every new entry starts in draft. Appending never touches another entry's
approval — that is the point of per-entry approval, and it is why the plan can
grow without ceremony.
Writing good entries:
Done when must be checkable by someone else without asking you: a command
that exits 0, a file that exists, a behaviour that can be reproduced. If you
cannot write it that way, the entry is still an intention, not a plan item.Depends on is the only ordering primitive. No dates, no estimates, no
assignees — this is not a scheduler.none for work that produces no OpenSpec
change). If an entry needs two changes, it is two entries.dropped with the
reason, so the plan keeps the record of the decision.Present the drafted entries to the human: id, title, Done when, and what each
one commits to. Then stop and end the turn.
Do not create a change, write a spec, or touch implementation files in the same turn in which you request approval. Requesting approval hands control back — the same way plan mode does. A turn that asks for approval and then proceeds has not asked for anything.
When the human approves, and only then, fill the approval block:
bash scripts/check-plan-gate.sh --hash E02 # never compute this by hand
date "+%Y-%m-%d %H:%M" # never guess the timestampThen set Approved by to the human's name, Approved at to that clock read, and
Approval hash to the printed value.
Never write an approval block the human did not give. Computing the hash early is fine; filling the block is not. This is the one human checkpoint in the method — an agent that fills it in on its own turns it into decoration.
draft. Nothing else is affected; no
re-approval of anything.Change, Depends on,
Done when): the gate will report HASH-MISMATCH for that entry alone. Show
the human what changed and ask them to re-approve, then recompute the hash.approved → in-progress → done): free. State is not a
contract field precisely so that progress never needs re-approval.dropped and write the reason in the entry.Each state transition gets one HISTORY.md line, tag plan-mode, written
entry <id> <old> → <new> — see rules/history-log.md. Rewording or drafting is
not a transition and gets no line.
Run the script; do not judge the plan by eye.
bash scripts/check-plan-gate.sh| Reason | What happened | What to do |
|---|---|---|
NO-ENTRY | an active change has no entry binding it | draft the entry, get it approved — or the change should not have been started |
NOT-APPROVED | the entry exists but has no valid approval block | ask the human; do not fill it yourself |
HASH-MISMATCH | the contract changed after approval | show the diff, ask to re-approve, recompute |
STALE | the change is archived but its entry is not done | set the entry to done |
NO-ENTRY on a change that already exists is worth a pause rather than a quick
fix: it means work started outside the plan. Adding a retroactive entry is
sometimes right, but say so explicitly instead of quietly papering over it.
When openspec/PLAN.md exists and every entry is draft, cede control to the
separate plan-judge capability for its Round 1 review. The handoff is only
for a fully draft plan: when the plan is absent or any entry is not draft,
retain the existing plan-mode workflow instead.
plan-judge returns a rewritten Markdown artifact at a human checkpoint. It
does not approve entries, calculate an approval hash, advance a state or modify
ROUTER.md; plan-mode and the human retain those responsibilities.
An approved entry is what openspec-propose needs in order to run: the router
checks scripts/check-plan-gate.sh --change <name> before routing there, and
refuses with the reason verbatim if it fails.
When a change is archived, come back and set its entry to done — otherwise the
gate reports STALE and the plan stops describing reality.
.tessl-plugin
rules
skills
handoff
handoff-skill
openspec-apply-change
openspec-archive-change
openspec-explore
openspec-propose
openspec-sync-specs
plan-judge
plan-mode
prompt-loop
requirement-gathering
spec-as-source-setup
templates
openspec-schema
spec-as-source
templates
spec-ci-sync
spec-loop
spec-rebuild
spec-verify
spec-writer
work-review