agentspine
v0.2.0
Published
One-shot scaffolder for a shared agent setup across Claude Code, Codex, opencode, Mistral Vibe, and Cursor.
Maintainers
Readme
agentspine
Five coding agents, five config directories, one setup. agentspine writes your conventions,
skills and blocking hooks once into .agents/, then wires Claude Code, Codex, opencode,
Mistral Vibe and Cursor to read them.
npx agentspine- One copy of everything. Edit a skill once, every tool sees it.
- Hooks that actually block. Shared policies, a per-tool adapter for each host's protocol, and
doctorto prove the block landed rather than assuming it. - Opt-in skill packs. Multi-agent code review, TDD, diagnosis, planning: installed once, read by every tool.
Architecture and the reasoning behind each decision: docs/design/0001-architecture.md.
What each tool actually supports, with sources and dates: docs/capability-matrix.md.
The problem
Every one of these tools wants its own directory. Maintain five and they drift: a skill written three times, a hook wired four ways, a convention updated in one file and stale in the rest.
agentspine writes one real copy of the content under .agents/ and points each tool at it.
.agents/
├── skills/ # SKILL.md — read natively by four of the five tools
├── agents/ # intersection-only frontmatter, symlinked where portable
├── hooks/
│ ├── policies/ # tool-agnostic shell: git safety, quality gate, naming
│ └── adapters/ # per-tool input parsing and exit-code mapping
├── scripts/ # worktree-create.sh, worktree-remove.sh
├── worktree.env # install command and branch prefix for new worktrees
└── quality.toml # per-path lint/typecheck commands, detection-seeded
.claude/skills -> ../.agents/skills
.cursor/skills -> ../.agents/skills
...Two facts make this work, both verified in
docs/capability-matrix.md: all five tools follow the Anthropic
Agent Skills spec, and .agents/skills/ is already read natively by four of them — Codex,
opencode, Mistral Vibe and Cursor. Only Claude Code needs the symlink.
Design in one table
| | |
| :-- | :-- |
| Scaffolder, not sync engine | Writes once. Templates bundled in the package, offline-safe. |
| Emitted output is Node-free | Markdown and POSIX shell. A Go or Python repo gets no package.json. |
| One content copy | .agents/ is canonical; symlinks reconcile, --no-symlink copies. |
| Three normalised hook events | pre-tool:bash, post-edit, turn-end; policies written once. |
| Stack-agnostic quality gate | Commands live in .agents/quality.toml, per-path array. |
| Core plus opt-in packs | adapt-to-project always; --packs thinking,engineering,planning,review,ci-review. |
| Honest tiers | What a tool cannot do is documented, not emulated. |
Support matrix
| Tool | Memory | Skills | Hooks | Blocking verified by doctor |
| :-- | :-- | :-- | :-- | :-- |
| Claude Code | CLAUDE.md -> AGENTS.md | symlink | settings.json | yes |
| Codex | native | native | config.toml block | yes |
| Cursor | native | native | hooks.json | yes |
| Mistral Vibe | native | native | hooks.toml block | yes |
| opencode | native | native | JS plugin shim | no — needs a live session |
Skills need no translation: all five follow the Anthropic Agent Skills spec. Hooks do — each host names its events differently, and Mistral Vibe denies by printing JSON rather than by exit code — so shared policies are written once and a per-tool adapter speaks each host's protocol.
Hooks read their policies, and the quality gate its commands, from .agents/, and Claude Code
runs hooks outside its sandbox. So its settings.json also denies sandboxed commands writes to
.agents/ (sandbox.filesystem.denyWrite), in effect once you turn the sandbox on. Codex
write-protects .agents/ itself. Cursor has no setting that denies a write inside the
workspace, and opencode and Mistral Vibe run commands without a sandbox:
who can rewrite .agents/.
Skill packs
One skill ships with every scaffold: adapt-to-project. Run it once after init. It runs
each inferred quality-gate command, activates the ones that pass once you confirm them, and writes
a project map into AGENTS.md: the lint, typecheck and test commands, the trunk, the issue
tracker, and where the glossary, ADRs and conventions live.
Everything else is opt-in and off by default:
npx agentspine --packs thinking,engineering| Pack | Skills |
| :-- | :-- |
| thinking | grilling, grill-me, grill-with-docs, codebase-design, domain-modeling, improve-codebase-architecture, prototype, handoff, zoom-out, writing-for-agents, research, wait-what, to-questionnaire, caveman, retro |
| engineering | tdd, diagnosing-bugs, wizard, find-dead-code |
| planning | to-spec, to-tickets, wayfinder, implement, implement-spec; installs thinking, engineering and review too, since it invokes their skills |
| review | review-changes (seven-area multi-agent review), address-review-comments, pr-description, and the eight reviewer subagents they dispatch |
| ci-review | pr-ci-review, review-retro, and a GitHub Actions workflow that reviews every PR; installs review too. Claude Code only, and it needs setup: see below |
They install once into .agents/skills/, where three of the five tools find them with no further
wiring. Each is written against no particular stack: where a skill needs project conventions, a
command or a tracker, it reads the project map rather than assuming a layout. The planning skills
publish to whatever issue tracker the session can reach, or write local markdown under
docs/plans/ when there is none.
Several of these skills are meant to run only when you name them. Claude Code and Cursor honour that from frontmatter, and Codex gets the same effect from a file emitted beside each one. On opencode and Mistral Vibe the model can still fire them itself. See the matrix.
The review pack also installs .agents/agents/ and links it into opencode and Cursor. Claude
Code gets a rendered copy in .claude/agents/ instead of a link, because three of the reviewers
carry a key only it reads (effort: high) and opencode would forward that key to the model
provider. Codex and Mistral Vibe have no project-scoped subagents, so nothing is emitted for them and
review-changes degrades to dispatching the reviewer briefs inline — the review shrinks in
mechanism, never silently to nothing.
Prerequisites: Node >= 20 to run the scaffolder, and jq, awk, sed and bash >= 3.2 on any
machine where the hooks run.
Reviewing every pull request
--packs ci-review emits .github/workflows/claude-code-review.yml: a deterministic preflight
decides full versus incremental, Claude Code reviews the PR through the review pack's seven
reviewers, and a poster writes the verdict. The model has no write access to the PR. It emits a
structured record; the poster renders it, anchors each important finding to a line in the diff, and
pins a claude-review commit status. A complete run that dies, or that ends before every reviewer
it spawned has reported, still posts "not reviewed" or "review incomplete" and fails the check,
because green silence reads exactly like a clean review.
The model's job holds a read-only token, and its tools are locked by managed settings that no file
in the PR can widen: read-only gh and git verbs, no file edits, and a Bash sandbox that writes
only to the checkout and Claude Code's temp directory and reaches no host but GitHub's API.
Credentials are scrubbed from the shell it drives, except the job's read-only GitHub token, which
gh needs. A verb the lock refuses lands in the run's record as a denied call rather than stalling
the run.
The workflow's deterministic steps are agentspine review preflight|schema|post|metrics, installed
from npm at the version that scaffolded the file, so the repository gets a workflow and no
toolchain of its own. The workflow, the pr-ci-review skill and that tooling share a contract, and
re-running with --packs ci-review updates the skill but never rewrites an existing workflow. To
upgrade all three, delete the workflow and commit that, re-run
npx agentspine --yes --packs ci-review,<every other pack you use>, then re-apply any edits you had
made to the workflow.
The anthropics/claude-code-action pin also fixes which Claude Code runs the review, since the
action bundles it; the capability matrix
records which version. To follow Claude Code releases, let Dependabot bump the workflow's actions.
It changes the action pins only, never the tooling version, and its pull requests get no review,
because its author is none of the associations below:
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: dailyTwo setup steps, both in the comment at the top of the emitted workflow: add the
CLAUDE_CODE_OAUTH_TOKEN secret, and create the ci/review-metrics orphan branch that stores each
run's record for review-retro to mine.
Only the repository's owner, members of its organisation and invited collaborators can start a
review, whether by opening a PR or by commenting @claude review. GitHub records that
association without a permission level, so a read-only collaborator counts. A push to a PR only
triggers a review when the PR's author pushed it.
Asking by comment. @claude review on a PR runs the review on demand, and the words right
after it on the same line, in any order, shape it. The first word that is not one of them ends
the request, so @claude review, thanks is a plain review.
| Comment | Review |
| :------ | :----- |
| @claude review | the changes since the last complete review; the whole PR when there is none, when it reviewed this same head, or after a rebase or a merge from the base |
| @claude review full | the whole PR diff |
| @claude review quick | correctness, plus security when a trust boundary is touched, one instance each |
| @claude review deep | every area, gate or not (spec still needs an issue the PR closes); one more instance of each size-scaled area (up to 3); maintainability on Opus; the orchestrator at high effort |
| @claude review security docs | only the named areas (correctness, security, context, conventions, maintainability, docs), plus spec when the PR closes an issue |
A quick or area-restricted review is partial: the next review diffs from the last complete one,
so what it skipped is covered then; comment @claude review for a complete pass now. The review
adds its verdict to the comment as a reaction when it ends: 🚀 (no important findings), 👍 (no
important findings from a partial review, which leaves the claude-review status to the last
complete one, and on a head none has covered leaves it unset), 👎 (important findings posted), or
😕 (not reviewed: the run failed or was cancelled). A partial review sets no status even when it
fails; its 😕 and the failed post-review job show it.
A comment on a PR that closed before the review started gets no reaction, and neither does a
request displaced from the queue by a later push, which never starts; comment again. If the
Claude GitHub app is installed it also reacts 👀 to any @claude mention on its own.
GitHub and Claude Code only, because it runs through anthropics/claude-code-action. On any other
host, review-changes reviews the same seven areas locally.
Install safety
Refuses a dirty git tree without --force — git is the backup. Existing markdown is edited only
between <!-- agentspine:start --> markers. JSON config is deep-merged, never clobbered. TOML gets
a text-spliced managed block so comments survive. Re-runs are idempotent.
Usage
agentspine [init] Scaffold .agents/ and wire each detected tool
agentspine doctor Probe the wiring and verify hooks actually block
agentspine review <step> CI review tooling, run by the emitted workflow
--tools <list> claude-code, opencode, codex, mistral-vibe, cursor (default: detected)
--packs <list> thinking, engineering, planning, review, ci-review (default: none)
--dir <path> Target repository (default: cwd)
--no-symlink Copy shared content instead of symlinking it
--skip-hooks Scaffold content but wire no hooks
--dry-run Print the plan, write nothing
--check Exit non-zero if the tree differs from what init would emit
--yes Apply without confirming
--json Machine-readable output
--force Proceed even though the git tree is dirtyEvery prompt has a flag, so an interactive run is always reproducible as one command — and an
agent driving the CLI never hits a prompt that hangs. Without a TTY and without --yes, init
prints its plan and exits non-zero rather than guessing.
Verify it actually works
npx agentspine doctorFires a probe hook against each locally installed tool and checks the block landed. Hooks that
silently fail to block are the main risk in a five-tool port; doctor is how you catch them, and
--json makes its output worth attaching to a bug report.
Contributing
.agents/ in this repository symlinks into templates/, so editing a skill here edits the shipped
skill. CONTRIBUTING.md says which path is canonical and what CI will hold you
to. Bug reports are welcome.
