| name | specops |
| description | Spec-driven development workflow where specs are the source of truth, paired with a plan protocol for tracking work-in-flight as a micro-DAG. Use this skill whenever starting new features, planning implementation, writing specs, reviewing code against specs, working in or with a `plans/` directory, closing out a plan, or when the user mentions "spec", "specs/", "spec-first", "plans/", "plan protocol", "closeout commit", or asks how something should work or what to work on next. Also use when creating a new project that will use this development methodology, or when onboarding someone to a spec-driven codebase. |
Spec-Driven Development (SpecOps)
Philosophy
Specs declare the complete desired state of the software. Implementation follows spec. All work begins with a spec update.
This is not documentation-driven development (where docs describe what was built). This is specification-driven development — the spec describes what should exist, and the implementation is brought into conformance with it. The spec leads; the code follows.
Why this matters
When agents or developers implement features, they make hundreds of micro-decisions. Without a spec, each decision is a guess that may or may not match the user's intent. With a spec, those decisions are already made — the implementer's job is execution, not invention. This is especially powerful for AI-assisted development where multiple agents may work on different parts of the same system.
The core loop
1. Spec change → propose what should be true
2. Accept → reviewer agrees on desired state
3. Implement → bring code into conformance
4. Verify → compare running software to spec
Code without a corresponding spec is unspecified behavior — it may exist for practical reasons, but nothing guarantees it. Spec without corresponding code is a known gap — track it.
How to write specs
The right level of detail
Specs declare what must be true, not how to implement it.
Right level — declarative state + rules:
"Each row shows: from_stop_name, to_stop_name, pathway_mode label, completion fraction (populated field count / applicable field count). Sort: incomplete first, then alphabetical by from_stop_name. A pathway is 'on this level' when both its from_stop and to_stop share the same level_id."
This tells an implementer what must be true without dictating how. It's testable — you can look at the screen and verify conformance.
Too vague — feature narratives:
"The task list shows pathways grouped by level with completion tracking."
An agent reading this still has to make hundreds of decisions. Which fields? What sort order? What happens when data is missing?
Too detailed — implementation pseudocode:
"Query pathways WHERE from_stop.level_id == to_stop.level_id, LEFT JOIN field_notes, ORDER BY field_complete ASC, render each as a <li> with..."
This is just writing the code twice. The spec rots the moment implementation diverges.
Encode the philosophy, not just the enumerated rules
Two words, one idea. A project's philosophy is its guiding stance — the why behind how it behaves ("we're offline-first"; "the field user comes before the desk analyst"). You write that philosophy down as principles: individual, decisively-stated rules, each picking a side of a trade-off. The philosophy is the whole; a principle is a unit of it. So "encode the philosophy" concretely means: capture its principles where implementers will read them. Throughout this skill, "philosophy" names the stance and "principle" names the written unit — principles.md and the ## Principles section of a spec hold principles.
Recall why this skill exists: implementers make hundreds of micro-decisions, and the spec is what makes those decisions match intent. Enumerated rules can only cover the decisions you anticipated — the cases you thought to write down. A principle covers the rest: it's a generative rule that resolves the cases no enumeration reaches, the same way the author would have.
The two belong together:
- Enumerated rule — "Sort incomplete pathways first, then alphabetical by from_stop_name." Resolves one specific case.
- Principle — "This is a field-data-entry tool used one-handed on a phone in transit stations. When a display or interaction decision is in tension, optimize for the surveyor mid-walk over the analyst at a desk." Resolves every case the enumeration missed — the same way the author would have.
A principle earns its place in a spec only if it's decisive: it must pick a side of a real trade-off and rule things out. Compare:
- Not a principle (platitude — rules nothing out): "The UI should be clean and user-friendly."
- A principle (decisive — an implementer can act on it): "Prefer showing stale data with a freshness timestamp over blocking the screen on a refresh. A surveyor underground with no signal must always see the last-known state."
The test is the same as for any spec: could two implementers read it and disagree about whether the code conforms? "User-friendly" — yes, so it's useless. "Stale-with-timestamp beats blocking" — no, so it's enforceable.
Where principles live — default to the most specific spec the principle governs. A principle that only shapes one screen belongs in that screen spec's ## Principles section, right next to the rules it backs, where an implementer reading that spec will actually see it. Reserve principles.md for the genuinely project-wide ones that no single feature spec owns. When a local principle turns out to govern decisions beyond its home spec, promote it to principles.md and leave a pointer behind. The concrete trigger to watch for: the same or a similar principle showing up in a second spec — that duplication is the signal it's outgrown any one spec, so lift it once and replace both copies with references rather than letting two near-identical statements drift apart. This keeps principles.md short and high-signal instead of a dumping ground — and keeps each principle close to the work it steers. Don't agonize over the placement at capture time: put it on the spec you're in, promote later if it spreads.
Reference the relevant project principles down into each spec. Promotion sends principles up; this is the flow back down. Agents (and people) routinely dive into one or two specs without ever opening principles.md — so a project-wide principle that quietly governs a screen is invisible to whoever's working on it. Counter this: in a spec's ## Principles section, name the principles.md entries that especially bite on this spec, each with a one-line gloss of how it applies here, and link to the full statement. The implementer reading just this one spec then inherits the governing principles instead of missing them. Be selective — reference only the principles that genuinely shape this spec's decisions, not the whole list; a spec that links every principle teaches nothing. When you promote a local principle up to principles.md, the pointer you leave behind is one of these references.
What specs should cover
- Display rules — what data appears and under what conditions
- Data requirements — where data comes from (API, local DB, derived)
- Actions — what the user can do and what each action causes
- Navigation — where you can go from here, where you came from
- Business rules — calculations, state machines, validation logic
- API contracts — request/response shapes, auth, error cases
- Principles — the decisive rules that resolve the unspecified micro-decisions consistently — your philosophy, written down (see Encode the philosophy above)
What specs should NOT cover
- Visual design — colors, spacing, fonts. That's wireframes + theme constants.
- Widget/component decomposition — how screens break into classes. Implementation decision.
- Test cases — tests derive from specs but aren't the spec.
- Variable names, file paths — implementation details that change freely.
Spec directory structure
Organize specs by what they describe, not by when they were written:
specs/
├── README.md # Workflow docs, directory layout, format conventions
├── principles.md # Project-wide principles — the decisive rules (your philosophy, written down) that resolve unspecified decisions
├── architecture.md # Tech stack, project structure, foundational decisions
├── data-model.md # Schema, field definitions, relationships
├── api/ # One file per endpoint or endpoint group
│ ├── conventions.md # Auth, versioning, error envelope, content types
│ └── <endpoint>.md
├── screens/ # One file per screen/route
│ └── <screen-name>.md
└── behaviors/ # Cross-cutting rules that span multiple screens
└── <behavior>.md
principles.md — the project's philosophy written down as principles: the decisive, cross-cutting rules that resolve micro-decisions no enumerated rule reaches. Distinct from architecture.md (concrete tech and structure choices) — principles are the value judgments that pick a side when two reasonable implementations conflict. A principle local to one screen or behavior lives in that spec's ## Principles section; principles.md holds the ones that apply everywhere — and feature specs reference those down into their own ## Principles sections.
screens/ — one file per screen/route. What the user sees and can do at that URL.
behaviors/ — rules that span multiple screens. When a screen spec says "completion fraction", the completion behavior spec defines how it's calculated.
api/ — the contract between client and server. Both sides implement to these specs.
Spec file templates
Screen spec
# Screen: <Name>
## Route
The path/URL for this screen.
## Data Requirements
What data this screen needs and where it comes from.
## Display Rules
Declarative description of what appears and under what conditions.
This is what a reviewer checks the implementation against.
## Actions
What the user can do and what each action causes.
## Navigation
Where you can go from here, where you came from.
## Principles (optional)
The decisive rules governing this screen — what to favor for any case the rules
above don't enumerate.
**Inherited** — project principles from `principles.md` that especially bite here:
- [Offline-first beats fresh](../principles.md#offline-first-beats-fresh) — this screen
must render last-known state instantly; treat the live refresh as a background nicety.
**Local** — principles owned by this screen (promote to `principles.md` if they spread):
- ...
Omit either subsection if empty. Reference only principles that genuinely shape this
screen's decisions — not the whole list.
Behavior spec
# Behavior: <Name>
## Rule
The invariant or rule, stated declaratively.
## Applies To
Which screens or components this behavior affects.
## Details
Edge cases, calculations, timing, error handling.
## Principles (optional)
Same two-part shape as the screen template: **Inherited** — linked `principles.md`
entries that especially govern this behavior, each with a one-line gloss of how it
applies; **Local** — the decisive trade-off behind this rule, if any (promote if it
spreads). Omit either subsection if empty.
API spec
# API: <Name>
## Endpoint
Method, path, auth requirements.
## Request
Parameters, body shape with field types.
## Response
Success body shape with field types. Error cases.
## Notes
Caching, idempotency, offline implications.
## Principles (optional)
Same two-part shape as the screen template: **Inherited** — linked `principles.md`
entries that especially govern this endpoint, each with a one-line gloss; **Local** —
the decisive trade-off for this endpoint (e.g. what to favor when consistency and
latency conflict), promoted if it spreads. Omit either subsection if empty.
Principles (principles.md)
# Principles
The project's philosophy, written down as principles. Each is decisive: it picks a
side of a real trade-off so an implementer can resolve an unspecified case the way
the author would.
## <Principle name>
The principle, stated as a rule that rules something out. Include the *why* — the
context that makes the trade-off land — so a future reader applies it correctly.
> Example: **Offline-first beats fresh.** This tool runs in stations with no signal.
> When freshness and availability conflict, always show last-known state with a
> timestamp rather than block on a network round-trip.
Give each principle a short, stable ## <Name> heading — that heading is its anchor, and feature specs link to it (principles.md#<name>) from their ## Principles sections. References run one direction only: specs point up at principles.md. Don't maintain a "governed by these specs" list inside principles.md — like a hand-drawn DAG, it would rot the moment a spec changed. To find what a principle governs, grep for links to its anchor.
Any screen, behavior, or API spec may also carry an optional ## Principles section for a principle local to that one spec — same bar (decisive, rules something out). Promote it to principles.md once it starts governing decisions beyond that one file.
How agents use specs
When implementing a feature or fixing a bug:
- Read the relevant spec first. Every screen, endpoint, and behavior has a spec file. Read it before writing code.
- The spec answers "what", not "how". It says what data appears, what actions exist, what rules apply. It does not dictate widget trees or class hierarchies.
- If the spec is ambiguous, clarify the spec — don't guess and code. Propose a spec amendment.
- If the spec is wrong, fix the spec — don't work around it in code.
- When done, check your work against the spec — every display rule, every action, every conditional.
When starting a new feature
- Write or update the spec files first
- Get the spec reviewed and accepted
- Then implement to match the spec
- Verify the running software matches what the spec says
When fixing a bug
- Check if the behavior is specified — if so, the spec is right and the code is wrong
- If the behavior is unspecified, decide: should the spec be updated to cover this case, or is the fix obvious enough to just code?
- For non-trivial fixes, update the spec first so the fix is documented
When reviewing code
Compare the implementation against the spec, not against your own ideas of how it should work. The spec is the acceptance criteria.
ALWAYS: watch for decisions and principles that belong in a spec
This is a standing responsibility, not a phase you enter and leave. While doing any work — implementing, debugging, reviewing, or just talking a problem through with the user — stay alert for the moment a key decision gets made or a principle gets resolved (the project's philosophy crystallizing into a rule you could write down). These moments are easy to miss because they feel like progress, not like spec work:
- The user explains why they want something a certain way, revealing a principle that will govern future decisions.
- A trade-off gets settled in conversation ("always favor X over Y when they conflict").
- You hit an unspecified case, pick an answer, and that answer implies a general rule.
- A review comment establishes a convention the whole codebase should follow.
- A "we'll always / we'll never" sentiment surfaces — almost always a principle in disguise.
- You write — or notice — the same (or a barely-reworded) principle in a second spec. Duplication across specs is the loudest promotion signal there is: the principle has outgrown any one spec. Don't leave two copies to drift apart. Lift it to
principles.md once, then replace both copies with ## Principles references to that single entry (see Encode the philosophy). Watch for similar, not just identical — two specs that each say "favor the field user over the desk analyst" in different words are the same principle.
When you notice one, stop and surface it: name the decision, say whether it reads as an enumerated rule or a principle, and propose exactly where it should be operationalized — defaulting to the most specific spec it governs (a screen/behavior/API spec's rules or its ## Principles section), and reaching for principles.md only when it's genuinely project-wide (see Encode the philosophy). Then make (or propose) that spec change through the normal spec-first flow before it evaporates.
The bar for flagging: if a decision would change how a future implementer resolves a micro-decision, it belongs in a spec. If it only affected this one line, let it go.
Why this is load-bearing: a decision that lives only in code, a commit message, or a chat scrollback is unspecified behavior the moment the context window closes. The next agent will re-litigate it — possibly differently — and the codebase drifts. This vigilance is what keeps specs generative rather than merely descriptive. Specs aren't written once at feature-kickoff and frozen; they accrete the project's resolved judgment, and most of that judgment gets resolved in the middle of doing something else.
Plans: the work DAG that bridges specs to code
Specs describe state (what should be true forever). Plans describe motion (how we're getting there next). Every chunk of feature work starts with a plan file in plans/ declaring its scope, the specs it implements, its dependencies on other plans, and concrete validation criteria. The plan files together form a micro-DAG that is the project's working plan.
Plans are temporal — once merged, they freeze as historical record. Their merged-PR links plus completed validation criteria are the project's working memory of what got built, how, and what was deferred.
A plan's frontmatter:
---
status: planned
depends: [other-plan-slug]
specs:
- specs/architecture.md
upstream-specs:
- other-repo:specs/behaviors/transactions.md
issues: [128]
pr: 42
---
A plan's body has a fixed template: Scope, Implements, Approach, Validation (load-bearing checkbox list — converts "in-progress" to "done"), Risks / unknowns, Notes (populated at closeout), Follow-ups (populated at closeout).
The full protocol — frontmatter schema, body template, status lifecycle, the closeout-commit ritual, the Follow-ups taxonomy (Issue / Deferred to plan / Tracked as / None), and the deferral-absorption rule — is in references/plans-protocol.md. Read it before authoring or closing out a plan.
Querying the plans DAG
plans/README.md deliberately does not maintain a hand-drawn DAG or a status table — they'd rot the moment someone forgot to update them. The bundled specops CLI queries the authoritative plan frontmatter on demand and emits agent-friendly TOON output:
Plans
scripts/specops next [--include-in-progress] [--slugs-only] [--dir <path>] — Plans ordered by readiness — ready (deps met, nothing awaited) first, then awaiting-external and blocked, with what unblocks the most work on top.
scripts/specops dag [--direction TB|LR|BT|RL] [--fence] [--include-cancelled] [--dir <path>] — Mermaid graph of the plans DAG, nodes styled by status, external blockers dashed.
Session
scripts/specops hook install [--scope project|global] [--dir <path>] | hook status | hook uninstall [--scope project|global] — Manage the SessionStart hook that loads this repo's plans dashboard at the start of every agent session.
Run with no command (scripts/specops) for the current repo's plans dashboard — a readiness summary, what's ready, and what's blocked. Hygiene warnings (dangling deps, a status: blocked plan with nothing recorded as blocking it) are surfaced on stdout where the agent will see them.
The CLI is a thin determinism layer over a files-first workflow: it computes readiness, ordering, the dependency graph, and warnings across all plan files — work an agent can't reliably do by eye. To read or edit a single plan, open its file directly; there is deliberately no view/show command.
It ships as a self-contained bundle at scripts/specops.mjs (invoked through the scripts/specops shim), runs on node ≥ 20 with no npm install and no node_modules, and is rebuilt from the TypeScript in src/cli/ with bun run build. Run scripts/specops hook install once in a repo to load the plans dashboard into every agent session automatically.
Setting up spec-driven development in a new project
- Create a
specs/ directory at the project root
- Write
specs/README.md documenting the workflow and directory layout
- Write
specs/architecture.md with foundational tech decisions
- Write
specs/principles.md capturing the principles you already hold — your project philosophy written down as decisive "always favor X over Y" trade-offs that should steer every implementer. Seed it with what you know now; it grows as decisions get resolved (see keeping specs alive)
- For each feature area, create the relevant spec files before coding
- Reference the specs directory in your project's CLAUDE.md or README
- Establish the convention: PRs that add features should include spec updates
- Set up the spec drift auditor (see below)
- Set up the plans protocol (see below)
Setting up the spec drift auditor
The spec drift auditor is a specialized agent that does an exhaustive comparison of your specs/ directory against the actual implementation, producing tables of gaps, undocumented implementations, and conflicts. To set it up in a project:
-
Copy the agent definition from this skill's references/spec-drift-auditor.md into your project at .claude/agents/spec-drift-auditor.md. Customize the "Methodology" phases to match your project's structure — for example, update Phase 3 ("Inventory the Implementation") to list the specific directories and key files in your codebase (source directories, migration paths, frontend code, infrastructure files, etc.).
-
Copy the command definition from this skill's references/audit-spec-drift.md into your project at .claude/commands/audit-spec-drift.md. This gives users a /audit-spec-drift slash command that launches the auditor agent.
-
Reference in CLAUDE.md — add a note to the project's CLAUDE.md mentioning the auditor is available, e.g.:
## Spec Drift Auditing
Run `/audit-spec-drift` to launch a comprehensive audit comparing specs/ against the implementation.
The reference files are located at:
references/spec-drift-auditor.md — the agent definition (goes in .claude/agents/)
references/audit-spec-drift.md — the command definition (goes in .claude/commands/)
Note: the auditor checks the specs: field of plan files and the specs/ tree. It does not check upstream-specs: (those are owned by other repos by design — see the plans protocol).
Setting up the plans protocol
The plans protocol gives a project a structured way to track work-in-flight without it rotting. To set it up:
- Create the
plans/ directory at the project root.
- Write
plans/README.md that briefly states what plans are (motion vs state) and points at references/plans-protocol.md for the full spec. Resist the urge to maintain a DAG drawing or status table inside it — both rot. The specops CLI regenerates that view on demand.
- Document the protocol in the project's CLAUDE.md — add a Plans section summarizing the workflow (statuses, closeout commit, Follow-ups taxonomy) and link to
plans/README.md. The reference doc in this skill is the canonical source; the project CLAUDE.md just needs enough for someone working in the repo to find their way without re-reading the whole reference.
- Use the
specops CLI in-place from the skill. scripts/specops is the self-contained bundle shipped with this skill; run it from the skill's install directory against the project's plans/ — there's nothing to copy, symlink, or vendor, and no npm install (it runs on node ≥ 20). Claude resolves the skill path automatically when specops triggers; from the project root the invocation is <specops-skill-path>/scripts/specops next (or dag). Optionally run <specops-skill-path>/scripts/specops hook install once so every session in the repo opens with the plans dashboard.
- Establish the convention in the team: a new chunk of work starts with a plan file; the last commit before merge flips it to
done. Quick-reference checklist for closeout is in references/plans-protocol.md.
Keeping specs alive
Specs rot when they diverge from reality. Prevent this by:
- Making spec updates part of the PR process — if the code changes behavior, the spec should change too
- Periodically auditing specs against the running software
- Treating spec-code divergence as a bug, not technical debt
- Having agents read specs before implementing, which creates a natural feedback loop when specs are wrong
- Capturing decisions and principles as they're resolved (see ALWAYS: watch for decisions and principles that belong in a spec) — most of a project's durable judgment gets resolved mid-task, and a spec only stays alive if that judgment lands in it instead of in a scrollback