Files
skills/skills/spec-driven-dev/SKILL.md
T
mathias 1d368d4b5d
CI / Check / Test (push) Successful in 1s
CI / Tag release (push) Successful in 1s
docs(skills): drop the Mode 2 routing notes, fix the contributing steps
Seven skills carried a `## Mode 2 Routing Note` citing a routing pod ("Plan 6")
that does not exist. Several admitted in their own text that nothing about the
skill changes between modes — so it was roughly ten lines of context on every
load for zero behavioural effect. The content is not worthless, just not
per-skill: it now lives as one table in README.md, with the caveat that nothing
depends on it.

The related line inside the session_log sections stays, because that one does
motivate behaviour — but it no longer cites a component that has never shipped.
It now says what is true: pass-rate is what a future routing decision would be
based on, and a skill that never logs contributes no evidence either way.

While in README: the contributing steps were stale enough to fail CI for anyone
who followed them. They said to add a row to SKILLS_INDEX.md by hand — the table
is generated now, and `task check` fails on a hand-edited diff — and listed none
of the four frontmatter keys the gates require. Step 4 now names the
pressure-test bar from #19 rather than leaving it to be discovered.

Closes #36
Bump-Type: patch
2026-08-29 14:50:14 +02:00

12 KiB
Raw Blame History

name, surfaces, summary, use_when, description
name surfaces summary use_when description
spec-driven-dev
claude-code
library
Spec-driven development for PMs Writing specs and requirements Write a structured specification before writing any code. Use when starting a new project, feature, or significant change. Adapted for a PM-first workflow where why comes before how.

Spec-Driven Development

Overview

Write a structured specification before writing any code. The spec is the shared source of truth — it defines what we're building, why it matters, and how we'll know it's done.

Code without a spec is guessing.

A spec doesn't need to be long. A two-paragraph spec beats no spec. The value is in forcing clarity before code is written, not in the length of the document.

Mathias PM Context

As a digital product manager building software:

  • Why before how: The spec must capture the business context and user need before technical decisions. Agents reading the spec should understand why this matters, not just what to build.
  • Explicit success criteria: Vague requirements produce vague results. Every spec must have testable success criteria.
  • Surfaces assumptions: The spec's primary job is to surface misunderstandings before they become expensive code.
  • Living document: Update the spec when decisions change. An outdated spec is still better than no spec.

When to Use

Always create a spec when:

  • Starting a new project or feature
  • Requirements are ambiguous or incomplete
  • The change touches multiple files or modules
  • You're about to make an architectural decision
  • The task would take more than a day to implement

When NOT to use: Single-line fixes, typos, or changes where requirements are unambiguous and self-contained.

The Gated Workflow

Do not advance to the next phase without validation at each gate.

SPECIFY → [review] → PLAN → [review] → TASKS → [review] → IMPLEMENT

Each gate is a deliberate pause: does the next phase make sense given what we know?

Phase 1: SPECIFY

Surface Assumptions Immediately

Before writing spec content, list what you're assuming:

ASSUMPTIONS I'M MAKING:
1. This is a Go backend service (no frontend changes)
2. Authentication is handled by the existing middleware
3. The database is PostgreSQL (matching the rest of the stack)
4. The feature is used by authenticated users only, not public
→ Confirm or correct before I proceed.

Don't silently fill in ambiguous requirements.

Write the Spec

A spec covers six areas:

1. Objective — WHY are we building this?

This is the most important section. It must answer:

  • What user problem does this solve?
  • Who is the user?
  • What does success look like from the user's perspective?
  • Why now?
## Objective

Invoice importers at small accounting firms manually copy payment details
from PDF invoices into their banking system, taking 10–20 minutes per invoice.

**User:** Invoice processor at an accounting firm (10–50 invoices/day)
**Problem:** Manual data entry is slow, error-prone, and creates compliance risk
**Goal:** Reduce per-invoice processing time from ~15 minutes to < 2 minutes

