Spec coverage and drift detection for spec-driven development.
Like a test runner, but for your requirements.
Status: pre-1.0. The spec format and CLI are documented in FORMAT.md and stable for the v0.x line; semver minor bumps (0.1 → 0.2) may include documented breaking changes until v1.0. See CHANGELOG.md for the full release history.
Spec-driven development is having a moment — GitHub Spec Kit, Kiro, Cursor rules, Claude Code, Tessl. All of them treat specs as an input to code generation.
What nobody is solving: specs and code drift apart the moment you ship them.
- A requirement gets edited. Did anyone update the code? Nobody knows.
- Code gets refactored. Is it still implementing what the spec said? Nobody knows.
- A new engineer reads the spec. Is it still true? Nobody knows.
Within weeks, the spec is fiction. The AI tools that generated the code from the spec have no idea the spec changed. There is no equivalent of coverage or tsc for requirements.
SpecSeal is that equivalent.
Two primitives, nothing more:
1. Specs are Markdown files with stable IDs.
## REQ-AUTH-001: Session tokens expire after 24 hours
**Acceptance:**
- A token issued at T is rejected at T+24h+1s
- Refresh within the window extends expiry
- Revoked tokens are rejected immediately2. Code references specs via hash-bound annotations.
// @spec REQ-AUTH-001 #a3f2b1
export function validateToken(token: string) { ... }The #a3f2b1 is a short hash of the spec's acceptance criteria at the time the annotation was written. If the spec's behavior changes, the hash mismatches and the code is flagged as stale — exactly like a snapshot test failing.
Cosmetic edits to the spec's prose do not trigger drift. Only changes to the behavioral contract do. (This mirrors how semver distinguishes breaking changes.)
$ specseal check
✓ 47 requirements, 89 code annotations
✗ 3 stale annotations:
src/auth.ts:42 REQ-AUTH-001 spec changed since annotation
src/auth.ts:88 REQ-AUTH-003 spec changed since annotation
src/session.ts:12 REQ-AUTH-001 spec changed since annotation
✗ 1 unimplemented requirement: REQ-AUTH-005
✗ 1 orphan annotation: src/legacy.ts:5 references REQ-AUTH-999 (does not exist)
exit 1
That output is the whole pitch. It's the spec-equivalent of a failing test suite. Drop it into CI and you have a quality gate for your requirements.
npm install -g specseal
specseal init # scaffold specs/ with a starter spec
specseal check # gate your CI on spec ↔ code consistency
specseal coverage # print spec coverage %
specseal map REQ-AUTH-001 # show which files/symbols implement a requirement
specseal map src/auth.ts # reverse: show which specs a file touches
specseal sync # refresh stale hashes after reviewing spec editsOr, without installing, try it against the bundled examples:
git clone https://github.com/xantus-ai/spec-seal.git
cd spec-seal
npm install
npm run demoWhen you write // @spec REQ-AUTH-001, SpecSeal:
- Looks up
REQ-AUTH-001in your specs. - Hashes the structured behavioral sections (
Acceptance:,Non-functional:) of that requirement. - Writes the short hash into the annotation:
// @spec REQ-AUTH-001 #a3f2b1.
On every specseal check:
- Re-hash the current spec section.
- If it matches → annotation is fresh.
- If it doesn't → annotation is stale; the code may no longer reflect the requirement.
When check reports drift, run specseal sync to walk through each stale requirement, see its current behavioral contract, and confirm that your code matches before the hash is refreshed. Use --yes for non-interactive batch updates or --dry-run to preview without writing.
- Plays well with existing tools (Spec Kit, Cursor, Claude Code). It's the layer underneath, not a competitor.
- CI-integratable. Companies will adopt it for audit/compliance the same way they adopted coverage tools.
- Clear contributor surface. New language scanners (Python, Go, Rust) are well-defined units of work.
- Measurable. "Spec coverage went from 40% to 80%" is a metric you can put in a PR.
- Markdown spec parser with stable IDs and section-scoped hashing
- TypeScript / JavaScript annotation scanner
-
init,check,coverage,map,synccommands - Drift, orphan, and unimplemented-requirement detection
- Interactive sync to refresh stale hashes after reviewing spec changes
- JSON output with a versioned schema for CI integrations (
specseal check --json) - Documented format and CLI contracts: see FORMAT.md
- Python, Go, Rust annotation scanners (contributor-friendly)
- Quality linting (vague language, missing acceptance criteria)
specseal syncorphan handling (offer to remove or remap broken annotations)- GitHub Action wrapper
- Rust rewrite of the scanner hot path (following the biome/oxc/ripgrep playbook)
// @spec REQ-AUTH-001 #a3f2b1@spec— the markerREQ-AUTH-001— a requirement ID defined in anyspecs/*.mdfile#a3f2b1— short content hash (optional on first write;specseal checkwill tell you the expected value)
Multiple specs per symbol? Stack them:
// @spec REQ-AUTH-001 #a3f2b1
// @spec REQ-AUTH-003 #b9c4d2
export function validateToken(token: string) { ... }The fastest way to help: add a scanner for your favorite language. The scanner interface is one function — (filePath, source) => Annotation[]. See src/core/annotation-scanner.ts for the TS/JS reference implementation.
MIT