CtrlK
BlogDocsLog inGet started
Tessl Logo

spec-driven-development/spec-as-source

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

Quality

85%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Low

Low-risk findings worth noting

Overview
Quality
Evals
Security
Files

SKILL.mdskills/plan-mode/

name:
plan-mode
description:
Builds and maintains openspec/PLAN.md, the human-approved plan that sequences OpenSpec changes toward a stated goal — the tier above tasks.md. Explore read-only, draft entries, then stop and ask for approval; a change cannot start until its entry is approved. Trigger — make a plan, what should we build first, plan the work, roadmap, add this to the plan, approve the plan, why is the plan gate failing, NO-ENTRY, HASH-MISMATCH.

Plan Mode

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.

Preconditions

  • The project has openspec/. If it does not, this is a spec-as-source-setup job first — the plan has nothing to sequence yet.
  • Read 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.

Phase 1 — Explore (read-only)

Investigate before proposing a sequence. Write no file in this phase except the plan itself — no changes created, no specs, no code.

Product-context preflight (first)

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:

  • For 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.
  • For missing or not configured, read nothing and record the state as a gap; neither state blocks the existing draft-and-approval workflow.
  • For conflict, preserve every candidate path but do not choose or read any candidate. Record the path conflict as unresolved for the human.
  • Multiple admitted ADR files are independent sources, not a path conflict.

Synthesize only material that can change the planned work:

  • PRD: outcomes, scope and out-of-scope relevant to the entry.
  • personas: only needs or JTBD that affect a priority or requirement.
  • TECH-STACK and ADR: decisions, constraints and forbidden patterns.
  • backlog: only priority, current sprint and relevant carry-over.

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:

  • The goal. If 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.
  • Where the project actually is. 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.
  • What is already written down as intent. .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.

Phase 2 — Draft

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:

  • Title the outcome, not the activity. "Router refuses changes outside the plan", not "work on the router".
  • 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.
  • One entry, one change (or none for work that produces no OpenSpec change). If an entry needs two changes, it is two entries.
  • Never delete an entry. Work decided against becomes dropped with the reason, so the plan keeps the record of the decision.

Phase 3 — Request approval, then stop

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 timestamp

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

Amending an approved plan

  • Adding an entry: append it in draft. Nothing else is affected; no re-approval of anything.
  • Changing an approved entry's contract (title, 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.
  • Advancing state (approved → in-progress → done): free. State is not a contract field precisely so that progress never needs re-approval.
  • Dropping: set 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.

Reading a gate failure

Run the script; do not judge the plan by eye.

bash scripts/check-plan-gate.sh
ReasonWhat happenedWhat to do
NO-ENTRYan active change has no entry binding itdraft the entry, get it approved — or the change should not have been started
NOT-APPROVEDthe entry exists but has no valid approval blockask the human; do not fill it yourself
HASH-MISMATCHthe contract changed after approvalshow the diff, ask to re-approve, recompute
STALEthe change is archived but its entry is not doneset 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.

Handoff to the rest of the workflow

Delegation to plan-judge

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.

skills

plan-mode

README.md

tile.json