Success: Invoice processor can extract and queue a payment from a PDF in under 2 minutes,
with confidence the data is correct.

2. Commands — Full executable commands

Build:   task build
Test:    task test (or: go test ./...)
Lint:    task lint (or: golangci-lint run)
Dev:     task dev
Deploy:  task deploy:staging

3. Project Structure — Where things live

internal/
  domain/          → Core types and interfaces
  service/         → Business logic
  store/           → Database implementations
  handler/         → HTTP handlers
cmd/
  server/          → Main entry point

4. Code Style — One real example beats three paragraphs

// Error handling: always wrap with context
if err != nil {
    return fmt.Errorf("parse invoice PDF: %w", err)
}

// Dependency injection: accept interfaces
func NewInvoiceService(store InvoiceStore, parser PDFParser) *InvoiceService { ... }

// Context: always first parameter for I/O operations
func (s *InvoiceService) ProcessPDF(ctx context.Context, r io.Reader) (Invoice, error) { ... }

5. Testing Strategy

Framework:  testing package + testify
Locations:  *_test.go files in same package
Unit tests: table-driven, in-memory implementations for stores
Fast path:  go test ./... (unit tests only)
Full suite: go test -tags=integration ./... (includes DB tests)
Coverage:   >80% for business logic packages

6. Boundaries

Always:
  - Run go test ./... before committing
  - Wrap errors with fmt.Errorf("context: %w", err)
  - Pass ctx as first parameter to any I/O function
  - Run govulncheck before adding new dependencies

Ask first:
  - Schema changes
  - Adding new external dependencies
  - Changing public API contracts
  - Performance changes that affect existing behavior

Never:
  - Commit secrets or API keys
  - Remove or skip failing tests
  - Send client data to external APIs
  - Use naked returns on errors

Spec Template

# Spec: [Feature/Project Name]

