The first release of wSpec worked, but adopting it meant copying a pile of files into your project: the slash commands, the subagents, the hooks, and an MCP server that then had to be installed and built inside every repo. Updating meant doing it again. That’s fine for the person who wrote it and a poor deal for anyone else. This week I repackaged it as a Claude Code plugin, and the install is now three commands.
Three commands, once
/plugin marketplace add c-rw/wSpec
/plugin install wspec
/wspec:setup
The first two install the plugin. The third runs once per project. After that, /wspec:propose, /wspec:implement, and /wspec:finalize work the same way they did before, just namespaced. The wspec- filename prefix is gone from the commands because the plugin namespace supplies it, so /wspec-capture is now /wspec:capture.
What moved into the plugin:
- Commands, subagents, and hooks now live in the plugin, not copied into each project’s
.claude/. One copy, updated by updating the plugin. - The MCP server ships prebuilt as a single dependency-free file. No per-project
npm install, nonpm run build, no “the build is stale” failure mode. - The Claude Code hooks that surface the current change state at session start and on every prompt come with the plugin, which made the old
statusLineentry redundant. Upgrading removes one wSpec wrote earlier and leaves one you set yourself alone.
What a plugin can’t ship
I couldn’t move everything, and that’s the reason /wspec:setup exists. Three things have to live in the project itself:
- Templates and schemas. Command prompts and MCP tools read them by relative path, and neither gets
${CLAUDE_PLUGIN_ROOT}substitution the way hook, MCP, and agent config does. - Git hook shims. Git invokes hooks directly and has no notion of a Claude Code plugin. The commit and push gates only work if git can find them.
- Read-only permissions. A plugin’s own
settings.jsononly supports theagentandsubagentStatusLinekeys, so the auto-allowedpermissions.allowentries have to be merged into the project’s.claude/settings.json.
Setup previews every change before making it. It only wires core.hooksPath and touches settings.json if you say yes, never overwrites your config.yaml, principles.md, or change packets, never overrides a core.hooksPath something else set, and leaves an unparsable settings.json untouched. Running it twice changes nothing the second time.
One honest wart: the git hook shims find the plugin’s server by absolute path, resolved at setup time. If the plugin moves or updates, that path changes, so you re-run /wspec:setup after updating. It’s a single command and it’s safe to repeat, but it’s a step a fully self-contained tool wouldn’t need.
The packaging found bugs the old layout hid
Moving from “copied into the project” to “lives somewhere else” broke assumptions I didn’t know I’d made. Three of them were real bugs:
The commit and push gates did nothing on a branch with no commits yet. That’s exactly the state of a brand-new repo when its first commit happens. The current branch was looked up with git rev-parse --abbrev-ref HEAD, which fails before the first commit, so the gate concluded there was no active change and let the commit through, even with unresolved CRITICAL findings. It now uses git symbolic-ref, which works on an unborn branch. The same lookup fed the change-branch checks and state.json, so those were wrong on a new repo too.
The uncomfortable part is how it failed. An enforcement tool that errors out shouldn’t read that as “nothing to enforce.” It should read it as “I can’t tell,” and stop. A gate that fails open on the one moment a repo is most likely to be unreviewed is worse than no gate, because it looks like protection.
The plugin’s hooks wrote into projects that never ran setup. With the plugin enabled, ending a turn in any git repo created wspec/state.json and wspec/usage-cursor.json. A plugin is loaded everywhere, not just where you asked for it. The hooks now do nothing unless the project has a wspec/config.yaml.
wspec.doctor always reported fail for plugin users. It looked for the server bundle inside the project, where it no longer lives. The commands a blocked commit prints for recording an override had the same problem, pointing at a project-relative path that doesn’t exist. Both now resolve the plugin’s real location, and doctor also checks that the git hooks still point at the running plugin.
None of these showed up while wSpec was files-in-a-project, because every path I’d hardcoded happened to be true there.
A few additions along the way
/wspec:setupis backed by a newwspec.setupMCP tool. The other commands now stop and point at it if the project hasn’t been set up, instead of failing somewhere confusing.- A dynamic workflow for research. Merging several independent research angles into one dossier is a real barrier: single-shot agents whose combined output is one document. That’s what a workflow’s
parallel()is for, so/wspec:proposenow fans out throughwspec-research-fanout, and a failed angle becomes a placeholder in the dossier instead of failing the run. Where dynamic workflows aren’t available, it falls back to the old inline dispatch. - Tighter subagents. The read-only analyst and phase validator can no longer write or edit, and the researcher and analyst have turn caps, so a runaway subagent has a ceiling.
- Three more hooks.
PreCompactflushes state before compaction, aPostToolUsehook schema-validatesanalysis.mdthe moment it’s written, andSessionEnddoes a final usage flush for exits that don’t fireStopcleanly.
Key Takeaways
- Distributing a Claude Code workflow as a plugin turns install and update into three commands and one re-run, instead of a per-project copy-and-build.
- Some things can’t ship in a plugin: relative-path templates, git hooks, and
permissions.allow. Plan for a setup step rather than pretending the plugin is self-contained. - Repackaging is a free audit of your assumptions. Hardcoded project-relative paths hide bugs until the layout changes.
- Enforcement that can’t determine state has to refuse, not pass. A gate that fails open is indistinguishable from protection until the day it matters.
Try It Out
wSpec is open source under the MIT License. Install it with the three commands above, or try it without installing by pointing Claude Code at a clone:
claude --plugin-dir /path/to/wSpec
If you’re already on the old layout, /wspec:setup (or the installer with --upgrade) migrates a project: it removes the old mcpServers.wspec entry, wSpec’s old hook entries, stale permission entries, and the old statusLine. It doesn’t delete files, so remove the old .claude/commands/wspec-*.md, .claude/agents/wspec-*.md, and wspec/mcp/ by hand once the plugin is installed.
Discussion
Loading comments...
Leave a comment
Your email is required but will never be displayed publicly.