Bring your Spec Kit, OpenSpec, Kiro or Gherkin specs
You will connect the specs your team already writes with GitHub Spec Kit, OpenSpec, Kiro or Gherkin to the Putnami spec-driven development (SDD) gate, without rewriting them. The Markdown or .feature file stays the one home of the prose. Putnami reads it in memory, shows what each requirement still lacks, and fails the gate on an unproven or lost requirement once you decide to enforce.
You move in three steps: read the specs and change no exit code, make each requirement provable, then enforce. Moving a feature to the native JSON format comes last and is optional.
Before you start: declare @putnami/sdd
SDD is an optional extension. Add it to putnami.workspace.json, then install it:
{
"extensions": ["@putnami/sdd"]
}putnami extensions install
putnami context generateName it in each project that holds a feature manifest, in that project's putnami.json, and list the formats the project reads in options.sdd.specFormats:
{
"extensions": ["@putnami/typescript", "@putnami/sdd"],
"options": {
"sdd": { "specFormats": ["speckit"] }
}
}The formats are speckit, openspec, kiro and gherkin. A project's list replaces the one set in putnami.workspace.json, and the default is empty. A manifest that names a format its project did not list fails putnami specs validate, and the file is not read.
Point each feature at its spec
A spec folder never creates a feature: each feature is declared in putnami.features.json. Its entry names its external spec with spec, a format and a workspace-relative path. The spec member needs manifest protocolVersion 3:
{
"$schema": "https://putnami.dev/schemas/putnami-features.json",
"protocolVersion": 3,
"namespace": "billing",
"features": [
{
"id": "billing/invoice-export",
"type": "feature",
"name": "Invoice export",
"outcome": "Customers can export issued invoices",
"owner": "billing",
"target": "modeled",
"spec": { "format": "speckit", "path": "specs/001-invoice-export" }
}
]
}What each format reads at path:
| Format | path names |
What Putnami reads |
|---|---|---|
speckit |
a feature directory, or its spec.md |
spec.md: Success Criteria (SC-xxx) as outcomes, Out of Scope as non-goals, Functional Requirements (FR-xxx) as requirements, acceptance scenarios |
openspec |
a capability directory, or its spec.md |
## Purpose as outcomes, each ### Requirement: as a requirement, each #### Scenario: |
kiro |
a spec directory, or its requirements.md |
the Introduction as outcomes, each acceptance criterion as one requirement |
gherkin |
a .feature file, or a directory of them |
the Feature: description as outcomes, each Rule: as a requirement (a Feature: with no Rule:, or with scenarios outside its rules, is a requirement too), each Scenario: |
Putnami never reads plan.md, tasks.md or design.md: they are process state or context. The OpenSpec reader turns links to doc/adr/*.md records into decision links; no other link becomes a decision. Two features cannot name the same path, or a path inside another's.
Run the gate in report mode
report is the default verification mode. Run the gate as CI does:
putnami specs validate --projects @acme/billing
putnami lint,test,build,validate --impacted
putnami specs verifyspecs validate lists each finding at its file and line, such as specs/001-invoice-export/spec.md#L42. Fix every error it names. Warnings point at what is still loose: a scenario with no declared check, a declared check with no scenario, or a section that restates a fact Putnami already derives, such as a status or a branch. specs verify shows each requirement and whether a test proved it. On this first run, no requirement has a check yet, so none is proven, and report changes no exit code for it.
putnami specs list shows each spec's source format, such as speckit. putnami specs inspect billing/invoice-export lists the native gaps: what a native spec would add, each at its line with the step that removes it. Gaps are information and never fail a run.
Nothing in your spec files changes. Your team can stay in report as long as it wants.
Make each requirement provable
A requirement is proven when the feature declares checks for it and a test passes each check. Checks live in putnami.features.json, under a manifest requirement whose id is the requirement's ID in the spec:
"requirements": [
{
"id": "export-format",
"stage": "coded",
"evidenceKinds": ["attestation"],
"verification": { "kind": "acceptance", "checks": ["export-states-its-content-type"] }
}
]A requirement's stage must be later than modeled. A test then names the check, here in TypeScript:
import { specTest } from '@putnami/spectest';
specTest(
'states the content type of an export',
{ feature: 'billing/invoice-export', requirement: 'export-format', check: 'export-states-its-content-type' },
async () => {
// exercise the export and assert its content type
},
);Go uses spectest.Proves and Python the putnami_proves marker.
Anchor each positional ID
A Spec Kit or Kiro requirement is numbered by its position: FR-001, or criterion 1 of ### Requirement 1. Insert a requirement above it and every number below shifts. Give each one an anchor, a stable ID you choose, on the line directly above it:
### Functional Requirements
<!-- putnami:id export-format -->
- **FR-001**: An export MUST state its format in the response content type.An OpenSpec requirement or a Gherkin requirement needs no anchor, because its ID is its name in kebab case: ### Requirement: Export as PDF is export-as-pdf. An accented letter keeps its base letter: Überprüfung is uberprufung. A letter with no ASCII form, such as ß or a CJK character, counts as a separator: Straße is stra-e. Anchor a requirement when you rename it, or when its name leaves nothing, such as 日本語, because enforce refuses its positional ID requirement-<n>. In a Gherkin file, the anchor is the tag @putnami-id:export-format among the tags above the Rule:, or above the Feature: when the file has no Rule:.
An anchor above a scenario names the check it stands for. In a Gherkin file, that tag is @putnami-check:<id> on the Scenario:. Without one, the scenario's check is its text in kebab case. A scenario only ever produces a warning: it never adds a check and never removes one.
Switch to enforce and record the baseline
Once every requirement has its checks and a passing test, enforce in the project's putnami.json:
{
"options": {
"sdd": {
"specFormats": ["speckit"],
"verification": { "specs": "enforce" }
}
}
}From now on, the gate fails on an unproven requirement and on a positional requirement with no anchor. Split any requirement text over 512 bytes into two before you enforce. In the same change, record the baseline, the list of requirements the gate must keep proving:
putnami lint,test,build,validate --impacted
putnami specs baseline --updatespecs baseline --update writes specs.baseline.json in the project directory. Commit it. A later change that drops a recorded requirement, or moves the project back to report, fails the gate unless it edits that file too.
Why checks stay in the manifest, and why positional IDs need anchors
Both are decisions, recorded in ADR 0004 of @putnami/sdd.
- Checks stay in
putnami.features.json. The baseline recordsfeature#requirementpairs, not scenarios. If scenarios defined the checks, deleting a scenario would remove a check, and the requirement would turn green with less proof while no gate noticed. Keeping checks in the manifest means only a reviewed manifest change can shrink the proof. - Positional IDs need anchors. The gate compares requirement IDs with the baseline. A number that shifts when a requirement moves makes a moved requirement look like a removed one, and a removed one look like a moved one. An anchor gives the requirement an ID that order cannot change.
reportreads unanchored IDs, so you can start before you anchor.
Optional: move one feature to the native format
The native specs/*.json format gives more guarantees than a Markdown file:
- a closed schema, where an unknown field is an error, not a silent drop;
- canonical bytes, so the file never shows a whitespace diff;
- requirement IDs written by hand, never derived from a heading or a position;
- decisions as typed links to
doc/adr/*.mdrecords, checked on every run.
Move one feature at a time. Preview the draft, then write it:
putnami specs init billing/invoice-export --from speckit:specs/001-invoice-export --dry-run
putnami specs init billing/invoice-export --from speckit:specs/001-invoice-exportThe --from value must equal the feature's spec reference. The draft copies the outcomes, non-goals, requirement IDs and texts, and decision links, and adds no text of its own. It refuses what enforce would refuse, at the source file and line, so anchor positional IDs first. It never overwrites a file, and it has no --all.
specs init edits neither the Markdown nor the manifest. In the same change, delete the external spec and the feature's spec member: until you do, specs validate fails on two specs for one feature. A half-done move cannot merge.
You now have your existing specs behind the SDD gate: read in report, proven by named checks, enforced with a committed baseline, and free to move to the native format one feature at a time. See Spec-driven development for how the pieces fit, and the specs command reference for every flag.