Spec-Driven Development

Spec-Driven Development mit Polycrate: Specs planen, implementieren, dokumentieren und versionieren. Kanonischer Flow für Workspace- und Block-Releases, exakte Gate-Tabelle, Agent-Integration.

Spec-Driven Development (SDD) ist der Prozess, mit dem Änderungen an Polycrate-Workspaces und Blocks geplant, umgesetzt, dokumentiert und versioniert werden. Polycrate liefert dafür ein standardisiertes .specs/-Layout, ein maschinenlesbares Spec-Format mit ID-basiertem Lifecycle und die CLI-Commands polycrate spec * und polycrate release *. SDD ist für die Zusammenarbeit mit Coding Agents (Cursor, Claude Code, Codex, …) ausgelegt, funktioniert aber genauso ohne.

Diese Seite beschreibt genau das, was die CLI prüft — nicht mehr. Die Single Source of Truth ist polycrate spec manual (Sektion CANONICAL FLOW); diese Seite, die generierte .cursor/rules/sdd.mdc und Entwickeln und deployen spiegeln sie.

Warum Spec-Driven Development?

Frage SDD-Antwort
Warum wurde etwas geändert? Die Spec archiviert Kontext, Analyse und Lösung — dauerhaft, neben dem Code
Was ist der Stand? Live-Scan von .specs/: Ordnerposition + Frontmatter sind der Status
Wie kommt eine Änderung zum Release? draft → done → release create → release update → release finalize
Was ist offen? Backlog in .specs/.next/; offene Entscheidungen in ## Open Questions
Was darf ein Agent nicht entscheiden? Alles, was als Open Question steht — die CLI verweigert done, solange welche offen sind
Wie sieht die Ziel-Architektur aus? Optional .specs/ARCHITECTURE.md, frei editierbar

Der kanonische Flow

Es gibt genau einen Flow. Alles Weitere auf dieser Seite erklärt seine Bausteine.

A. Workspace-Änderung (Spec → Release)

W=/abs/pfad/zum/workspace   # -w muss absolut sein — oder weglassen, wenn cwd der Workspace ist

# 1. Planen — ein Problem oder Feature pro Spec
polycrate spec create --name "Hub-Token rotieren" --type feat -w $W
#    → .specs/.next/<id>-<slug>.md, status: draft (IDs beginnen bei 1)

# 2. Schreiben — Sektionskörper direkt editieren (nur H3+) oder per Flag
polycrate spec update <id> --section-file solution=/tmp/.polycrate/spec-sections/spec-<id>-solution.md -w $W

# 3. Struktur prüfen — Drift blockiert jede Status- und Release-Operation
polycrate spec lint <id> -w $W      # melden
polycrate spec format <id> -w $W    # reparieren, Inhalte bleiben erhalten

# 4. Implementieren. Informative Checks (blockieren nie):
polycrate spec verify <id> -w $W    # Acceptance-Checkboxen + @pfad:ZEILE-Anker
polycrate spec review <id> --no-pager -w $W

# 5. Abschließen — Open Questions MÜSSEN leer sein (in Solution/Context auflösen, nicht löschen)
polycrate spec update <id> --set status=done -w $W

# 6. Release — Specs optional; ein reiner Versions-Bump mit Release Note ist gültig
polycrate release create 1.4.0 --spec <id> -w $W
polycrate release update 1.4.0 --set type=feat --set message="Hub-Token-Rotation" \
  --changelog-item "Feature: Hub-Token werden automatisch rotiert" \
  --note-section highlights="- Hub-Token-Rotation ohne Downtime" -w $W
polycrate release status 1.4.0 --no-pager -w $W   # zeigt, was finalize prüfen wird

# 7. Finalisieren — setzt workspace.poly version, führt pre_actions/post_actions aus. Kein Git.
polycrate release finalize 1.4.0 -w $W

B. Block-Änderung (Blocks haben keine Specs)