## Objective
[What we're building and why. User story or problem statement.]
[Who is the user? What does success look like from their perspective?]

## Tech Stack
[Language, key libraries, relevant existing infrastructure]

## Commands
Build:   [full command]
Test:    [full command]
Lint:    [full command]
Dev:     [full command]

## Project Structure
[Directory layout with descriptions]

## Code Style
[One real code example showing the patterns to follow]

## Testing Strategy
[Framework, test locations, what to unit test vs integration test]

## Boundaries
- Always: [...]
- Ask first: [...]
- Never: [...]

## Success Criteria
[Specific, testable conditions that define "done"]
- [ ] [Condition 1: metric/threshold/method]
- [ ] [Condition 2: ...]

## Open Questions
[Anything unresolved that needs input before implementation begins]

EARS-lite acceptance criteria

Measurable criteria answer "is it done?". They do not always answer "what should it do when X happens?" — and behavioural requirements written as prose are where ambiguity survives review. For any criterion that is conditional or event-driven, use EARS-lite instead of prose:

WHEN <trigger> THE SYSTEM SHALL <observable response>
WHILE <state> THE SYSTEM SHALL <observable response>
IF <unwanted condition> THEN THE SYSTEM SHALL <observable response>

There is a fourth, unconditional form. Use it for invariants — the properties that must hold with no trigger at all, which are usually the ones that matter most:

THE SYSTEM SHALL <observable response>

Worked, deliberately from a domain unlike whatever you are specifying:

THE SYSTEM SHALL reject a firmware image whose signature does not verify
WHEN a sensor reports a temperature above the shutdown limit
  THE SYSTEM SHALL open the contactor within 200ms
WHILE the contactor is open
  THE SYSTEM SHALL refuse every start command
IF a sensor reading is missing or stale by more than one interval
  THEN THE SYSTEM SHALL treat it as above the limit

The example is off-domain on purpose. An on-domain example gets copied: a spec written against an invoice-flavoured example inherited a requirement about extraction confidence that its feature request never mentioned, and the author only noticed on review. Read the shapes, not the content.

Each conditional line names a trigger and one observable response, which usually maps onto a single test. The WHILE and unconditional forms are universally quantified — "every start command", "a firmware image" — so they map onto a property or a set of cases, not one assertion. That is still worth writing: it closes the requirements half of the audit trail, which is otherwise issue-body prose.

This is an option, not a replacement. Threshold criteria ("p99 < 200ms under 100 RPS") stay as they are — EARS phrasing adds nothing to a number. Use it for conditional behaviour, error handling, and state-dependent rules.

For the executor-facing handoff, feature-spec also carries a Context snapshot section — inline the architecture facts the implementer cannot look up.

Reframing Vague Requirements

When you receive a vague requirement, translate it into specific success criteria before writing any spec content:

Vague: "Make the invoice parser more reliable"

Reframed success criteria:
- Parser correctly extracts IBAN from 95% of Swedish invoice formats
- Parser correctly extracts total amount from 98% of tested invoices
- Parser returns a structured error (not a panic) for unrecognized formats
- Processing time < 2 seconds for PDFs up to 10MB
→ Are these the right targets?

Phase 2: PLAN

With a validated spec, create a technical implementation plan:

  1. Identify major components and their dependencies
  2. Determine implementation order (foundations first)
  3. Note risks and unknowns
  4. Identify what can be built in parallel vs. what must be sequential
  5. Define verification checkpoints between phases

The plan should be reviewable: anyone should be able to read it and say "yes, that's the right approach" or "no, change X."

Load the planning skill for detailed task breakdown.

Phase 3: TASKS

Break the plan into discrete, implementable tasks. Load the planning skill for the full task breakdown methodology.

Each task must have:

  • Acceptance criteria
  • Verification step (test command, build, manual check)
  • File count estimate (no task should touch more than ~5 files)

Phase 4: IMPLEMENT

Execute tasks one at a time. For each task:

  1. Load tdd skill — write failing tests first
  2. Implement minimal code to pass
  3. Load clean-code skill — refactor

Keeping the Spec Alive

  • Update when decisions change — spec first, then code
  • Update when scope changes
  • Commit the spec — it belongs in version control
  • Reference the spec in PRs — link to the section each PR implements

Common Rationalizations

Rationalization Reality
"This is simple, I don't need a spec" Simple tasks still need acceptance criteria. A two-line spec is fine.
"I'll write the spec after" That's documentation, not specification. The spec's value is forcing clarity before code.
"The spec will slow us down" A 15-minute spec prevents hours of rework.
"Requirements will change anyway" That's why the spec is a living document.
"The user knows what they want" Even clear requests have implicit assumptions. The spec surfaces those.

Verification

Before starting implementation:

  • Spec covers all six core areas
  • Mathias has reviewed and approved the spec
  • Success criteria are specific and testable
  • Boundaries (Always/Ask First/Never) are defined
  • Open questions are resolved or accepted as unknowns
  • Spec is saved to a file in the repository

Brain MCP Integration

Logging

Call session_log once at the end of every phase to record the outcome. Pass-rate is computed downstream by the /pass-rate HTTP endpoint, which treats pass as success, fail as failure, skip as neither.

At end of each phase:

  • session_log with {session_id: "<this session's id>", skill: "spec-driven-dev", phase: "<phase-name>", final_status: "pass" | "fail" | "skip", message: "<one-line summary>", project_root: "<absolute path>"}

Phases for this skill: specify, plan, tasks, implement

Status semantics:

  • pass — the phase's intended outcome was reached (gate passed).
  • fail — the phase's intended outcome was NOT reached (gate blocked, rework required).
  • skip — phase was skipped intentionally.

Why this matters: pass-rate is what a future routing decision would be based on — whether a given call can be served by a local model instead of a cloud one. A skill that never logs contributes no evidence either way.

Cross-References

  • Load problem-analysis skill for deep requirement understanding before speccing
  • Load user-stories skill to decompose the spec into stories
  • Load planning skill for task breakdown
  • Load feature-spec skill once implementation begins, to scope individual features inside the project
  • Load tdd skill during implementation