A scaffold for spec-driven development that uses a behavior-driven approach to keep code aligned with the intent articulated in specs: business use cases are written as Gherkin scenarios in Markdown specs, and those scenarios run as the acceptance suite that every change must keep green.
Note on the spec format. The video shows specs with
Feature:,Rule:andScenario:written inside```gherkinfences. The format has since moved to plain Markdown to keep it simpler: the capability, its requirements and its scenarios are ordinary Markdown headings, and only the Given/When/Then steps stay in fences. Everything demonstrated in the video works exactly the same here — only the spec layout changed. The move also makes specs valid OpenSpec, soopenspec validateandopenspec archivenow work natively instead of being reimplemented by the project skills.
- Intent articulated in the spec. The behavior-driven approach lets us express the intent behind an implementation in the specification itself, as business use cases; the acceptance suite continuously checks that the code honors them.
- Spec as source. This repo establishes the foundational aspects of spec-as-source development: directly updating code without first updating the spec is forbidden. Every change starts as a spec delta whose red scenarios define the work.
- Keeping agents on the articulated use cases. An experiment in preventing a coding agent from deviating from the business use cases we have articulated — enforced mechanically (hooks, an always-green acceptance suite), not by prompt discipline alone.
- Acceptance tests must always pass. Run the suite after every code change; never leave it red. If failing code was written without a driving spec delta, it is reverted and redone spec-first — never patched into passing.
- Specs and code are never modified together. Enforced live by a zone-guard hook (
tasks.mdfiles exempt).
Specs here are managed with OpenSpec and the behavior-driven schema, but OpenSpec is only one example — the same approach works with other spec-driven tools such as Spec Kit. The same is true of the test runner: two are included, and adding your own is a documented path rather than a rewrite.
The two project skills under .claude/skills/ — acceptance-test-authoring and bdd-zone-check — are plain Markdown plus dependency-free reference scripts, so nothing about them is Claude Code specific. Copy (or symlink) both directories into .agents/skills/ and point your agent at them. Skill auto-discovery from .agents/skills/ is not yet universal, so where your agent does not pick them up automatically, reference the files from its instructions file instead.
Two things do not carry over as-is:
- Paths.
acceptance-test-authoring/SKILL.mdinvokes the Gherkin extractor and the linter by path (.claude/skills/acceptance-test-authoring/references/...). Repoint those at wherever the skill actually lives. - Enforcement.
bdd-zone-checkis guidance only. Rule 2 is enforced by.claude/hooks/zone-guard.sh, a Claude CodePreToolUsehook. On another agent the rules still read fine, but nothing blocks a cross-zone edit until you wire up the equivalent pre-edit hook.
The acceptance suite ships with two reference runners, selected by stack: in openspec/config.yaml:
stack: |
Runner | Report |
|---|---|---|
javascript |
cucumber-js | reports/cucumber-report.html |
python |
behave (1.2.7+, for Gherkin v6 Rule: support) |
reports/behave-report.html |
Spec linting is shared: gherkin-lint over the extracted output, with one pinned config for both, so the two stacks accept and reject exactly the same specs.
Adding your own. These two are bindings of a runner-agnostic contract, not special cases. Fork and add a stack by implementing the procedure in the acceptance-test-authoring skill against your runner:
- Gherkin extraction with line fidelity — extracted line N is
spec.mdline N - Effective-spec composition — source of truth + active deltas, with superseded rules excluded
- Archive exclusion —
openspec/changes/archive/never executes - Composition report naming every left-out scenario at its source
spec.md:line
Both halves are written down rather than implied by the shipped code: references/EXTRACTION.md defines item 1 line by line, and references/COMPOSITION.md defines items 2–4. Those two documents, not the JavaScript or Python source, are what a port implements.
The one capability everything else builds on is excluding specific scenarios without editing the specs. How you get there depends on the runner: cucumber-js filters at discovery time with line-targeted paths, while behave would only runtime-skip them (polluting the skipped count), so the Python stack prunes the superseded rules out of the generated .extracted/ tree instead. Either shape is fine — what matters is that superseded scenarios neither run nor appear as skipped.
To confirm a port, run it and an existing stack against the same specs: the scenario counts and names must match.
This is the openspec/config.yaml used in the video above, showing only the project-specific parts — the spec format and the extract-and-lint requirement come from the behavior-driven schema itself, so a project never restates them:
schema: behavior-driven
stack: javascript
context: |
PollCast is an intuitive web application allowing users to instantly create,
share, and track custom polls. To prevent spam without the friction of
passwords, the platform features a streamlined UI where participants simply
cast votes using their email address as a unique identifier, guaranteeing
highly accurate and duplicate-free poll results.
Tech stack: Node.js, server-side rendered Express, JSON file store to begin with.
rules:
proposal:
- Less than 200 words
tasks:
- Use ctx7 for any documentation on npm modules