Blocks tragen kein .specs/-Verzeichnis. Ein Block-Release besteht aus dem CHANGELOG.poly-Eintrag, der block.poly-Version und dem Registry-Tag.

  • Logikänderung im Block → eine Workspace-Spec im Repository, das den Block hostet, mit Label block:<name>. Ihr Lifecycle ist A.1–A.5.
  • Reiner Versions-Bump (Dependency, Image-Tag, kein Verhaltensunterschied) → keine Spec; der Changelog-Eintrag dokumentiert ihn.
# 0. Nur bei Logikänderung: Spec im Host-Workspace, Label setzen
polycrate spec update <id> --add-label block:hardening -w $W

# 1. Changelog-Eintrag (idempotent; legt den Eintrag an, falls er fehlt)
polycrate release --block hardening update 0.5.6 --set type=fix \
  --set message="Konfigurierbare OpenSSH PerSourcePenalties" \
  --changelog-item "Fix: exempt list für PerSourcePenalties" \
  [--set app_version=A.B.C] -w $W

# 2. Finalisieren — setzt block.poly version, validiert, pusht in die Registry (idempotent)
polycrate release block-finalize hardening 0.5.6 [--app-version A.B.C] [--no-push] -w $W

# 3. Downstream konsumieren — schreibt from: auf jede Instanz dieses Blocks im Workspace
polycrate block pull cargo.ayedo.cloud/ayedo/linux/hardening:0.5.6 -w /abs/pfad/zum/downstream-workspace
polycrate run hardening install -w /abs/pfad/zum/downstream-workspace

release --block <name> create, spec --block, release --block update --spec und --note-section* mit --block existieren nicht mehr und brechen mit einem Hinweis auf diesen Flow ab.

C. Gates — was tatsächlich fehlschlägt (Exit ≠ 0)

Operation Hard Fail wenn Fix
jeder Command -w ist nicht absolut vollen Pfad übergeben oder -w weglassen
spec update --set status=done struktureller Drift (Überschriften, Reihenfolge, Frontmatter) spec format <id>
spec update --set status=done ## Open Questions nicht leer in Solution/Context auflösen
spec update --section* H1/H2 im Sektionskörper H3+ verwenden
release create/update --spec <id> Spec ist nicht done oder cancelled erst status=done
release finalize (Workspace) Drift in einer zugewiesenen Spec spec format
release finalize (Workspace) Open Questions in einer zugewiesenen Spec auflösen, dann status=done
release finalize (Workspace) CHANGELOG.poly-Eintrag ohne message release update --set message=
release finalize (Workspace) RELEASENOTE.md highlights leer release update --note-section highlights=
release block-finalize Changelog-Eintrag fehlt, message/description leer release --block <name> update
release block-finalize app_version leer, obwohl block.poly eines führt --app-version oder --set app_version=
release delete / spec delete interaktive Bestätigung abgelehnt --force überspringt den Prompt

--force ist eine auditierte Notbremse (--force-reason Pflicht, Protokoll in .specs/.force-audit.log): bei status=done umgeht sie Drift und Open Questions; bei release finalize umgeht sie nur highlights — message, Drift und Open Questions zugewiesener Specs bleiben hart.

Keine Gates (Warnung oder stumm): Ergebnisse von spec verify, fehlendes spec review, unerfüllte needs:, 0 Specs an einem Release, der Git-Zustand (Dirty Tree, vorhandenes Tag, ungepushte Commits).

D. Git ist kein Prozessschritt

Ob ein Workspace oder Block released werden kann, hängt nicht vom Zustand eines Git-Repositories ab. release finalize erzeugt kein Tag und keinen Commit; nichts prüft den Working Tree. Released heißt: CHANGELOG.poly-Eintrag + Versionsdatei (workspace.poly/block.poly) bzw. Registry-Tag (Block). Agents führen git commit/tag/push/stash/reset oder polycrate workspace sync nie als Spec- oder Release-Schritt aus. Wer Git-Historie will, committet selbst — siehe Git-Integration.


Konzepte

Specs

