Skip to content

Repository files navigation

SpecSeal

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.


The problem

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.

The core idea

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 immediately

2. 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.)

The killer demo

$ 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.

Quick start

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 edits

Or, 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 demo

How drift detection works

When you write // @spec REQ-AUTH-001, SpecSeal:

  1. Looks up REQ-AUTH-001 in your specs.
  2. Hashes the structured behavioral sections (Acceptance:, Non-functional:) of that requirement.
  3. 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.

Why this works as a wedge

  • 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.

What's in v0.1

  • Markdown spec parser with stable IDs and section-scoped hashing
  • TypeScript / JavaScript annotation scanner
  • init, check, coverage, map, sync commands
  • 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

What's coming

  • Python, Go, Rust annotation scanners (contributor-friendly)
  • Quality linting (vague language, missing acceptance criteria)
  • specseal sync orphan handling (offer to remove or remap broken annotations)
  • GitHub Action wrapper
  • Rust rewrite of the scanner hot path (following the biome/oxc/ripgrep playbook)

Annotation syntax

// @spec REQ-AUTH-001 #a3f2b1
  • @spec — the marker
  • REQ-AUTH-001 — a requirement ID defined in any specs/*.md file
  • #a3f2b1 — short content hash (optional on first write; specseal check will 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) { ... }

Contributing

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.

License

MIT

Releases

Packages

Contributors

Languages