Wer mit Coding Agents nach einer Spezifikation entwickelt, merkt den Unterschied zwischen zwei Frameworks selten beim ersten Feature. Er kommt bei der zweiten Änderung, wenn die ältere Spec-Datei plötzlich etwas anderes behauptet als der Code. An genau diesem Punkt werden GitHub Spec Kit und OpenSpec hier gemessen, am selben Change.
spring-petclinic ist die Demo-Tierarztpraxis schlechthin. An diesem Projekt spielt die Spring-Welt seit Jahren neue Ideen durch: Besitzer, Haustiere, Besuchstermine. Ein harmloseres Testobjekt für einen Framework-Vergleich ist schwer zu finden. Zwei Spezifikationsdateien, derselbe aktuelle Code, entgegengesetzte Regeln. Links specs/001-pet-owner-transfer/spec.md aus dem Spec-Kit-Lauf: Ein Pet mit anstehenden Terminen darf nicht übertragen werden. Rechts openspec/specs/pet-owner-transfer/spec.md aus dem OpenSpec-Lauf: Pets mit künftigen Besuchsterminen dürfen jetzt übertragen werden. Beide Läufe hatten dieselbe Folgeänderung umgesetzt, beide endeten mit 255 grünen Tests, beide beschreiben denselben aktuellen Code. Die eine Datei dokumentiert einen früheren Change, die andere den fortgeschriebenen Stand. Beide sind als Artefakte korrekt, aber nur eine lässt sich ohne Zusatzwissen als geltende Spezifikation verwenden. Der Versuch zeigt deshalb keinen allgemeinen Sieger, sondern den Unterschied zwischen historischer Änderungsakte und kanonischem Gegenwartsstand.
Die entscheidende Frage: Welche Datei kann der nächste Agent als geltenden Stand lesen, ohne zuerst die Änderungsgeschichte zu rekonstruieren?
Es geht nicht um den besseren Agenten, sondern um den Lebenszyklus der Spezifikation. Kommt für jede Änderung ein neuer Snapshot zum Stapel, oder schreibt die Änderung den geltenden Stand fort und schiebt die Historie ins Archiv? Darin unterscheiden sich GitHub Spec Kit und OpenSpec. Was Spec-Driven Development überhaupt ist und warum es sich vom losen Prompten unterscheidet, steht in Was Spec-Driven Development mit Coding Agents leistet, Teil des Praxis-Strangs im Leitfaden. Dieser Text setzt es voraus.
Kurz gefasst: Verglichen werden zwei SDD-Frameworks, GitHub Spec Kit (v0.13.3) und OpenSpec (
@fission-ai/openspecv1.6.0), am selben Brownfield-Change inspring-petclinic-rest. Claude Code bleibt als Agent konstant. Der Ausgangs-Change ist ein expliziter Pet-Ownership-Transfer. Die Folgeänderung hebt die Sperre für Pets mit künftigen Besuchsterminen auf, und erst diese Folgeänderung zeigt den Unterschied. Kernbefund: Im getesteten Flow-Forward-Ablauf legte Spec Kit für die Folgeänderung einen zweiten nummerierten Snapshot an, der erste blieb als historische Aufzeichnung stehen. OpenSpec übertrug das typisierte Delta in eine kanonische Haupt-Spec und archivierte die Änderung. Danach ließ sich nur die OpenSpec-Haupt-Spec ohne zusätzliches Wissen als geltender Stand verwenden. Es ist ein Praxisversuch, kein Benchmark. Die Java-Implementierung blieb als Kontrollgröße konstant, sodass sich der Vergleich allein auf die Spezifikationsartefakte beschränkt.
Der Aufbau: ein Change, zweimal, gleicher Agent
So sind die Aussagen gekennzeichnet: Beobachtungen stammen aus dem beschriebenen Lauf. Produktbeschreibungen beruhen auf der Dokumentation der gepinnten Version. Auswahl- und Risikoeinschätzungen sind redaktionelle Schlussfolgerungen.
Getestet wurde derselbe Change zweimal im selben Bestandsprojekt, einmal mit Spec Kit, einmal mit OpenSpec. Claude Code blieb in beiden Läufen der ausführende Agent. Baseline ist spring-petclinic-rest beim gepinnten Commit c7b5f5e (tag experiment-baseline), gebaut mit Temurin/OpenJDK 21.0.2 und Spring Boot 4.1.0, Agent ist Claude Code 2.1.217 mit Opus 4.8, in beiden Läufen identisch. Build-Kommando: JAVA_HOME=~/.sdkman/candidates/java/21.0.2-open ./mvnw clean test.
Der Build umfasst nach dem Change 255 Tests. SC-005 aus der Spezifikation verlangt, dass alle 237 bestehenden Tests grün bleiben. Zusammen mit 18 neuen Tests ergibt das 255, in beiden Läufen ohne Failures, Errors oder Skips.
Der Ausgangs-Change war für beide Läufe wortgleich vorgegeben:
Change: Expliziter Pet-Ownership-Transfer
- Dedizierte Transfer-Operation, getrennt vom generischen Pet-Update-Endpoint
- Ownership NICHT über den normalen Update-Endpoint änderbar
- Audit protokollieren: Vorbesitzer, Neubesitzer, Zeitstempel, Grund
- Ablehnen, wenn das Pet einen oder mehrere künftige Besuchstermine hat
- Wiederholte identische Anfrage muss idempotent sein
- OpenAPI aktualisieren, sonstiges API-Verhalten unverändert
Wichtig: kein Punktestand, keine Generierungszeit als Qualitätssignal. Dazu eine Abgrenzung, ohne die der Vergleich unfair wäre: Beide Läufe zweigten vom identischen Commit c7b5f5e ab, und in beiden erzeugte der Agent zuerst die Planungs- und Spezifikationsartefakte eigenständig auf der unveränderten Baseline. Erst für die Implementierungsphase übernahm der OpenSpec-Lauf den bereits grünen Java-, Test- und OpenAPI-Diff aus dem Spec-Kit-Lauf per git apply und ließ ihn frisch kompilieren und testen. Der Code-Diff blieb damit als Kontrollgröße konstant. Verglichen werden ausschließlich die Spezifikationsartefakte, eine kausale Wirkung des Frameworks auf die Codequalität lässt sich daraus nicht ableiten.
Jeder Workflow wurde einmal ausgeführt. Aussagen über Rückfragen, selbst getroffene Entscheidungen und erzeugte Formulierungen beschreiben daher diesen Lauf, nicht eine reproduzierte Häufigkeit.
Alle Beobachtungen beziehen sich auf die genannten, fest gepinnten Versionen. Spätere Versionen können andere Workflows und Artefakte erzeugen.
GitHub Spec Kit
Spec Kit führt von der Absicht über specify, plan und tasks zur Implementierung. Der Einstieg lief mit uvx --from git+https://github.com/github/spec-kit@v0.13.3 specify init. Der Befehl legt einen .specify/-Baum an (Bash-Skripte, Templates, memory/constitution.md) und installiert zehn speckit-*-Skills unter .claude/skills/. Der Ablauf führt über /speckit.constitution, /speckit.specify, optional /speckit.clarify, dann /speckit.plan, /speckit.tasks, /speckit.implement. Jedes Feature bekommt einen nummerierten Ordner:
specs/001-pet-owner-transfer/
├── spec.md (WAS/WARUM: FR-001…FR-008)
├── plan.md (WIE + Constitution Check)
├── tasks.md (T001…T021, nach Story gruppiert)
├── research.md (6 Entscheidungen, inkl. Idempotenz R5)
├── data-model.md (PetTransfer-Entity + pet_transfers-DDL)
├── contracts/transfer.openapi.yaml
└── checklists/requirements.md (0 [NEEDS CLARIFICATION])
Die spec.md ist eine vollständige, in sich geschlossene Momentaufnahme dieser Änderung. Genau eine echte Frage blieb offen, der Idempotenz-Mechanismus. Spec Kit klärte sie eigens im clarify-Schritt und hielt Wahl wie verworfene Alternative in plan.md und research.md fest. Das war die einzige Rückfrage dieses Werkzeugs in diesem Lauf.
OpenSpec
OpenSpec trennt den aktuellen Stand (openspec/specs/) von der vorgeschlagenen Änderung (openspec/changes/). Der Einstieg lief mit openspec init --tools claude über die Node-CLI. Sie schreibt sechs /opsx:*-Kommandos und sechs Skills unter .claude/ sowie ein nahezu leeres openspec/. Die sechs Core-Kommandos sind propose, explore, apply, update, sync und archive. Im Versuch liefen propose, apply, sync und archive. /opsx:explore, der interaktive Klärungsdialog, wurde nicht genutzt. v1.6.0 verwendet den /opsx:*-Namespace, nicht das ältere /openspec:*. Nach Sync und Archive sieht der Baum so aus:
openspec/
├── config.yaml
├── specs/
│ └── pet-owner-transfer/spec.md ← GELTENDER STAND (Purpose + 7 Requirements, keine Delta-Marker)
└── changes/
└── archive/
└── 2026-07-22-add-pet-owner-transfer/ ← eingefrorene Historie
├── proposal.md (warum)
├── design.md (wie / Decisions)
├── specs/pet-owner-transfer/spec.md (das Delta: ## ADDED Requirements)
└── tasks.md (13/13 [x])
Ein Requirement steht als ### Requirement mit #### Scenario (WHEN/THEN). Auszug aus dem Delta:
## ADDED Requirements
### Requirement: Dedicated pet transfer operation
The system SHALL expose a dedicated API operation to transfer a pet from its current owner
to another owner, distinct from the generic pet update operation. (AC1)
#### Scenario: Transfer via the dedicated endpoint succeeds
- **WHEN** an authorised client sends POST /api/pets/{petId}/transfer with a valid new owner id and a reason
- **THEN** the pet's owner becomes the new owner
- **AND** the response is 200 OK carrying the transfer audit record
Im getesteten /opsx:propose-Lauf stellte OpenSpec keine interaktive Rückfrage. Der propose-Skill weist den Agenten an, offene Punkte selbst zu entscheiden und im Pflicht-Abschnitt „Decisions“ der design.md samt Alternativen festzuhalten. Das ist keine feste Eigenschaft des Werkzeugs, für den Klärungsdialog gibt es /opsx:explore, hier nicht genutzt. So wurde auch die offene Idempotenz-Frage behandelt. Das Risiko entsteht, wenn niemand diese selbst getroffenen Entscheidungen prüft: Eine plausible, aber falsche Annahme bleibt dann unbemerkt in design.md stehen.
In beiden Läufen fiel dieselbe Idempotenz-Entscheidung, ein zustandsbasierter No-op ohne client-seitigen Idempotency-Key. Eine zweite identische Anfrage trifft diesen No-op: Das Pet gehört dem Zielbesitzer bereits, die Antwort ist dieselbe Erfolgsmeldung ohne zweiten Audit-Eintrag. Auch ein abweichend formulierter Grund erzeugt keinen neuen Datensatz. Die Deduplizierung erfolgt über den Endzustand, also den Zielbesitzer, nicht über den Grundtext.
Die werkzeugeigene Prüfung war ein Strukturvalidator: openspec validate --strict erzwingt die #### Scenario-Struktur, openspec status liest den - [x]-Fortschritt. Das ersetzt kein semantisches Review, sondern prüft eine andere Ebene: Struktur statt Bedeutung. Beide Kommandos liefen im ersten Versuch durch.
Die Folgeänderung entscheidet
Am ersten Change trennt die beiden Modelle wenig. Der Unterschied zeigt sich erst an der Folgeänderung: Pets mit künftigen Besuchsterminen dürfen jetzt übertragen werden, die Termine bleiben dem Pet zugeordnet, und die Antwort enthält deren Anzahl.
OpenSpec hielt die Änderung mit drei typisierten Operationen in einer Datei fest. Der REMOVED-Block, wörtlich:
## REMOVED Requirements
### Requirement: Transfer rejected for pets with future visits
**Reason**: Superseded by the follow-up: pets with future visits may now be transferred (F1).
**Migration**: Callers that relied on the 409 rejection must stop doing so; future visits no
longer block a transfer and remain linked to the pet.
Dazu ein MODIFIED (die Transfer-Operation bekommt futureVisitsAffected in der Response) und ein ADDED (Übertragung für Pets mit künftigen Besuchsterminen erlaubt). Im getesteten Ablauf aktualisierte /opsx:sync die Haupt-Spec, während der Change aktiv blieb. Einen CLI-Befehl openspec sync gibt es nicht. Wer den Slash-Command überspringt, kann die noch nicht synchronisierten Delta-Specs beim Archivieren mit openspec archive in openspec/specs/ zusammenführen lassen. Danach verschiebt der CLI-Befehl den Change ins Archiv. Die inzwischen aufgehobene Regel wurde aus dem geltenden Stand entfernt und durch „erlaubt“ ersetzt, sie überlebt nur in den archivierten Deltas und in der Reason/Migration-Notiz. Anschließend prüfte openspec validate --specs die Haupt-Spec erneut.
Bei Spec Kit fand ich im geprüften Workflow keine Amend-Operation. Für die Folgeänderung entstand ein neuer nummerierter Ordner, 001 blieb unangetastet:
specs/
├── 001-pet-owner-transfer/ # spec.md sagt WEITERHIN "reject transfer if future visits" (FR-006)
+ └── 002-transfer-future-visits/ # FR-101 hebt AC4 auf, FR-103 ergänzt die Anzahl
Das kann Absicht sein, und dieser Fall ist der stärkste Punkt für Spec Kit: Ein nummerierter Snapshot kann bewusst festhalten, was für eine bestimmte Änderung damals spezifiziert wurde, statt den heute geltenden Stand abzubilden. Für ein Review oder eine Freigabe ist das wertvoll, denn der eingefrorene Stand bleibt zitierbar. 001/spec.md wird also nicht aktualisiert und behält die damalige Sperre. Zum Problem wird das erst, wenn ein Team den Ordner als aktuelle Kontextquelle behandelt: In diesem Ablauf entsteht kein kanonischer aktueller Stand, und verbunden werden die beiden Ordner nur durch eine von Hand ergänzte Supersedes:-Zeile, eine Konvention des Autors, kein Feature des Werkzeugs. Wer zuerst 001 öffnet, liest den überholten Stand.
Wichtig für die Einordnung: Der Versuch bildet bei Spec Kit das dokumentierte Flow-Forward-Modell ab. Spec Kit selbst setzt kein Persistenzmodell als Standard voraus. Die Spec-Persistence-Dokumentation zu v0.13.3 beschreibt drei Teamkonventionen, Flow-Back Spec, Flow-Forward Spec und Living Spec, und schreibt keine davon vor: „None is the default, and none is required by Spec Kit“, das Modell sei „a team convention, not a CLI setting“. Nicht geprüft wurde, wie derselbe Fall mit den beiden anderen Modellen ausgefallen wäre.
Beide Modelle haben einen Fehlermodus
Der Versuch erlaubt keine Rangfolge der beiden Fehlermodi. OpenSpec kann beim Sync inhaltlich falsch zusammenführen: Der Agent aktualisiert die Haupt-Spec und muss Begründung und Migrationshinweis liefern, aber openspec validate --strict prüft danach nur die Struktur, nicht die fachliche Konsistenz. Ein falsch zusammengeführter Stand bleibt so unbemerkt. Spec Kit lässt den früheren Snapshot unverändert stehen: Ein Widerspruch entsteht, sobald ein Team alle Feature-Specs als aktuellen Kontext behandelt, und niemand löst ihn automatisch auf.
Mein Test dafür war banal, und er ist die Leitfrage von oben: Welche Datei könnte ich dem nächsten Agenten geben, ohne ihm zuerst die Änderungsgeschichte zu erklären? Im OpenSpec-Lauf war das die einzige Haupt-Spec. Im Spec-Kit-Lauf hätte ich dazusagen müssen, dass 001 nicht mehr gilt. Wie eine Spec zur Kontextquelle für den nächsten Change wird, behandeln die taktischen Muster in Prompt-Muster für Enterprise-Codebasen.
Spec Kit oder OpenSpec: Entscheidung nach Änderungstyp
Das brauchbarste Auswahlkriterium ist der Spec-Lebenszyklus. Beide erzeugen menschenlesbares Markdown, das ohne das jeweilige Werkzeug lesbar bleibt. Die Matrix fasst die im Versuch beobachteten sowie in den gepinnten Versionen dokumentierten Unterschiede als Prüfhypothese zusammen, nicht als allgemeine Auswahlregel:
| Situation | Eher Spec Kit | Eher OpenSpec |
|---|---|---|
| Klar abgegrenztes neues Feature | Nummerierter Snapshot passt | Delta funktioniert auch |
| Bestehendes Verhalten ändern | Alter Snapshot bleibt stehen | Delta fließt beim sync in die Haupt-Spec |
| Offene Fragen an den Nutzer klären | Eigener clarify-Schritt | /opsx:propose entschied im Versuch selbst, /opsx:explore (interaktiv) nicht genutzt |
| Entscheidungen dokumentieren | research.md / plan.md | Pflicht-Abschnitt „Decisions“ in design.md |
| Explizite Historie pro Änderung | Ordner nebeneinander | Archiv mit Reason/Migration |
| Semantischer Abgleich | /speckit.analyze für Artefaktkonsistenz, /speckit.converge gegen den Code | /opsx:verify im erweiterten Profil, im Versuch nicht genutzt |
| Syntaktische Strukturprüfung | im getesteten Ablauf keine vergleichbare CLI-Prüfung der Spec-Struktur | validate --strict |
Wer auf Portabilität achtet: Bei Spec Kit liegt die Automatisierung als Bash-Skripte und JSON-Manifeste im Repo, gut inspizierbar, aber an Shell und Agent gekoppelt. OpenSpec schreibt weniger Workflow-Dateien ins Repo und kapselt die Logik in der CLI, koppelt den Ablauf also an ein installiertes Binary.
Was dieser Versuch bewusst ausklammert: wer die Spec schreibt, freigibt und archiviert, und wie das im Team verbindlich geregelt wird. Sobald mehr als eine Person am geltenden Stand arbeitet, wird aus der Werkzeugfrage eine Governance-Frage, ein eigenes Thema für einen späteren Teil des Clusters.
Nicht im Vergleich: der Agent und das Gegenmodell
Claude Code war in beiden Läufen der ausführende Agent, nicht ein drittes verglichenes Framework. Es liefert selbst keine vollständige SDD-Methode, nur den Rahmen dafür: die installierten Skills beider Werkzeuge unter .claude/ und den Plan-Modus, in dem der Agent recherchiert, Dateien liest und einen Plan schreibt, den Quellcode aber nicht verändert, bis der Plan freigegeben ist.
Nicht getestet: Kiro. Kiro integriert Requirements, Design und Tasks direkt in eine spezialisierte IDE. Das ist ein anderes Modell als die hier verglichenen, agentenunabhängigen Markdown-Workflows und verdient einen eigenen Praxistest.
So prüfen Sie den Ablauf an einer kleinen Bestandsänderung
Der Ablauf lässt sich an einem kleinen Bestandsfall kompakt nachvollziehen.
- Change wählen. Nehmen Sie eine Änderung, die Sie in einem Satz beschreiben können, die aber den Bestand berührt, etwa eine neue Operation neben einem generischen Update-Endpoint. Bei einem Diff, den ein Satz vollständig beschreibt, kostet der Plan oft mehr, als er einbringt.
- Akzeptanzkriterien vorab notieren. Fünf bis acht, bevor ein Agent läuft.
- Beide Werkzeuge in getrennten Branches aufsetzen. Spec Kit mit
uvx --from git+https://github.com/github/spec-kit@v0.13.3 specify init, OpenSpec mitopenspec init --tools claude. Für einen reinen Spec-Vergleich halten Sie den Code-Diff konstant und übernehmen die Implementierung aus dem ersten Lauf in den zweiten. - Die Artefakte lesen, nicht nur den Diff. Prüfen Sie
spec.md,plan.md,design.mdund die Entscheidungsabschnitte, nicht nur den erzeugten Code. - Eine Anforderung ändern und beobachten. Was passiert mit der ursprünglichen Spezifikation, bleibt sie stehen oder wird der geltende Stand fortgeschrieben? Genau hier trennen sich Snapshot und Delta.
Probieren Sie es an einem echten Legacy-Ausschnitt aus, wie in Legacy-Monolith mit Coding Agents refaktorieren beschrieben.
Die Werkzeugwahl ist zweitrangig, solange nicht feststeht, welche Datei als geltender Stand gilt. Definieren Sie diese Quelle, benennen Sie eine verantwortliche Rolle und testen Sie jede Folgeänderung daran. Sonst bleibt die Spezifikation entweder saubere Historie ohne Gegenwart oder eine gepflegte Gegenwart ohne verlässliches Review.
Versuchsartefakte
Alle Artefakte liegen im Companion-Repo spring-petclinic-spec-kit-vs-openspec. Für die Reproduktion sind insbesondere folgende Stände relevant:
- Baseline: Commit
c7b5f5e, tagexperiment-baseline - Spec-Kit-Lauf: Branches
speckit-initial-changeundspeckit-follow-up - OpenSpec-Lauf: Branches
openspec-initial-changeundopenspec-follow-up - Die beiden widersprüchlichen Spezifikationen direkt: Spec Kit
specs/001-pet-owner-transfer/spec.md, OpenSpecopenspec/specs/pet-owner-transfer/spec.md - Archiviertes Ausgangs-Delta: OpenSpec
openspec/changes/archive/2026-07-22-add-pet-owner-transfer/specs/pet-owner-transfer/spec.md - Folgeänderungen: Spec Kit
specs/002-transfer-future-visits/, OpenSpecopenspec/changes/archive/2026-07-22-allow-transfer-with-future-visits/ - Exakte Prompts und Kommandos: Spec Kit
evidence/speckit/03-prompt-and-commands.md, OpenSpecevidence/openspec/03-prompt-and-commands.md - Testprotokolle (255/0/0/0): Spec Kit
08-tests-followup.txt, OpenSpec08-tests-followup.txt - Pläne und Entscheidungen: Spec Kit
plan.mdundresearch.md, OpenSpecdesign.md(Abschnitt „Decisions“)
Primärquellen
- GitHub Spec Kit v0.13.3
- Spec Kit Spec-Persistence-Dokumentation (v0.13.3)
- OpenSpec v1.6.0 CLI
- OpenSpec v1.6.0 Commands
- Claude Code Plan Mode, offizielle Anthropic-Dokumentation