Eine Spec ist ein Markdown-Dokument mit YAML-Frontmatter, das eine Änderung von der Idee bis zur Implementierung begleitet:

  • ID — ganzzahlig, stabil, nie wiederverwendet; polycrate spec inspect <id> findet sie unabhängig vom Speicherort
  • Status — draft, done, cancelled
  • Typ — Conventional Commits: fix perf feat refactor chore docs ci build style test breaking analysis
  • Sektionen — festes Schema (siehe unten)
  • Labels — frei, z. B. block:<name>
  • needs / relates-to — Abhängigkeiten (Warnung bei done, kein Gate) bzw. thematische Links

Ordnerposition ist der Zustand

Es gibt kein index.yml. Specs und Releases werden live aus dem Dateisystem gelesen:

Pfad Bedeutung
.specs/.next/<id>-<slug>.md Backlog — in Arbeit, keinem Release zugewiesen
.specs/<version>/<id>-<slug>.md Release <version> zugewiesen
.specs/<version>/RELEASENOTE.md Release Note (Scaffold durch release create)
mein-workspace/
├── .specs/
│   ├── ARCHITECTURE.md     # Referenzarchitektur — frei editierbar
│   ├── .rules.md           # Projektregeln für Agents — von spec init geschrieben, repo-lokal erweiterbar
│   ├── .invariants.md      # Harte, prüfbare Repo-Regeln — eine pro Zeile
│   ├── .next/
│   │   ├── 7-hub-token-rotieren.md
│   │   └── 8-login-timeout.md
│   └── 1.4.0/
│       ├── RELEASENOTE.md
│       └── 5-s3-bucket-provisioning.md
├── .cursor/rules/sdd.mdc   # generiert: polycrate spec rules generate
├── blocks/<name>/          # block.poly + CHANGELOG.poly — kein .specs/
├── workspace.poly
└── CHANGELOG.poly

Was direkt editiert werden darf

