sdx: a toolchain for spec-driven development

sdx — published on npm as specdx — is a CLI toolchain for spec-driven development. It gives specs a formal schema, validates and lints them deterministically — no LLM calls in the core pipeline, no API keys — packs the relevant ones into token-budgeted context for an agent session, and statically checks implementation drift afterwards. This page is its canonical reference; the revision log below records how it changes.

It is alpha software, and this page says so plainly: twelve published versions since March 2026, all prereleases. The practice it tools up is documented separately in Spec-driven development; this page is about the tool.

What sdx is

The problem, as I keep meeting it: most AI-assisted development workflows are ad hoc. Specs — where they exist at all — are markdown files in inconsistent formats, pasted into context windows by hand, with no validation, no diffing, and no CI integration. The agent is asked to respect constraints that nothing enforces. sdx makes the spec a first-class, machine-checked artifact: a schema says what a spec must contain, a linter holds the line, a packer decides what an agent session actually needs to read, and two drift checks catch the places where documents and code have quietly stopped agreeing.

One naming wrinkle, stated once: the project is called sdx; the npm package and the installed binary are both specdx. The shape underneath is a ten-package monorepo with one-way data flow — a schema package at the bottom, a core parser above it, then lint, pack, diff, and check as siblings, all bundled into a single CLI with sixteen subcommands.

The minimal loop looks like this:

npm install -g specdx

specdx init --name "my-app" --template lightweight
# → spec.config.yaml plus stub specs with their required sections scaffolded

specdx pack --task "implement user authentication" --copy
# → the relevant specs, token-budgeted, on your clipboard for the agent session

specdx check   # after implementing: does the code still match the specs?
specdx lint    # before committing: are the specs themselves still healthy?

Everything deterministic exits 0 or 1, which is the entire CI contract.

Why specs, not prompts

The argument for working spec-first is made in the methodology page and I won’t repeat it here. The argument for tooling is narrower: without enforcement, “we have specs” degrades into “we have markdown files someone hopes the agent reads.” Each command exists because I kept hitting a specific failure that hoping didn’t fix:

Prompts are ephemeral; specs are reviewable artifacts. The toolchain’s job is to keep the artifacts honest at machine speed.

The pieces

The schema. Nine spec types — PRD, technical design, user story, test plan, ADR, API contract, epic, quick-spec, and project-context — each with named required sections (a PRD must contain Problem Statement, Goals, Non-Goals, Features, and Success Criteria; an ADR must contain Context, Decision, Status, Consequences). Seven required frontmatter fields per spec, and typed cross-references between specs with five relationship kinds, from implemented-by to supersedes. A spec.config.yaml at the repo root declares the suite: which specs exist, their types, and what requires what.

The linter. Thirteen rules: five structural ones that default to error (valid frontmatter, required sections, valid references, no circular dependencies, a single project-context spec) and eight content rules that default to warn — staleness, vague language, terminology consistency, story coverage, edge-case coverage among them. Three presets: minimal is the five structural rules, recommended is all thirteen, strict is all thirteen with every severity forced to error. Errors fail the build; warnings don’t.

The packer. Three stages: resolve, allocate, format. Relevance is scored from your task string against each spec — tag and title hits weighted ×3, section headings ×2, body ×1 — and specs adjacent in the dependency graph inherit half the score, so packing “implement auth” pulls in the design doc the auth story depends on. The budget defaults to 12,000 tokens. A project-context spec gets a reserved allocation (up to 2,000 tokens, uncompressed) before anything else competes, because the coding-rules spec is the one you never want evicted. Remaining specs fill greedily by relevance until the budget runs out. The token report prints to stderr so stdout stays pipeable.

The differ. Spec-to-spec drift between two git refs: which specs changed, and which downstream specs are now suspect, each with a staleness score. The default staleness threshold is 14 days.

The checker. Spec-to-implementation drift, fully static — an AST pass, not a model. Routes extracted from Express, Hono, or Next.js App Router code are matched against API contract specs; TypeScript interfaces, Zod schemas, and Prisma models against a technical design’s data model; test titles against test-plan cases by word-set similarity. The output is an implementation-coverage percentage. Code that exists but isn’t in any spec is reported as extra and never penalised — the score only drops for what’s missing or mismatched, because the specs constrain what must exist, not everything that may.

The gate. specdx ready runs five checks — required specs present, zero lint errors, no broken references or cycles, nothing stale, every PRD feature covered by a story — and exits 1 if any fail. It’s the boundary between planning and building, and it’s what CI runs.

The agent surface. The package ships nine Claude Code skills (spec authoring, drift checking, pre-commit review, task kickoff among them) and an MCP server exposing seven tools, so agents can run the same checks the CLI does without shelling out blind.

Using sdx with an agent

The working loop on a real project: pack --task before the session, so the agent starts from the specs that matter instead of the whole suite; the agent implements; check and lint after; ready in CI as the gate. sdx’s own repository runs exactly this — its CI workflow executes validate, lint, and status on every spec change, plus diff --base origin/main to flag staleness on pull requests. The tool maintains a fifteen-spec suite about itself and holds it at zero lint errors.

The design decision that shapes all of this: the core pipeline makes no LLM calls. The reasoning engine in this loop is the agent already sitting in your editor; sdx’s job is to hand it validated context going in and deterministic findings coming out. There is exactly one exception, and it’s opt-in: check --ai sends the already-computed findings (not your source code) to a model to classify likely false positives. It requires an API key, and nothing in the workflow requires it.

Design decisions

Numbered and dated, mirroring the revision-log ethos of this site:

  1. 2026-03 — Skills-first, not API-first. The original design had sdx calling LLM APIs directly for intent analysis, ambiguity scoring, and drift detection — which meant provider abstraction, cost estimation, caching, confidence thresholds. Recognising that the developer’s coding tool already has a model in it cut roughly 40% of a phase’s scope and left the core deterministic and CI-safe.
  2. 2026-03 — No provider-specific packing formats. Pack emits XML, markdown, or JSON. Modern models handle all of them well enough that per-provider formatting is marginal complexity for marginal gain.
  3. 2026-03 — js-tiktoken for token counting. Accurate enough (the test target is within 5% of real tokenisation) with no native dependencies to break installs.
  4. 2026-07 — Derive every spec-type enum from one source of truth. The config schema had drifted from the sections registry: it accepted six of the nine spec types, making the other three impossible to declare at all. The fix derives the enums from a single SPEC_TYPES constant and adds drift tests against the raw schema JSON, so the two can’t disagree again.

Decision 4 came from dogfooding. Setting sdx up on this website is what surfaced the bug — and the root cause was its own small horror story: the schema package’s incremental build didn’t track JSON files, so a correct fix could land in source one minute before a publish and still ship a stale artifact. As of this revision the fix is committed but the latest published version, 0.4.0-alpha.2, still carries the bug. Reference pages should say things like that out loud.

Roadmap

What’s deliberately not built yet, and what each item is waiting on:

The constraint carried across all of it, from the roadmap’s own risk notes: AI-assisted features stay opt-in and are never required for the core workflow. The deterministic pipeline is the product; everything else negotiates for a place around it.

Revisions

  1. Created as a living reference page; outline only.
  2. Wrote the full reference body from the sdx repo, its spec suite, roadmap, and the npm registry.