I’d used both GitHub’s Spec Kit and Fission-AI’s OpenSpec, and each one got part of the workflow right and left another part missing. Spec Kit’s artifact vocabulary was solid but nothing enforced it once code-writing started; OpenSpec’s change lifecycle was clean but had the same gap. I saw what both had and where both were lacking, and built my own: wSpec, a spec-driven workflow for Claude Code with the enforcement layer neither one shipped.
The Problem: Specs Without Enforcement
Spec Kit gave me the artifact vocabulary I kept: Given/When/Then acceptance criteria, FR-NNN/SC-NNN requirement numbering, phased task lists, the branch-numbering scheme. OpenSpec gave me the lifecycle: active changes live in their own folder while they’re being worked, and once a change ships, its delta specs merge into a capability-oriented library instead of staying scattered across change history.
What neither one does is stop an agent from ignoring the plan once it starts writing code. Both are file-and-prompt-convention systems: they hand the model a spec and trust it to follow the spec. There’s no hook that fires when a task gets marked done without its acceptance criteria being met, no gate that blocks a commit because a security finding from the design phase never got resolved. The plan is real, but nothing enforces it once implementation starts. wSpec isn’t a better spec format. It’s an enforcement layer around one.
How wSpec Works
Four commands cover the full lifecycle:
/wspec-capture <brain-dump> turns a free-form idea dump into tracked GitHub or GitLab issues. It decomposes the dump into discrete work items and reconciles each one against what’s already open, so a repeated idea updates an existing issue’s body instead of creating a duplicate.
/wspec-propose <idea | #issue> fans out parallel haiku researcher subagents for prior art and conventions, asks three to five clarifying questions with a recommendation attached to each, creates a feat/NNN-name branch, and generates the full packet: research.md, proposal.md, spec.md, design.md, tasks.md. An adversarial pass then attacks the not-yet-implemented spec and scans the whole packet, writing analysis.md.
/wspec-implement [change-id] executes tasks.md phase by phase. After every phase, a subagent checks the diff against the spec’s requirements, the analysis findings, and the project’s principles.md. A CRITICAL finding or a principles violation stops it there.
/wspec-finalize [change-id] diffs any delta specs into the capability library, archives the change folder, and closes the linked issue with a comment pointing at the archive path.
The Enforcement Layer
An MCP server with 27 tools means change state (wspec/state.json, analysis.md findings, task completion) lives somewhere a hook can query, not just somewhere the model can read if it remembers to.
Four git hooks enforce it at the git layer:
pre-commit blocks on unresolved CRITICAL findings for the active change
pre-push blocks on unresolved CRITICAL/HIGH findings, warns on state drift
commit-msg validates commit message format
prepare-commit-msg scaffolds the message
Five more hooks enforce it inside Claude Code itself: a PreToolUse hook blocks edits outside the active change folder while a change is still in drafting status, a PostToolUse hook keeps state.json in sync after any tool that changes it, and a Stop hook nudges when every task is done but the change is still marked implementing.
Before any of that, there’s an earlier gate. The wspec-analyst subagent (opus) attacks the spec before a single line of code exists: boundary value analysis, equivalence partitioning, missing error paths, ambiguous wording, and security edge cases like IDOR or injection. That subagent used to be two: a wspec-redteamer that attacked the spec, then a separate wspec-analyst that scanned the finished packet and merged the redteamer’s findings in. Two serial opus dispatches on every /wspec-propose. Merging them into one dual-pass agent, attack first, then scan, in a single dispatch, cut a full opus round-trip out of every change without changing what gets checked.
Findings that come out of that pass block progress: CRITICAL and HIGH severity stop /wspec-implement and the git hooks until they’re resolved or explicitly overridden, and an override is fingerprint-bound to the specific finding, expires after 14 days, and gets logged to wspec/overrides.log. “I’ll fix it later” is allowed, but it’s time-boxed and on the record, not a silent bypass.
Model Routing
| Subagent | Model | Job |
|---|---|---|
wspec-researcher | haiku | Prior-art and convention research, one angle per dispatch |
wspec-analyst | opus | Adversarial spec attack plus cross-artifact quality scan |
wspec-phase-validator | sonnet | Per-phase diff review against requirements and principles |
wspec-scan-gapfiller | haiku | Closes one low-confidence gap from a repo scan |
The adversarial pass runs on the strongest model available because it’s the one place in the loop where a weak model produces a clean-looking report instead of an honest one. Everything else runs on the cheapest model that can do the job, because that output is cheap to verify and doesn’t reward extra reasoning depth.
Because the subagents run on different models by design, a Stop hook attributes every turn’s exact token usage to whichever command triggered it, per change, and rolls it into a cost estimate. That turns “this felt like an expensive propose” into an actual number you can compare against a cheaper implement phase.
Technical Implementation
The MCP server is 5,351 lines of TypeScript across 18 modules: tool definitions, git hook logic, the findings/severity system, the fingerprint-bound override store, the cost ledger, and issue-tracker integration (gh/glab) for capture. The terminal CLI wraps the same tools for scripting or CI:
node wspec/mcp/dist/cli.js tool wspec.doctor '{}'
node wspec/mcp/dist/cli.js tool wspec.status '{}'
wspec.doctor is a health probe: git and forge CLI availability, whether the MCP build is current, whether the git hooks are actually wired, whether state.json has drifted. Five of the six commands also have deterministic terminal wrappers for the mechanical parts (branch creation, template scaffolding, status flips). /wspec-capture doesn’t, on purpose: decomposing a brain dump and deciding what’s a new ticket versus a follow-up on an existing one is a judgment call, not something a template-fill script can replicate.
What’s Borrowed, On Purpose
The spec and task templates still carry Spec Kit’s structure (the Given/When/Then format, the [P] parallel-task marker), the branch-numbering algorithm is a direct port of Spec Kit’s scan-for-highest-N logic, and the config override scaffold traces back to OpenSpec’s. Both projects are MIT licensed, both require their notice to travel with anything derived from them, and wSpec’s THIRD-PARTY-NOTICES.md documents exactly which files those are rather than leaving it to whoever reads the git history to figure out.
What I Learned
Merging two serial opus subagents into one dual-pass agent cut real cost with zero change to what gets checked. It’s the kind of savings that’s invisible until you’re tracking tokens per command per change and can actually see the before and after.
Auditing your own tool for copyright exposure before releasing it publicly is a mechanical process, not a legal mystery, once you know both source licenses are permissive. Rebuild the vendored commit from git history, diff it line by line against the current tree, and you get an exact answer instead of a guess.
What’s Next
Claude Code is the only integration today, but it wasn’t always. wSpec briefly supported Copilot and OpenCode before I dropped both to consolidate everything into Claude Code and get the enforcement layer solid in one place first, rather than three integrations done halfway. Now that the MCP server, the git hooks, and the model-routed subagents are proven there, extending back out to other agent tools is next.
Try It Out
The code is open source under the MIT License and available on GitHub. Install into an existing project with ./install.ps1 -Folder <repo-path> (Windows) or ./install.sh --folder <repo-path> (macOS/Linux); the installer builds the MCP server, wires the git hooks, and seeds wspec/config.yaml and wspec/principles.md on first run.
If you’re running Claude Code and want a spec that can actually gate the code instead of just describing it, that’s what wSpec is for.
Discussion
Loading comments...
Leave a comment
Your email is required but will never be displayed publicly.