Element Erlaubt Werkzeug
Sektionskörper (Text zwischen zwei ##-Überschriften) ja Editor / Agent-Dateiwerkzeuge
.specs/ARCHITECTURE.md ja Editor
YAML-Frontmatter (Status, Typ, Labels, …) nein polycrate spec update --set
##-Sektionsüberschriften, Reihenfolge nein polycrate spec format <id>
.specs/<v>/RELEASENOTE.md nein polycrate release update --note-section
CHANGELOG.poly (Workspace und Block) nein polycrate release update / release --block update
version in workspace.poly / block.poly, app_version nein release finalize / release block-finalize

Nach direkten Body-Edits polycrate spec lint <id> ausführen; spec format repariert Drift und erhält die Inhalte.

Sektionsschema

Sektion Inhalt Gate
context Hintergrund, Anforderungen, Analyse — für fix/perf/analysis: Symptom, Analyse, Root Cause —
solution Konkreter Plan mit @pfad/datei.ext:ZEILE-Ankern; darf nur bei analysis leer sein —
affected-files Ein Pfad pro Zeile —
acceptance-criteria - [x] <Bedingung> - verify: @pfad:ZEILE — spec verify prüft Checkboxen und Anker informativ
migration Upgrade-Schritte; Pflicht bei type=breaking spec validate Error
open-questions Offene Entscheidungen leer vor status=done
review Self-Review: Finding N [minor\|major\|blocker] @pfad:ZEILE: … - resolved: yes\|no informativ

Innerhalb der Sektionen sind nur H3 (###) und tiefer erlaubt; H1/H2 werden beim Schreiben abgelehnt, weil H2 die Sektionsgrenze ist.

.specs/.rules.md und .specs/.invariants.md

  • .rules.md — Prozessregeln in Prosa. polycrate spec init --force schreibt den aktuellen Default neu (Specs bleiben unberührt); repo-lokale Zusätze als eigene ##-Sektion anhängen und nach dem Neuschreiben wieder ergänzen.
  • .invariants.md — eine harte, prüfbare Regel pro Zeile (max. 25). Steht im generierten sdd.mdc an erster Stelle: Verstöße sind Bugs, keine Konventionsfragen.
# Repo invariants — my-project
- LANG: all user-facing output in English
- DB: use ORM only; no raw SQL
- SDD: in .specs/ only section bodies and ARCHITECTURE.md are hand-editable
- SDD: git is not a process step — never commit/tag/stash/reset/sync as part of spec or release work
- SDD: blocks have no .specs/ — block-related specs live in the workspace with label block:<name>

Referenzarchitektur (.specs/ARCHITECTURE.md)

Kein Spec-Dokument, wird nicht gescannt, frei editierbar. Empfohlen als kompaktes, lebendes Architekturdokument (Überschriften, Tabellen, Code-Verweise). polycrate spec arch list|search <query>|show <slug> liefert gezielt einzelne Abschnitte — Agents lesen vor dem Planen nur die relevanten.


Spec-Lifecycle

draft ◄──────────────► done      Gates: kein Drift, Open Questions leer
  └──────────┬──────────┘
         cancelled                jederzeit, kein Gate (cancellation_reason setzen)
polycrate spec update 7 --set status=done
polycrate spec update 7 --set status=draft                       # wieder öffnen
polycrate spec update 7 --set status=cancelled --set cancellation_reason="Ersetzt durch Spec 9"
  • spec list blendet cancelled standardmäßig aus (--all zeigt sie); spec validate und release diff ignorieren sie.
  • Deprecated Statusnamen werden beim Lesen gemappt: planned/pending-approval/approved/pending-verification/in-progress/ready → draft, implemented → done.
  • spec submit / spec finalize sind deprecated Aliase für --set status=done.

Open Questions — das eine Workflow-Gate

Eine offene Frage ist eine fehlende Entscheidung. Damit ein Agent sie nicht still „während der Implementierung“ trifft, verweigert die CLI status=done, solange ## Open Questions Text enthält (HTML-Kommentare zählen als leer). Beantwortete Fragen gehören in solution/context — die Sektion wird aufgelöst, nicht geleert.

● spec 7: Open Questions is not empty — resolve or clear the section before status=done

Abhängigkeiten und Links

polycrate spec link 5 --needs 3 --needs 4      # 5 setzt 3 und 4 voraus — Warnung bei done, kein Gate
polycrate spec link 5 --relates-to 8           # thematischer Link, ohne Wirkung auf den Lifecycle
polycrate spec update 5 --remove-needs 3
polycrate spec list --blocked                  # Specs mit nicht-done needs

Selbstreferenzen und Zyklen werden abgelehnt.

Informative Checks

Command Prüft Wirkung
spec lint <id> Strukturellen Drift (blockierend bei Status/Release) und Grounding-Hinweise (Anker in Context/Solution, existierende Affected Files) Drift → Error; Grounding → Warnung
spec review <id> Stempelt reviewed_at; status=done warnt, wenn der Stempel fehlt oder älter als die Datei ist Warnung
spec verify <id> Acceptance-Checkboxen und @pfad:ZEILE-Anker; --format json, --with-agent (Prompt für einen Verifier-Subagenten) Warnung; dieselbe Tabelle druckt status=done auf stdout
needs-check nicht-done Abhängigkeiten bei status=done Warnung

Quick-Capture

polycrate spec note "S3-Upload schlägt bei 403 still fehl" --type fix --label s3
# → draft-Spec sofort angelegt, ID zurückgegeben, aktueller Task geht weiter

Überblick

polycrate spec list                               # kompakte Tabelle / TUI
polycrate spec list --open                        # nicht done, nicht cancelled
polycrate spec list --version .next --status done # Backlog: fertig, aber ohne Release
polycrate spec list --label block:hardening
polycrate spec list --format json                 # Feld heißt "version" (leer = Backlog), nicht "release"
polycrate spec inspect 7 --section solution

Release-Lifecycle

Workspace-Release

polycrate release diff 1.4.0                        # Vorschau: welche Specs sind done und unzugewiesen?
polycrate release create 1.4.0 --spec 7 --spec 8    # verschiebt die Dateien nach .specs/1.4.0/, scaffoldet RELEASENOTE.md
polycrate release create 1.4.0 --spec-status done   # Bulk: alle done-Specs aus .next/
polycrate release create 1.4.1                      # ohne Specs — reiner Bump

polycrate release update 1.4.0 --set type=feat --set message="Kurzfassung" \
  --changelog-item "Feature: …" --changelog-item "Fix: …" \
  --set docs_url=https://ayedo.de/docs/polycrate/releases/
polycrate release update 1.4.0 --note-section highlights="- …" --note-section-file changes=/tmp/.polycrate/spec-sections/changes.md

polycrate release status 1.4.0 --no-pager           # Guards, Warnungen, Checkliste
polycrate release finalize 1.4.0                    # Version setzen, pre_actions → post_actions

release finalize setzt workspace.poly → version, führt die in workspace.poly unter config.release.pre_actions / post_actions konfigurierten Actions aus (z. B. Build, Image-Push, Hub-Upload) und schreibt nichts nach Git. Schlägt eine pre_action fehl, stoppt finalize — auch mit --force.

Release Notes (RELEASENOTE.md)

Sektion Pflicht für finalize
highlights ja
changes, fixes, migration nein
polycrate release note status 1.4.0
polycrate release note inspect 1.4.0 --section highlights

Auch hier: nur H3 und tiefer innerhalb der Sektionen.

Block-Release

Siehe Flow B. Zwei Versionen in block.poly, beide CLI-only:

Feld Bedeutet Setzt
version Block-Logik (Playbooks, Templates, Actions) release block-finalize
app_version Upstream-Anwendung, die der Block installiert --app-version oder release --block update --set app_version=

Erneutes block-finalize mit gleichem Stand ist ein sicherer No-Op: existiert das Tag in der Registry, wird der Push übersprungen und der Zustand bestätigt (--force pusht erneut).

polycrate release --block hardening status 0.5.6 --no-pager   # Changelog, Versionsdatei, Registry-Tag

Validierung und Recovery

polycrate spec validate                 # alle Specs: Frontmatter, Sektionen, needs, Migration bei breaking
polycrate release validate 1.4.0        # Specs im Verzeichnis done, Changelog-Eintrag, Versionsdatei
polycrate spec format <id>              # Drift reparieren
polycrate spec force-audit              # alle --force-Nutzungen

Legacy-Repos mit index.yml und 5-Status-Lifecycle: einmalig polycrate spec index migrate, dann polycrate spec lifecycle migrate-v2.


Agent-Integration

Polycrate generiert genau eine Agent-Datei: .cursor/rules/sdd.mdc. Sie enthält die Invariants, die absoluten Regeln, den kanonischen Flow mit Gate-Liste, die Key Commands und den Inhalt von .specs/.rules.md. AGENTS.md, CLAUDE.md oder andere IDE-Dateien werden nicht geschrieben.

polycrate spec init -w $W                     # legt .specs/ und .specs/.rules.md an (einmalig)
polycrate spec init --force -w $W             # schreibt nur .rules.md auf den aktuellen Default neu
polycrate spec rules generate --force -w $W   # schreibt .cursor/rules/sdd.mdc
polycrate spec rules show --no-pager -w $W    # zeigt .specs/.rules.md

Beides nach jedem CLI-Upgrade in jedem Repo mit .specs/ ausführen. Repo-lokale Prozessregeln gehören in .specs/.rules.md, prüfbare Konventionen in .specs/.invariants.md; fachliche Coding-Standards bleiben in eigenen .cursor/rules/*.mdc. Für IDEs ohne Cursor-Rules reicht polycrate spec manual --no-pager als Einstieg.

Session-Start

polycrate spec manual --no-pager -w $W        # einmal pro Session, nicht pro Turn
polycrate spec list --open --format tsv -w $W
polycrate spec arch list -w $W                # dann search/show für die berührten Themen

Agent-Disziplin

Regel Inhalt
Spec Handoff Die Chat-Antwort ist der Handoff: Spec-Identität, Zusammenfassung (Warum + Was/Wie), Open Questions ausformuliert oder „No open questions.“, nächster Schritt. Nicht annehmen, dass der Nutzer .specs/ öffnet.
Soft-Stop Bei offenen Fragen pausieren: nicht implementieren, nicht status=done. Die CLI stützt das — done scheitert bei nicht-leeren Open Questions.
Nicht erfinden Eine fehlende Tatsache ist eine Open Question in Spec und Chat.
Architektur vor Vermutung spec arch search/show für jedes berührte Thema, bevor geplant oder implementiert wird.
Self-Review Findings mit @pfad:ZEILE und [minor\|major\|blocker]; „alles OK“ zählt nicht.
Staging-Dateien Für --section-file unter /tmp/.polycrate/spec-sections/, nie unter .specs/.
Code-Kommentare // Spec: polycrate spec inspect 7 — nie den Dateipfad referenzieren, er ändert sich beim Release.

Multi-Agent

Vier Rollen pro Spec, sequenziell: Grounding (spec arch), Author (spec create/update, spec lint), Reviewer (spec review), Verifier (spec verify --with-agent). Specs ohne offene needs sind unabhängig und können parallel bearbeitet werden; spec list --version .next zeigt [BLOCKED] für wartende Specs.


Sandbox und Runtime-Verzeichnis

polycrate spec * und polycrate release * starten keinen Container. In eingeschränkten Umgebungen:

polycrate spec list --runtime-dir /tmp/polycrate-run -w $W

Alle Commands im Überblick

polycrate spec

Command Beschreibung
spec init [--force] .specs/ + .rules.md anlegen; --force schreibt nur .rules.md neu
spec create --name <name> [--type <t>] Neue Spec (draft)
spec note "<text>" [--type] [--label] Quick-Capture
spec list [--open\|--status\|--label\|--type\|--blocked\|--all\|--version\|--format] Übersicht
spec inspect <id> [--section <name>] Spec anzeigen
spec update <id> --set k=v Frontmatter: status, type, name, cancellation_reason, reviewed_at
spec update <id> --section k="…" \| --section-file k=pfad \| --section-stdin k Sektionskörper schreiben
spec update <id> --add-label\|--remove-label\|--add-needs\|--remove-needs\|--add-relates-to Labels, Abhängigkeiten
spec update <id> --force --force-reason "…" Drift- und Open-Questions-Gate umgehen (auditiert)
spec link <id> --needs <id> \| --relates-to <id> Alias für Abhängigkeiten
spec lint <id> / spec format <id> Drift melden / reparieren
spec review <id> Self-Review, stempelt reviewed_at
spec verify <id> [--format json] [--with-agent] Acceptance Criteria prüfen
spec arch list \| search <q> \| show <slug> Referenzarchitektur gezielt lesen
spec validate Konsistenz aller Specs
spec force-audit Force-Log anzeigen
spec delete <id> [--force] Spec löschen
spec manual [--no-pager] Vollständiges Manual (SSOT)
spec rules generate [--force] / spec rules show .cursor/rules/sdd.mdc schreiben / .rules.md zeigen
spec index migrate, spec lifecycle migrate-v2 Legacy-Migration

polycrate release

Command Beschreibung
release diff <version> Vorschau der zuweisbaren Specs
release create <version> [--spec <id>…] [--spec-status done] [--spec-type <t>] Workspace-Release anlegen; zugewiesene Specs müssen done/cancelled sein
release update <version> --set type= --set message= --set docs_url= --changelog-item "…" CHANGELOG.poly-Eintrag
release update <version> --note-section k="…" \| --note-section-file k=pfad RELEASENOTE.md
release update <version> --spec <id> Weitere Spec zuweisen
release status <version> / release list / release validate <version> Lesen
release finalize <version> [--force --force-reason] Workspace finalisieren (Gates siehe oben)
release --block <name> update <version> --set type= --set message= --changelog-item [--set app_version=] Block-Changelog-Eintrag
release block-finalize <name> <version> [--app-version] [--no-push] Block finalisieren + pushen
release --block <name> status <version> Block-Release lesen
release note create\|inspect\|status <version> Release Note
release unassign <version> <id> / release delete <version> Spec zurück nach .next/ / Release entfernen

Siehe auch