OpenSpec leerlijn — Deel 1: Wat is OpenSpec?
Een korte introductie op OpenSpec, het spec-first framework dat onder vrijwel al onze projecten draait. Wat een spec is, wat een change is, en waarom we dingen eerst opschrijven voor we ze bouwen. Eerste van twee korte modules.
OpenSpec is een lichtgewicht framework voor spec-driven development: eerst opschrijven wat een feature moet doen, dan pas code schrijven. In deze module van twaalf minuten leer je wat OpenSpec is, welke begrippen erin zitten, en waarom we het bij Conduction onder vrijwel elk project gebruiken. Aan het eind sta je klaar voor deel 2, waarin je je eerste change daadwerkelijk schrijft.
In één zin
OpenSpec is een lichtgewicht conventie voor het bijhouden van requirements naast je code. De spec leeft in je repo, in Markdown, en evolueert per change in een nette spec-delta — net zoals je code evolueert per commit.
Geen Confluence-pagina die uit sync raakt met de code. Geen Jira-ticket dat alleen jij begrijpt. Geen losse README waarin de echte regels verstopt zitten tussen voorbeelden. Eén plek voor het wat, één plek voor het hoe.
Waarom spec-first?
In de praktijk gaat code schrijven sneller dan een spec bedenken. Dus waarom eerst die extra stap?
- Aannames vroeg blootleggen. Wat lijkt op één feature blijkt vaak vier features tegelijk. Door eerst de scenario's op te schrijven, ontdek je gaten en tegenstrijdigheden vóór de code geschreven is — niet erna in een PR-review.
- AI-agenten productief maken. Onze Hydra-pipeline bouwt code uit specs. Hoe scherper de spec, hoe minder bijsturen achteraf. Dezelfde wet geldt voor je menselijke teamleden in een handover.
- Een audit-trail die niet jokt. Elke change zit in een eigen mapje met een proposal, een spec-delta, een design en een takenlijst. Een halfjaar later weet je nog precies wat er besloten is en waarom.
- Reviewers één bron. Reviewer ziet de spec-delta en de code naast elkaar. Klopt de code met de spec? Niet "wat had de ontwikkelaar bedoeld", maar "wat staat er".
De vier kernbegrippen
OpenSpec rust op vier woorden die je terug zult zien in elk project. Een halfuur investeren in deze begrippen scheelt later veel verwarring.
Hoe ze samenhangen
openspec/
├── specs/ ← stabiel — capabilities
│ └── publications/spec.md ← bevat REQ-001, REQ-002, … en scenarios
└── changes/ ← tijdelijk — wijzigingen in flight
└── add-publication-search/
├── proposal.md ← waarom en wat
├── specs/publications/ ← ADDED / MODIFIED / REMOVED requirements
│ └── spec.md
├── design.md ← hoe (technisch)
└── tasks.md ← implementatie-checklist
Een change voegt requirements toe aan, wijzigt of verwijdert requirements uit een spec. Zolang de change open staat, leeft de delta in changes/. Bij archivering verhuist de delta naar de hoofd-spec en gaat het hele mapje naar changes/archive/YYYY-MM-DD-<name>/. Daarmee houd je een complete geschiedenis.
Een requirement in het wild
Even concreet. Dit is een echte requirement zoals je ze in onze repos tegenkomt:
### REQ-001: Volledige tekstzoekfunctie
Het systeem MOET full-text zoeken ondersteunen over publicatie-titels
en -inhoud met behulp van PostgreSQL's tsvector.
#### Scenario: Zoekopdracht levert overeenkomende publicaties op
- GIVEN publicaties met titels "Klimaatrapport 2026" en "Begrotingsoverzicht"
- WHEN een gebruiker zoekt op "klimaat"
- THEN MOETEN de resultaten "Klimaatrapport 2026" bevatten
- AND MOGEN de resultaten "Begrotingsoverzicht" NIET bevatten
- AND MOETEN de resultaten op relevantiescore gesorteerd zijn
Drie dingen om te zien:
- MOET is geen stijlkeuze. RFC 2119 maakt onderscheid tussen MUST (absoluut), SHOULD (sterk aanbevolen, uitzonderingen mogelijk) en MAY (optioneel). Een reviewer leest dat woord en weet wat de bedoeling is.
- Het scenario is concreet genoeg om een test uit te schrijven. Iemand anders zou hetzelfde gedrag moeten kunnen bouwen zonder met je te overleggen.
- Implementatie-vrij. Geen klassennamen, geen controllers, geen tabelnamen. Die horen in
design.md, niet hier. De spec beschrijft gedrag, niet implementatie.
Spec versus README versus issue
Een veel gestelde vraag bij nieuwe mensen: "wat schrijf ik dan nog in de README, en wat in een issue?" Eenvoudige vuistregel:
| Document | Bevat | Lezer | Updatefrequentie |
|---|---|---|---|
openspec/specs/ | Het wat — requirements en scenario's. Stabiel gedrag van de app. | Reviewers, AI-agenten, latere developers | Per change |
openspec/changes/ | Een verandering — voorstel, delta, design en tasks van één in-flight change. | Reviewers tijdens de PR | Eénmalig per change |
README.md | Hoe zet je de app op. Hoe draai je hem. Hoe lever je iets aan. | Nieuwe developers, eerste sessie | Bij doorbroken setup-stap |
| GitHub Issue | Eén concrete taak — gekoppeld aan een change. Status, blocker, discussie. | Wie de taak oppakt | Continu tijdens het werk |
De gulden regel: gedrag van de app hoort in de spec. Als je merkt dat je hetzelfde uitlegt in een ticket, een Slack-bericht en de PR-beschrijving, mis je vermoedelijk een spec.
Wanneer geen OpenSpec?
Spec-first is geen religie. Twee situaties waarin we het overslaan:
- Een eenmalige tweak — typo's, één rij toevoegen aan een vertaalbestand, een dependency bumpen. Spec-overhead is hier groter dan de tweak zelf.
- Een exploratief prototype — als je nog niet weet of het idee überhaupt werkt. Toets je aannames eerst met een wegwerp-prototype; schrijf pas daarna de spec voor de versie die wél in productie gaat.
Voor alles daartussen — een nieuwe feature, een breaking change, een refactor met gevolgen voor consumers — loont OpenSpec zich.
Hoe ziet een Conduction-repo eruit?
Onze projecten hebben allemaal dezelfde OpenSpec-mappenstructuur. Eén keer kijken in openregister/openspec geeft je een goed beeld:
openspec/specs/— de capabilities van deze app, één map per domeinopenspec/changes/— de in-flight changes (vaak één per open PR)openspec/changes/archive/— afgeronde changes, met datum vooraan, voor de geschiedenisopenspec/architecture/— app-specifieke ADR's (architecturele besluiten die alleen voor deze app gelden)openspec/config.yaml— project-config (verwijst naar het gedeelde Conduction-schema)project.md— high-level beschrijving van de app, in de root van de repo (niet inopenspec/)
Bedrijfsbrede ADR's leven niet in elke app, maar in hydra/openspec/architecture. Bouw je in een app van Conduction? Dan gelden die ADR's ook voor jou — de Hydra-pipeline laadt ze mee in elke build. Vuistregel: alleen iets wat écht uniek is voor deze app gaat in openspec/architecture/; al het andere hoort bedrijfsbreed in hydra/openspec/architecture/.
Wie schrijft de specs?
Twee patronen, naast elkaar:
- Handmatig door een developer of architect — met
/opsx-newom de change-map aan te maken, gevolgd door/opsx-ff(alles in één run) of/opsx-continue(artefact voor artefact) in Claude Code. Dat is wat je in deel 2 gaat doen. - Retrofit op een bestaande app — voor legacy-code die er was vóór OpenSpec. Zie retrofit.md. Niet je eerste klus.
Welke route ook gebruikt wordt, het eindresultaat ziet er identiek uit: een nette change in openspec/changes/ met de vier verwachte artefacten.
De OpenSpec-pipeline in één oogopslag
Een volledige change loopt door vier stages, met een eigen /opsx--commando per stap. Bron: spec-driven-development.md.
| Stage | Commando | Wat doet het |
|---|---|---|
| Obtain | /opsx-explore | Verken een topic of probleem voor je een change start (optioneel) |
| Specify | /opsx-new | Lege change met metadata aanmaken |
/opsx-ff | Fast-forward: proposal + specs + design + tasks in één run | |
/opsx-continue | Per-artefact opbouwen i.p.v. alles in één keer | |
/opsx-plan-to-issues | Tasks → tracking-issue + één GitHub-issue per task | |
| Build | /opsx-apply | Implementeer de tasks, één voor één, tegen de spec |
| Validate | /opsx-verify | Check dat de code de spec ook echt levert |
| Done | /opsx-archive | Spec-delta → hoofd-spec; change → archive/ |
/opsx-sync | Alleen de delta in de hoofd-spec mergen (zonder archiveren). /opsx-archive doet dit ook — sync apart is voor als je de spec eerder wilt bijwerken dan de change afsluiten. |
De gulden vuistregel uit de Conduction-docs: de meeste changes hebben alleen /opsx-ff → /opsx-apply → /opsx-verify nodig. De rest is opt-in. Volledig automatisch werken kan ook — zie /opsx-apply-loop (één change, containerized) en /opsx-pipeline (meerdere changes parallel).
Visueel — de pipeline van begin tot eind
┌─────────────────┐
│ /opsx-explore │ (optioneel — verkennen, geen artefacten)
└────────┬────────┘
│
▼
┌─────────────────┐
│ /opsx-new │ start de change (alleen metadata)
└────────┬────────┘
│
▼
┌────────────────────────────────────┐
│ /opsx-ff OF /opsx-continue│
│ (alles in één) (per stap) │ artefacten genereren:
│ │ proposal → specs → design → tasks
└────────────────┬───────────────────┘
│
▼
┌─────────────────────────┐
│ openspec validate │ spec-syntax check (CLI, geen slash)
│ --strict │
└────────────┬────────────┘
│
▼ ── PR voor de spec (review op specs, niet op code) ──
│
▼
┌─────────────────────────┐
│ /opsx-plan-to-issues │ tasks → GitHub issues + tracking-epic
└────────────┬────────────┘
│
▼
┌─────────────────────────┐
│ /opsx-apply │ implementeren (per task: backend + UI + tests)
└────────────┬────────────┘
│
▼
┌─────────────────────────┐
│ /opsx-verify │ code-vs-spec check (CRITICAL/WARNING/SUGGESTION)
└────────────┬────────────┘
│
▼
┌─────────────────────────┐
│ /opsx-archive │ sync delta → hoofd-spec, change → archive/
└─────────────────────────┘
Deel 2 van deze leerlijn loopt door deze pipeline heen, beginnend bij /opsx-explore (optioneel) en eindigend bij /opsx-archive.
Test jezelf
Vijf korte vragen om te checken of je dit deel begrepen hebt. Vastgelopen? Klik Hint. Curieus naar het antwoord? Klik Antwoord.
1. Wat is het kernverschil tussen een spec en een change?
Hint
Eén leeft jaren mee, de ander leeft van voorstel tot merge. Waar staat elk in het repo?
Antwoord
- Spec beschrijft een stabiele capability — een coherent stuk gedrag van de app. Lange leefduur, één spec per capability. Alle requirements + scenarios staan erin. Leeft in
openspec/specs/<capability>/spec.md. - Change is een tijdelijke verandering aan één of meer specs. Bevat een proposal, spec-delta (ADDED/MODIFIED/REMOVED), design en tasks. Leeft in
openspec/changes/<naam>/zolang hij open staat, en verhuist bij archivering naaropenspec/changes/archive/YYYY-MM-DD-<naam>/.
Een spec leeft jaren mee; een change leeft van voorstel tot merge en wordt daarna geschiedenis.
2. Waarom schrijven we eerst een spec-delta voordat we code aanpassen?
Hint
Denk aan: aannames, AI-agenten, audit-trail, en reviewers. Welke winst pakt elk?
Antwoord
Vier voordelen die in deze module worden genoemd:
- Aannames vroeg blootleggen. Wat lijkt op één feature blijkt vaak vier features tegelijk. Scenario's schrijven brengt gaten en tegenstrijdigheden naar boven vóór de code in plaats van erna in een PR-review.
- AI-agenten productief maken. Onze Hydra-pipeline bouwt code uit specs — hoe scherper de spec, hoe minder bijsturen achteraf. Dezelfde wet geldt voor menselijke teamleden in een handover.
- Audit-trail die niet jokt. Per change één mapje met proposal, delta, design, tasks. Een halfjaar later weet je nog precies wat besloten is en waarom.
- Reviewers één bron. De reviewer ziet spec-delta en code naast elkaar — "klopt de code met de spec" in plaats van "wat had de ontwikkelaar bedoeld".
3. Wat zijn de drie bouwstenen waarmee je in OpenSpec gedrag beschrijft, en welke functie heeft een scenario?
Hint
Top-down: van capability, naar regel, naar concreet voorbeeld. De laatste maakt de regel toetsbaar.
Antwoord
De drie bouwstenen (top-down):
- Spec — de stabiele capability-beschrijving, top niveau.
- Requirement — één regel binnen een spec, geschreven met MUST / SHOULD / MAY (RFC 2119). Genummerd:
REQ-001,REQ-002, … - Scenario — concreet GIVEN / WHEN / THEN-voorbeeld bij een requirement. Per requirement minstens één.
Het scenario maakt de requirement toetsbaar: iemand anders zou hetzelfde gedrag moeten kunnen bouwen of testen zonder met je te overleggen. Geen beschrijving, maar een toets.
4. Waar staan de in-flight changes in een Conduction-project, en wat gebeurt er met een change als hij klaar is?
Hint
Twee mappen: één voor lopend werk, één voor afgeronde geschiedenis. En wat verhuist er waarheen?
Antwoord
- In-flight changes staan in
openspec/changes/<naam>/— proposal, spec-delta inspecs/, design en tasks. - Bij archivering (na implementatie + merge naar
development) gebeurt drie dingen:- De spec-delta wordt gesynct naar de hoofd-spec in
openspec/specs/. - Het hele change-mapje verhuist naar
openspec/changes/archive/YYYY-MM-DD-<naam>/. - De geschiedenis blijft compleet — je kunt teruglezen welke change wélke regel introduceerde.
- De spec-delta wordt gesynct naar de hoofd-spec in
Stap 1 en 2 doe je niet handmatig — /opsx-archive (deel 2) regelt het.
5. Noem een situatie waarin je OpenSpec juist NIET inzet en gewoon direct codeert.
Hint
De overhead moet kleiner zijn dan de winst. Bij welke twee situaties is dat duidelijk niet zo?
Antwoord
Twee situaties uit de module:
- Eenmalige tweak — typo's, één rij toevoegen aan een vertaalbestand, een dependency bumpen. De spec-overhead is groter dan de tweak zelf.
- Exploratief prototype — als je nog niet weet of het idee überhaupt werkt. Toets je aannames eerst met een wegwerp-prototype; schrijf pas daarna de spec voor de versie die wél in productie gaat.
Voor alles daartussen — een nieuwe feature, een breaking change, een refactor met gevolgen voor consumers — loont OpenSpec zich wel.
Volgende stap
Nu je de begrippen kent, schrijven we in deel 2 een echte change van begin tot eind.
