@mthanh/vespercli
v2.2.1
Published
Vesper Agent Suite — CLI to manage AI agents, prompts, instructions, and skills for GitHub Copilot (VS Code) and Claude Code
Maintainers
Readme
Vesper Agent Suite
A CLI that provisions an AI agent suite into your project for Claude Code + OpenCode or GitHub Copilot Chat (VS Code): an orchestrator persona, specialist subagents, skills, and maintenance workflows.
Vesper gives your AI coding assistant specialized agents, reusable skills, and structured workflows. Each workspace runs exactly one host, Claude Code (which always prepares OpenCode as well) or GitHub Copilot, and you pick it with vespercli init --host claude|copilot. The project's workflow state (.vesper/.tracking/, including the run ledger, and .vesper/memory.jsonl) is host-neutral and survives a host switch.
Upgrading from 1.x? Vesper 2.0 is a breaking release. Running
vespercli initonce migrates a 1.x workspace in place and backs up everything it removes. See Upgrading from 1.x andCHANGELOG.md.
The Vesper Lifecycle
Vesper is a pure orchestrator: it routes, gates, and approves every hand-off, but never does the work itself. Each task travels one revolution of the cycle. The violet delivery loop ships it: specialist subagents research, plan, implement, review, and test, and every step reads and writes tracking documents under .vesper/.tracking/. How much of the loop a task travels is set by its scope tier — S (implement, gate, log), M (short research, plan, implement, one review, gate) or L (the full pipeline with architect validation, two reviewers and unit tests) — and every tier ends in vespercli gate, a pass/fail the orchestrator reads by exit code without reading the work. The amber learning loop then mines what just happened. It compares plan with actual, then turns the result into skills, instructions, and knowledge-graph memory that feed the next task.
Every revolution leaves the system smarter: divergences from plan become glob-scoped instructions, repeated patterns become skills, and task context survives interruption, across sessions and across a host switch.
Tracking documents — the paper trail
Delegation prompts pass paths, never contents. Each document has one writer and defined readers, so a fresh-context subagent can pick up mid-pipeline. The tracking state is host-neutral, so a story researched before a host switch can be implemented after it.
The mechanics around the documents are CLI calls, not model calls. The orchestrator brackets every pipeline with vespercli log started … and vespercli log completed …, which append one JSON row each to the run ledger (.vesper/.tracking/runs/YYYYMMDD-<slug>.jsonl), and moves a story between status folders with vespercli story move <slug> <folder>. Resumption reads vespercli story list and vespercli log tail. On Claude Code a SubagentStart hook adds a delegated row per subagent spawn as optional enrichment; nothing depends on it. The 1.x history.md file and its logging agent are gone; the memory graph holds project knowledge, never pipeline state.
Implementation runs on prompts, not vibes
The planner doesn't just plan — it writes the executable brief each developer will follow. After the developers return, one LLM review (code-reviewer; principal-software-engineer joins on L only) and then vespercli gate — the project's own checks, plan conformance on L, the diff-size cap and protected paths — decide whether the change moves on; a GATE FAIL sends the headline, and nothing else, back to the developer. Documentation runs only when the public surface changed, the retrospective only on L or when the reviewer reports a new pattern, and three failed review or gate loops stop for the user before the hard cap of ten.
Task tracker — board is the source of truth
The Task Management section of the team roster names the system (ClickUp, Jira, …) and its Task Agent; the pipeline never talks to a board directly. Local mode works with zero configuration — the story folders alone carry status.
The retrospective engine — where skills are born
After every implementation, the Feature Wrap agent compares what was planned against what actually happened, then routes each learning through a decision tree:
Where learning re-enters the pipeline
Capture is worthless without injection — every knowledge output has a named consumer at a named step:
The design optimizes for compounding: mistakes convert to rules, workflows convert to skills, and state survives interruption, /clear, and a host switch. When a fix loop caps out (10 iterations), unresolved issues are written to issues/ and the pipeline continues. Dead ends become documents, not lost context.
Requirements
- Node.js >= 22.13.0 (the
vesper-indexingMCP server uses the built-innode:sqlitemodule) - git (worktree bootstrap, hooks and host resolution read the repository)
- One host: Claude Code (CLI, VS Code extension, JetBrains, or desktop app) — OpenCode opens the same workspace as-is — or VS Code with GitHub Copilot Chat
Installation
npm install -g @mthanh/vespercli # global install (recommended: the hooks call `vespercli`)
npx @mthanh/vespercli <command> # or run it without installingThe session-start triggers and git hooks call vespercli from PATH. They do nothing when it isn't installed, so a global install is what makes them work.
Quick start
vespercli init --host claude # Claude Code + OpenCode; or: --host copilot
vespercli init # interactive: asks "Which AI host is this workspace for?"A non-interactive run (no TTY) never guesses. It re-uses the host the checked-out tree already carries, and otherwise needs --host. -y/--force-default default the follow-up questions only, never the host choice. The last follow-up question, on both hosts, is the opt-in graphify pack (default no; --graphify answers yes without asking, which is also the only way to get it in a -y/--force-default/non-TTY run; a workspace that already has the pack is never asked again and never loses it).
Then say hello:
- Claude Code: start a session in the project. It opens as the
vesper-orchestratoragent. The first time, Claude Code asks you to trust the workspace, which loads thevesperplugin. Promptinit, and Vesper detects the missing Team Roster and runs the workspace setup. - OpenCode: open the same checkout.
opencode.jsonmakesvesper-orchestratorthe default agent and loads the always-on rules; the Vesper plugin under.opencode/plugins/registers the specialists (see OpenCode). - GitHub Copilot: reload VS Code (
Developer: Reload Window), open Copilot Chat, pick vesper-orchestrator in the agent dropdown, and promptVesperize.
Commit what init wrote (see the tables below). The committed files are what make the project a Vesper project for every clone, worktree, and teammate.
What gets written where
Vesper writes only inside the project, and every file it writes is recorded in an ownership ledger (see Ownership and removal).
Claude Code + OpenCode (--host claude)
The specialist catalog is not copied into the project. It ships inside the npm package as a Claude Code marketplace (vesper-marketplace) holding a core plugin (vesper) and the stack packs (vesper-frontend, vesper-playwright, vesper-backend, vesper-polyglot, vesper-docs, vesper-integrations, and the opt-in vesper-styles), all referenced in place. Core specialists resolve as vesper:<name> and core skills as /vesper:<name>; a packed one as vesper-<pack>:<name> / /vesper-<pack>:<name>. Core is always enabled; a pack is enabled per project — see Packs.
| Path | Committed? | What it is |
|---|---|---|
| .claude/settings.json | yes | Vesper's keys only: enabledPlugins["vesper@vesper-marketplace"] plus one enabledPlugins["vesper-<pack>@vesper-marketplace"] per enabled pack, agent: "vesper-orchestrator" (every session opens as Vesper), MCP tool permissions, the SessionStart guard hook and the SubagentStart run-ledger hook (vespercli log delegated … --stdin, optional enrichment). Your other keys are kept. |
| .claude/settings.local.json | no (gitignored) | extraKnownMarketplaces.vesper-marketplace, a directory marketplace source ({"source":{"source":"directory","path":…}}) pointing at this machine's npm install. Regenerated per machine, so the committed settings stay portable. |
| .claude/agents/vesper-orchestrator.md | yes | The thin main-thread persona (committed fallback). |
| .claude/skills/{reinit,compact-knowledge,revalidate-knowledge,revalidate-team}/ | yes | The four maintenance skills, disable-model-invocation: true (user-invoked only). |
| .claude/rules/vesper-orchestrator.md | yes | The orchestrator playbook, always-on. |
| .claude/rules/vesper-orchestration.md, .claude/rules/vesper-context.md | yes | This project's Team Roster and slim whitepaper, always-on. Seeded once, then maintained by the Vesper agent. |
| .claude/rules/vesper-styles.md | yes | Only with the opt-in styles pack enabled: a ~10-line Vesper-authored pointer rule that applies the third-party output styles (see Third-party skills). |
| .claude/vesper/context/ | yes | The on-demand context tier (read only when needed). |
| CLAUDE.md | yes | A short guarded prose pointer (<!-- vesper:start -->…<!-- vesper:end -->). No @import: the rules load natively. |
| .mcp.json | no (gitignored) | The three core MCP servers plus any you added. Regenerated at every session start. |
OpenCode (always written with --host claude)
The Claude host always prepares OpenCode too (there is no separate OpenCode host and no flag): three small committed files, recorded in the ledger under host claude, removed by reset --host claude and by a switch to Copilot, re-provisioned by a switch back.
| Path | Committed? | What it is |
|---|---|---|
| opencode.json | yes | Vesper's keys only, merged into your file (your keys and array entries are kept): default_agent: "vesper-orchestrator", instructions (the three .claude/rules/vesper-*.md files, append-only), mcp (the same servers .mcp.json carries, as ["vespercli", "mcp", "serve", "<name>"] — no machine path, so the file commits), plugin: ["./.opencode/plugins/vesper.js"] (append-only), and a Vesper-owned vesper object (packs: the enabled packs, mirrored by packs enable/disable/sync; agentsMode: plugin or files). A fresh file also gets $schema. |
| .opencode/agents/vesper-orchestrator.md | yes | The primary OpenCode agent (mode: primary) — the same pointer to the rules as the committed Claude persona. |
| .opencode/plugins/vesper.js | yes | A byte-exact copy of the shipped Vesper plugin. Refreshed on init/update when the shipped file changed; an edited copy is kept and reported. |
GitHub Copilot (--host copilot)
The generated Copilot catalog stays inside the npm package (assets/copilot/). Vesper points VS Code at it through workspace settings. Every package agent is generated with user-invocable: false, so the agent picker shows the built-in agents plus one Vesper agent.
| Path | Committed? | What it is |
|---|---|---|
| .vscode/settings.json | yes | Vesper's keys only: the chat.agentFilesLocations, chat.promptFilesLocations, chat.agentSkillsLocations and chat.instructionsFilesLocations entries (the core package folders, one assets/copilot/packs/<pack>/{agents,skills} folder per enabled pack — see Packs — plus ${workspaceFolder}/.vesper/{agents,instructions,skills}), and the managed chat/MCP/auto-approval settings. Your other keys and array entries are kept. |
| .vscode/tasks.json | yes | The Vesper: worktree bootstrap folder-open task, running vespercli guard --running-host copilot. Your other tasks are kept. |
| .vesper/agents/vesper-orchestrator.agent.md | yes | The one pickable Vesper agent. |
| .vesper/instructions/vesper-orchestrator.instructions.md | yes | The orchestrator playbook, always-on. |
| .vesper/instructions/vesper-{orchestration,context}.instructions.md | yes | The Team Roster and slim whitepaper, always-on, seeded once. |
| .vesper/context/ | yes | The on-demand context tier. |
| .vscode/mcp.json | no (gitignored) | The three core MCP servers plus any you added. Regenerated at every session start. |
The four maintenance workflows (reinit, compact-knowledge, revalidate-knowledge, revalidate-team) are package-resident Copilot prompt files, so they are available as native slash commands. A Copilot workspace gets no .claude/* file (apart from the cross-host trigger below) and no .github/* file.
Both hosts
| Path | Committed? | What it is |
|---|---|---|
| .vesper/.tracking/ (run ledger runs/ included), .vesper/memory.jsonl | yes | Shared workflow state. Host-neutral; never touched by a host switch, a reset, or the migration. |
| .vesper/history-archive/ | yes | Read-only archive. init moves a 1.x .vesper/history.md here as history-<YYYYMMDD>.md (never deleted); only /revalidate-knowledge reads it. |
| .gitignore, .gitattributes | yes | Guarded Vesper blocks: local state ignored; merge=vesper-memory for the memory graph, merge=union for the recent-decisions ring buffer. |
| .git/hooks/pre-commit, .git/hooks/post-checkout | local | A marked Vesper block (see Git hooks). |
| .vesperrc | no (gitignored) | The worktree's ownership ledger (see Which host is this?). |
| .vesper/index.db | no (gitignored) | The shared code index read by vesper-indexing. |
| Cross-host trigger | yes | A Claude workspace gets a Vesper: host guard task in .vscode/tasks.json; a Copilot workspace gets a SessionStart guard hook in .claude/settings.json. Each one only warns when the other host opens the project. |
Never written
Vesper 2.0 never writes a user-level location. Its write primitives refuse these paths:
~/.claude/skills,~/.claude/rules,~/.claude/agents,~/.claude/commands,~/.claude/CLAUDE.md,~/.claude/settings.json~/.copilot/agents,~/.copilot/skills,~/.copilot/instructions
Two user-level paths are exempt. ~/.claude/plugins/ is Claude Code's own plugin cache: Claude Code writes it when you trust a workspace, and it stays inert in any project whose own settings don't enable the plugin. ~/.vesperrc stores your answers to the optional vendored-skill prompts. The npm package itself lives wherever npm installs it.
vespercli doctor reports any Vesper file it finds in a never-write location. A 1.x install could leave some there; see Upgrading from 1.x.
Claude Code: plugin, trust and degraded mode
Claude Code loads the vesper plugin only after you trust the workspace. If you decline, approve the workspace later in Claude Code's trust settings and run /reload-plugins.
The committed fallback (the vesper-orchestrator agent, the always-on rules, and the four maintenance skills) resolves in every session. The plugin's vesper:* specialists and catalog skills resolve only where the plugin loads.
Degraded mode (cloud, remote, headless -p, Cowork, or a declined trust dialog). These sessions do not load the marketplace plugin, so the specialists are absent. Two routes lead there:
- Verified absence. The session-start guard emits
VESPER_PLUGIN_ABSENTwhen a condition the plugin needs is verifiably missing on the machine:vesper@vesper-marketplaceis not enabled once user, project, local and managed settings are merged; novesper-marketplacesource is declared in any of those files (extraKnownMarketplaces) nor recorded in Claude Code's plugins root (known_marketplaces.json); or every declared local marketplace directory is missing. On that token the orchestrator enters degraded mode at once, without a probe. - Probe. Otherwise (nothing emitted; presence is never claimed, because a disk check cannot prove the plugin loaded), at the first delegation the orchestrator sends one side-effect-free probe to
vesper:task-researcher. If the platform answers "subagent_type not found", degraded mode follows; if the probe succeeds, the full delegation law applies.
On entering degraded mode it prints
⚠️ Vesper degraded mode: specialist catalog unavailable; operating directly.
and then works as a single generalist, following the memory and run-ledger conventions inlined in the playbook. The plugin-carried skills are unavailable in degraded mode.
Session-start work also needs vespercli: the SessionStart hook is command -v vespercli >/dev/null 2>&1 && vespercli guard --running-host claude || true. Where vespercli isn't installed, nothing runs, so .mcp.json and the code index are not generated.
OpenCode
OpenCode (sst/opencode) reads a Claude workspace natively (CLAUDE.md, .claude/skills/), but not the always-on .claude/rules/*. The Claude host therefore always writes the overlay in the table above, and no reinit is needed between the two tools — both run on the same checkout. Open the repository in OpenCode:
vesper-orchestratoris the primary agent (default_agent), and the playbook, Team Roster and whitepaper are loaded throughinstructions.- The three core MCP servers (and any you added with
vespercli mcp add) run throughvespercli mcp serve <name>, soopencode.jsoncarries no machine path. - The specialists come from the npm package through the Vesper plugin, under the same names the roster carries (
vesper:<name>,vesper-<pack>:<name>); the four maintenance skills resolve from.claude/skills/.vesper.packsinopencode.jsonmirrors the enabled packs. - Fallback: set
vesper.agentsModeto"files"inopencode.jsonand runvespercli opencode render-agents— the specialists are then generated as.opencode/agents/<name>.mdfiles (re-rendered bypacks enable/disable/sync).
OpenCode ignores unknown top-level keys in opencode.json (its config decoder drops excess properties), so the vesper object is harmless at runtime; an editor validating against opencode.ai/config.json may flag it as unknown.
Verify on your install (the story's open questions): OQ-1 the plugin registers agents at runtime (otherwise use the files fallback); OQ-2 instructions accepts the three rule files as-is; OQ-3 the session.created hook's guard output reaches the model's context; OQ-4 vesper:<name> names with a : are accepted as agent identifiers by the Task tool.
Which host is this?
The checked-out tree decides. Vesper resolves the host from Vesper-only keys in files git can see, as they stand in the working tree: the plugin and agent keys in .claude/settings.json, or default_agent: "vesper-orchestrator" in opencode.json (the OpenCode overlay's key — a second Claude fingerprint, so both present is the normal state, and opencode.json alone still resolves Claude), for Claude; the .vesper/agents settings entry and the Copilot guard task for Copilot. Generic files such as .mcp.json never decide the host. Every path uses this rule: the guard, bootstrap, doctor, init, reset, list, add, update and mcp.
Consequences:
- After
git switch, the new commit decides. A Claude commit gets Claude, a Copilot commit gets Copilot, and a pre-Vesper commit (or a commit that removed Vesper) gets nothing.git statusstays clean. .vesperrcis gitignored and is the worktree's ledger: what Vesper wrote here, your opt-ins, and the version stamps. It never decides the host. Bootstrap never writes a host into it;initrefreshes it.- A 1.x commit is left unmigrated whatever the ledger says, because no 1.x artifact carries a 2.0 key.
- Both hosts' keys present at once means the workspace is ambiguous. Vesper provisions nothing, and
doctorreportshost-ambiguous.
Caveats:
- A repository that gitignores its resolving file (
.vscode/, a*.code-workspace, or.claude/settings.json): the host is resolved only when the ignored key and the ledger agree. Activation then follows the worktree, not the commit, and a session start refreshes only ignored local state (MCP config, code index).doctorreportshost-local-only. Commit the file to get per-commit behaviour. - A Copilot workspace whose own-host task command was edited and whose
.vesper/agentssettings entry was removed resolves no host until you runvespercli init --host copilotagain.
Session-start guard
vespercli guard runs at session start on both hosts. It resolves the checkout's host, runs doctor in-process, and then either warns (a host mismatch) or runs worktree bootstrap.
| Trigger | Command | Where it comes from |
|---|---|---|
| Claude Code opens the project | vespercli guard --running-host claude | SessionStart hook in .claude/settings.json |
| OpenCode opens the project | vespercli guard --running-host opencode | the Vesper plugin (.opencode/plugins/vesper.js, session.created) — opencode is the Claude host's identity: a Claude workspace is healthy, a Copilot one a mismatch |
| VS Code opens the folder | vespercli guard --running-host copilot | folder-open task in .vscode/tasks.json |
| git checkout/git switch of a branch | vespercli guard (no host) | post-checkout git hook |
VS Code runs folder-open tasks only when automatic tasks are allowed in the folder.
Host mismatch (the host that opened the project is not the checkout's host) is a warning, never a stop. The guard writes VESPER_HOST_MISMATCH and a one-line fix (vespercli init --host <running host>) to stdout, runs no bootstrap, and writes nothing under .vesper/ and no host-specific file. The only hard rule is a no-write rule: nothing is provisioned for a host the project did not choose.
- Claude Code:
SessionStartstdout goes into the model's context. The always-on playbook prints one banner —⚠️ Vesper host mismatch: this workspace is set up for <provisioned host>; run \vespercli init --host ` for the full team. Operating directly with reduced quality.` — and then continues in degraded mode, working directly as a generalist. Reduced quality is accepted; the session is never blocked. - Copilot direction, Claude workspace: VS Code opens Claude workspaces too (a Claude Code user editing in VS Code), so that task's message is advisory: it names
vespercli init --host copilotfor Copilot users, says it removes the Claude Code setup, and tells Claude Code users to ignore it. Nothing is written. - Copilot: the folder-open task prints the same token and fix in the terminal, and the Copilot orchestrator playbook carries the same banner-and-continue rule. There is no sentinel file and no always-on refusal rule; a
.vesper/instructions/vesper-host-guard.instructions.mdleft by an earlier 2.0 build is removed by the nextinit,updateorresetwhen it is byte-identical to what Vesper wrote (an edited copy is kept and reported).
Plugin absence (Claude Code only, never for --running-host opencode — the OpenCode plugin carries the specialists itself). On the healthy path of vespercli guard --running-host claude, after bootstrap has regenerated .claude/settings.local.json, the guard checks the conditions the plugin needs and writes VESPER_PLUGIN_ABSENT to stdout when one is verifiably missing (see degraded mode). It never writes a "present" token. At most one host token and one plugin token appear per run; a mismatch suppresses the plugin token, and the plugin token leaves the exit code unchanged.
Without --running-host (the git hook or a manual run) there is no identity claim: no mismatch check and no marker of either kind. The guard writes nothing unless the committed tree at HEAD carries the resolved host's keys and the working tree still agrees.
Exit codes (direct invocation only; the installed hook and task force exit 0, and the stdout token is the contract): 0 ok, 1 degraded, 2 mismatch.
vespercli doctor
A read-only validator. It never writes, deletes, or backs anything up. It checks host resolution, the ownership ledger against disk, MCP config drift, tracking shape, the framework version, git hooks, 1.x leftovers, user-level residue, and (INFO opencode-overlay-missing) a Claude workspace that lacks one of the three OpenCode overlay files — vespercli init adds them. Each finding is ERROR, WARN or INFO and names its fix: vespercli init, vespercli init --host <host>, vespercli reset --host <host>, or vespercli worktree bootstrap for local-only state such as a missing git hook block. A fresh worktree's ledger carries no framework version until init runs there once; doctor reports that as ledger-unstamped (INFO), not as a stale framework.
vespercli doctor # human-readable report
vespercli doctor --quiet --running-host claude # exit code only| Exit code | Meaning |
|---|---|
| 0 | OK (warnings and info only) |
| 1 | at least one ERROR (including a check that threw) |
| 2 | host mismatch (--running-host differs from the checkout's host) |
init runs doctor after provisioning, and the guard runs it at every session start.
Switching hosts, and removing Vesper
vespercli init --host copilot # in a Claude workspace: asks to switch, then switches
vespercli init --host copilot -y # same, without the confirmationA switch removes only the old host's recorded artifacts, then provisions the new host. It carries over what you added: --override pins and opt-in instructions, optional MCP servers (e.g. clickup), and the enabled packs (the opt-in styles pack and its pointer rule included). Tracking (run ledger included) and memory are untouched. The old host's Team Roster and whitepaper are kept where they are. The new host starts from freshly seeded templates, so run the /reinit workflow afterwards, or copy the old files across. The switch writes a journal (.vesper/.host-switch-journal.json). If it is interrupted, automatic provisioning stays suspended and a plain vespercli init resumes toward the host you chose.
vespercli reset # tear down the checkout's host; roster + whitepaper kept
vespercli reset --host copilot # in a Claude workspace: clean Copilot leftovers only
vespercli reset --host claude --purge-knowledge # full teardown incl. roster, whitepaper, context tierreset --host <x> on the active host is a full teardown. On the other host it is a foreign-residue clean that leaves the active host alone. Shared state is never touched. A full teardown also removes Vesper's git-hook block, unless another worktree sharing the hooks may still use Vesper (see Git hooks).
Ownership and removal
Every Vesper write is recorded in the .vesperrc v2 ledger (schemaVersion: 2): the path, host, category and, for whole files, a LF-normalised sha256. Files Vesper shares with you (.vscode/settings.json, .claude/settings.json, CLAUDE.md, .gitignore, the MCP configs) are recorded by key or region, and Vesper only ever edits its own keys or regions in them.
Vesper removes only what it can prove is its own: a recorded whole file whose bytes still match, or a pinned 1.x file (see below). A file you edited is kept and reported. A directory is removed file by file, and only once it is empty, so your own file beside a Vesper file survives. Name or location alone never justifies a removal.
Upgrading from 1.x
Install 2.0 and run vespercli init in the project. There is no flag and no dry run. The migration happens in place, with standard logging only.
Host choice. A 1.x workspace with one host's artifacts defaults to that host. A workspace with both (1.x dual-target) has no default: interactive runs ask, and non-interactive runs need
--host.Clean start. In a git repository the migration refuses to start while any path it would change has uncommitted changes, and lists them. The one exception is the leftovers of an interrupted migration on the same commit, which
initresumes.What it does.
- Removes the 1.x committed catalog (
.claude/{agents,commands,skills}), the 1.x playbook.claude/vesper/orchestrator.md, and the 1.xCLAUDE.md@importblock and SessionStart hooks. - De-injects the 1.x Copilot settings key by key (your own keys and array entries stay).
- Relocates the 1.x roster and whitepaper into the chosen host's home, and rewrites
.vesperrcto v2. - Relocates a Vesper-marked
.github/instructions/orchestration.instructions.mdinto the chosen host's home..github/copilot-instructions.mdis never removed: Vesper content in it is copied into the brain, and the file stays in place and is reported..github/skills/is never touched. - Tracking, memory and the history archive are never touched; a 1.x
.vesper/history.mdis moved into.vesper/history-archive/, never deleted.
- Removes the 1.x committed catalog (
Content proof. A 1.x file is removed only when its bytes equal what a released 1.x build wrote (
assets/legacy/claude-1x-digests.json). An edited 1.x file is kept and reported once.User-level residue. The 1.x machine-wide plugin folder
~/.claude/skills/vesper/is backed up and removed when it is provably Vesper's. A rule or instruction file under~/.claudeor~/.copilotis removed only when it holds nothing but Vesper guard regions. Anything else, including Vesper keys in~/.claude/settings.json, is reported with the exact edit to make, never edited.doctorkeeps reporting it on every project until it is gone.Backups. Everything removed without recorded ownership is first copied, verified byte-identical, to
.vesper/.migration-backup/<timestamp>/(project/andhome/subfolders; gitignored, never removed by Vesper). Nothing is ever overwritten there.Recover and roll back. At the end,
initprints this run's backup folder, acp -R …command that recovers every removed file, and a step-by-step rollback of that one migration:- check that
HEADis still the migration's starting commit (the check changes nothing); - run
sh <backup>/remove-created.sh, which removes the files the migration created, and only while each still holds the bytes the migration wrote; - copy back every whole file the migration rewrote, including the gitignored
.vesperrc,.mcp.jsonand the git hooks; git restorethe starting commit's committed layout, with hooks disabled for that one command;- copy back the removed files;
- reinstall the previous CLI, last:
npm install -g @mthanh/vespercli@<1.x version>. 1.9.x was never published to npm, so for a 1.9.x workspace the guidance says to install it from the repository or pin 1.8.0.
The rollback cannot undo a commit you already pushed after the migration.
- check that
Until you migrate, a 2.0 CLI leaves a 1.x workspace alone. Session start and bootstrap write nothing and print one warning,
update/add/listrefuse, anddoctorreportslegacy-workspace(ERROR), each pointing atvespercli init.Interruption. The 1.x specialists and the plugin enablement change in the same
vespercli init. If it is interrupted in between, the project has no specialists (never two competing copies) until you runvespercli initagain, which finishes the job.
Worktrees and bootstrap
git worktree add doesn't bring the gitignored local state along. vespercli worktree bootstrap regenerates it, and it already runs through the guard at session start and after a branch checkout, so you rarely call it yourself.
vespercli worktree bootstrap # regenerate local state (needs a confirmed host)
vespercli worktree bootstrap --running-host copilot # as the host would at session start
vespercli worktree bootstrap --quietOn a healthy workspace, bootstrap:
- regenerates the host's MCP config:
.mcp.jsonand.claude/settings.local.jsonfor Claude,.vscode/mcp.jsonfor Copilot; - rebuilds the code index;
- re-asserts the
.gitignore/.gitattributesblocks, the memory merge driver and the git hooks; - self-heals the cross-host trigger;
- refreshes the base branch's remote-tracking ref with a bounded, detached
git fetch. It never pulls and never touches your branch.
It never writes a project-committed catalog, never writes a host into .vesperrc, and never writes anything under .claude/ except the cross-host trigger in a Copilot workspace. A failed step exits 1.
It writes nothing when the running host disagrees with the checkout, on an unmigrated 1.x workspace, while a host switch is pending, or when nothing resolves. Without --running-host, it also writes nothing unless the committed tree at HEAD confirms the host. A repository that gitignores its resolving file is therefore bootstrapped only at session start.
The base-branch fetch is the only background network call Vesper makes (every skill is a packaged snapshot). It is non-interactive and fails fast; an authentication failure prints its cause and the fix (ssh-add, or run vespercli init in a terminal).
Git hooks
init and bootstrap install a marked block (# <!-- vesper:hook:start --> … # <!-- vesper:hook:end -->) into two hooks:
post-checkoutrunsvespercli guard(no host) only on an attached branch checkout:$3 = 1,HEADon a branch, and no rebase or bisect in progress. It does not run on file checkouts,checkout <sha>,worktree add --detach, rebase steps or bisect steps. It makes no network call, never prompts, and never fails the checkout.pre-commitrefuses to commit amr.jsonrecovery artifact. When a commit touches anything outside Vesper's paths, it also adds every dirty Vesper knowledge path to that commit:.claude/{agents,commands,skills,rules},.claude/vesper/context,.vesper/{skills,instructions,agents,context},.vesper/.tracking(the run ledger included),.vesper/memory.jsonl,.vesper/gates.jsonandCLAUDE.md. It prints which ones it added.- Agents: set
VESPER_NO_SWEEP=1for the commit and stage your own files by path. The sweep is then skipped: the hook stages nothing and prints one line naming the dirty knowledge paths it left out. This keeps another agent's in-flight edits out of an unrelated commit. The sweep stays on for human commits.doctorreports a block installed before this variable existed asgit-hook-outdated;vespercli worktree bootstraprewrites it.
- Agents: set
Opt out for one repository with git config vesper.hooks false. It takes effect at once, because both blocks check it on every run, and init/bootstrap then install no hook block. git config --unset vesper.hooks turns them back on. The .gitattributes merge config is not affected.
Hook safety:
- Vesper writes a hook only inside the repository's own hooks directory. It never writes through a symlink or
core.hooksPaththat points outside the repository, into a hook git tracks, into a non-shor binary hook, or into a hook that exits before the block would run. - In each of those cases it prints one line saying what to add by hand. It also prints one line when hooks are disabled, machine-wide, or managed by husky v9.
- It repairs, byte-exact, a block an earlier Vesper version appended to such a hook.
resetremoves the block only when no other worktree sharing the hooks may still use Vesper; an unknown state counts as "may". It never writes into a sibling worktree.
MCP servers
Every workspace gets three core servers: vesper-indexing (fast code search over .vesper/index.db), sequential-thinking, and memory (the knowledge graph in .vesper/memory.jsonl). The MCP config is gitignored and regenerated at every session start: .mcp.json (with ${VAR} substitution) for Claude, .vscode/mcp.json (with ${env:VAR}) for Copilot.
vespercli mcp # status for the checkout's host
vespercli mcp list # catalog + custom servers, and which are installed
vespercli mcp add clickup # add an optional catalog server (recorded in .vesperrc)
vespercli mcp add graphify # the graphify pack's server (packs enable graphify does this too)
vespercli mcp remove clickup
vespercli mcp enable | disable # the core servers
vespercli mcp sync # re-sync tool auto-approval (Copilot) / refresh config (Claude)
vespercli mcp reindex --full # clear and rebuild the code indexmcp disable makes no lasting change. It removes the servers from the MCP config only until the next session start (or any other bootstrap), which regenerates them. MCP commands never write a host-resolving key, and a Claude MCP write on a workspace not provisioned for Claude is refused with nothing written.
mcp status's "Server built" line probes the vesper-indexing server's two runtime dependencies (@modelcontextprotocol/sdk, zod):
✓ yes: built and verified.⚠ yes, but dependencies missing: the build exists but a dependency is broken. Runnpm installinsrc/mcp/vesper-indexing, or reinstall Vesper.✗ no: not built.
Copilot: opt-in stack-specific instructions
Stack-specific Copilot instructions (flutter, java, nextjs-tailwind, reactjs, tailwind-v4-vite, self-explanatory-code-commenting, task-implementation) are opt-in per project. Each one you add is injected into chat.instructionsFilesLocations and recorded in the ledger. A host switch carries it over.
vespercli add instructions # pick from the list
vespercli add instructions reactjs # add one; reload VS Code to applyadd agents|prompts|skills is informational on Copilot, because every package agent, prompt and skill is already injected.
On Claude Code every specialist and skill is served by the plugin, so add is informational too. vespercli add <agents|skills> <name> --override pins a project-level copy, which shadows the plugin's version. The copy is recorded, reset --host claude removes it, and a host switch carries it over. add prompts is refused, because 2.0 provisions no slash commands.
Packs
The catalog is a core plugin plus six stack packs, and a project enables only the packs its Team Roster needs. Core (vesper) holds the support, review and generic developer/tester specialists and the skills every project uses; each pack is a separate plugin in the same marketplace (assets/claude/packs/<pack>/, plugin vesper-<pack>):
| Pack | Plugin | Agents | Skills |
|---|---|---|---|
| frontend | vesper-frontend | expert-react-frontend-engineer, expert-nextjs-developer, the five jest-react-* | reactjs, nextjs, nextjs-tailwind, react-mui-tanstack, vercel-react-best-practices, tailwind-shadcn, tailwind-v4-vite, web-frameworks, frontend-design, aesthetic, premium-frontend-ui, web-design-guidelines |
| playwright | vesper-playwright | the five playwright-* | playwright-generate-test, playwright-typescript, chrome-devtools |
| backend | vesper-backend | nodejs-express-developer, firebase-developer | backend-development, databases, better-auth, nestjs, devops |
| polyglot | vesper-polyglot | — | java, flutter, google-adk-python |
| docs | vesper-docs | — | document-skills, ai-multimodal |
| integrations | vesper-integrations | clickup-agent | gitlab-guardrails |
| graphify (opt-in) | vesper-graphify | — | graphify-research (see graphify) |
Every pack also carries a byte-identical copy of the shared agent-fundamentals skill (every agent preloads it by bare name; the generator keeps the copies in sync). vespercli catalog (the shipped assets/CATALOG.md) names each asset's pack, and assets/manifest.json carries the same mapping for the CLI.
Enabling. vespercli packs sync reads the Team Roster (.claude/rules/vesper-orchestration.md on Claude, .vesper/instructions/vesper-orchestration.instructions.md on Copilot), maps every named agent and assigned skill to its pack, and enables the packs needed. It never disables a pack (it reports the ones no longer needed). init and update run it, and the /reinit and /revalidate-team workflows end with it. By hand: vespercli packs enable <pack> / disable <pack>; vespercli packs lists what the package ships, what is enabled here and what the roster needs.
- Claude Code: one
enabledPlugins["vesper-<pack>@vesper-marketplace"]key per enabled pack in the committed.claude/settings.json, recorded in the ledger like the core key. The marketplace source in.claude/settings.local.jsonalready covers every plugin. A packed specialist is delegated to asvesper-<pack>:<name>; the roster carries that name. - Copilot: one
chat.agentFilesLocations/chat.agentSkillsLocationsentry perassets/copilot/packs/<pack>/{agents,skills}folder, injected and recorded like the core entries. Names stay plain, as everywhere on Copilot. The opt-in instructions stay a separate, per-project mechanism (vespercli add instructions) whichever pack their skill lives in.
Only the core key resolves the host: a workspace whose settings carry pack keys alone resolves nothing. reset --host <host> removes the pack keys or locations with the core ones, and a host switch replays them onto the new host. vespercli doctor reports a roster naming an asset of a disabled pack as pack-not-enabled (WARN, fix vespercli packs sync) and an enabled pack the roster does not use as pack-unused (INFO). A workspace provisioned before packs keeps working: core resolves as before, and the next init (or packs sync) enables what its roster needs.
graphify (optional)
The opt-in graphify pack gives the research-side roles (task-researcher, se-system-architecture-reviewer) a code knowledge graph built by graphify — an external tool installed on your machine (PyPI package graphifyy, CLI graphify); Vesper ships only its own graphify-research skill, which routes impact ("who calls X"), path ("how does A reach B") and architecture questions to the graphify MCP tools and keeps locate/edit questions on vesper_search / vesper_symbols. vesper-indexing remains the default codebase tool for every role; developers, testers and reviewers are unchanged.
uv tool install graphifyy # the tool (external) — or: pipx install graphifyy / python3 -m pip install --user graphifyy
vespercli packs enable graphify # the consent — prepares the workspace, see below (vespercli init asks the same question)
graphify update . # builds graphify-out/graph.json (~35 s on a 25k-LOC repo, 0 tokens) — the enable runs this for you when the tool is installedWhen graphify is on PATH and graphify-out/graph.json does not exist yet, packs enable graphify (and init's consent, -y/--graphify runs included — it is local, 0 tokens, no prompt) builds the graph right away with graphify update .; a failed build warns and leaves the pack enabled, and a re-init, packs sync or worktree bootstrap never rebuilds.
Installing the tool. Three routes, in the order Vesper prefers them: uv tool install graphifyy when uv is on PATH, pipx install graphifyy when pipx is, python3 -m pip install --user graphifyy when only Python is; with none of them, install uv first (curl -LsSf https://astral.sh/uv/install.sh | sh, or pip install uv). vespercli init offers the pack as its last follow-up question (default no, --graphify answers yes) and, interactively, when the tool is not on PATH, offers to run this machine's install command for you (Install graphifyy now? (<command>), default yes; a failed install prints the hint and never fails init). packs enable graphify makes the same offer; -y, --force-default and non-TTY runs never install anything and print the command instead. vespercli doctor's graphify-missing fix and vespercli mcp status name the same command.
packs enable graphify writes three things, all ledger-recorded and removed by packs disable graphify, reset --host and a host switch (replayed on the new host): a guarded .gitignore sub-block (graphify-out/* ignored, !graphify-out/graph.json so the graph itself is committed and shared), a guarded sub-block in Vesper's post-checkout hook that runs graphify update . after a branch checkout and is a no-op when the tool is not on PATH, and the graphify MCP server — registered on both hosts as vespercli mcp serve graphify (the same registration as vespercli mcp add graphify, and the opencode.json mirror), which runs python -m graphify.serve graphify-out/graph.json under the interpreter graphify recorded in graphify-out/.graphify_python when it built the graph (a uv tool / pipx install lives in its own venv, so a bare python would not import it), else python3 / python on PATH. When the roster lists graphify-research on the two research rows, workspace-learner writes it there and nowhere else.
Data flow. Code never leaves the machine: a code-only graphify update . is tree-sitter extraction at 0 tokens. Doc/PDF extraction and community labels call an LLM — pass --no-label to skip the labels. Agents use the MCP server (the graph stays resident), never the CLI (which reloads the ~50 MB graph.json per call), always pass path-qualified node names (matching is fuzzy), never read GRAPH_REPORT.md whole, and weight every edge by its EXTRACTED / INFERRED / AMBIGUOUS tag. vespercli doctor reports, never as an error: graphify-missing (tool not on PATH), graphify-no-graph (no graph.json yet) and graphify-stale (the graph's built-from commit is not HEAD — graphify update .).
Third-party skills (packs)
Third-party content is pack content: shipped in the npm package like every other pack asset, served through the pack's plugin (Claude) or injected folder (Copilot), pinned to an upstream commit in an UPSTREAM.json beside each SKILL.md (with the upstream LICENSE verbatim), never fetched at command time — and never written into a project. Nothing lands under .claude/skills/ or .vesper/skills/; there is no consent prompt, no --adhd/--ponytail flag and no ~/.vesperrc record any more.
- In
vesper-integrations(enabled by the Team Roster like any pack, orvespercli packs enable integrations):gitlab-ci-skillandgithub-actions-to-gitlab-ci(MIT, Copyright (c) 2026-present GitLab Inc.), as/vesper-integrations:<name>. - In the opt-in
vesper-styles:i-have-adhd(MIT, Copyright (c) 2026 Ayoub Ghriss), an output-style skill, andponytail(MIT, Copyright (c) 2026 DietrichGebert), a "laziest solution that works" decision ladder (rule text only; no upstream hooks or scripts ship).vespercli packs enable stylesis the consent. Because both styles are meant to apply on every reply, enabling the pack also writes a ~10-line Vesper-authored pointer rule —.claude/rules/vesper-styles.md(Claude) or.vesper/instructions/vesper-styles.instructions.mdwithapplyTo: '**'(Copilot) — telling the model to load/vesper-styles:i-have-adhdand/vesper-styles:ponytailat session start. The rule is recorded in the ledger (pack-rule), removed byvespercli packs disable styles,reset --hostand a host switch (and replayed on the new host); an edited rule is kept and reported. No third-party text is ever written into the repo.
A newer upstream reaches your project with the next Vesper release. vespercli packs upstream compares each pinned commit with the upstream HEAD (network, 3 s per call, never a prompt; an unreachable upstream is a row, not an error), and an attended vespercli update prints one advisory line per newer upstream. doctor, guard, worktree bootstrap and init never check.
Upgrading: init retires the copies an older Vesper placed in the project (.claude/skills/<name>, .vesper/skills/<name>, .claude/rules/{i-have-adhd,ponytail}.md, .vesper/instructions/<name>.instructions.md, the CLAUDE.md import blocks) — on proof only: a recorded hash, bytes equal to the shipped snapshot, or the pinned 1.x digests. An edited copy is kept and reported once; delete it yourself. A recorded opt-in to the styles enables the styles pack so the behaviour you chose survives.
Commands
| Command | What it does |
|---|---|
| vespercli init [--host claude\|copilot] [-y] [--force-default] [--graphify] [--path <dir>] | Provision, switch host, re-provision, or migrate a 1.x workspace. --graphify enables the opt-in graphify pack without asking (otherwise the last follow-up question; the default is no pack). --target is a deprecated alias of --host; all and comma lists are rejected. |
| vespercli doctor [--quiet] [--running-host <host>] | Read-only validation; exit 0/1/2. |
| vespercli guard [--running-host claude\|copilot\|opencode] | Session-start guard (installed as the hook, the task and the OpenCode plugin's session.created hook); opencode stands for the Claude host. |
| vespercli gate [--plan <file>] [--base <ref>] [--json] | Run the committed gate policy (.vesper/gates.json): checks, plan conformance, diff-size cap, protected paths. Exit 0 pass, 1 fail, 2 no config. See Gates. |
| vespercli log <started\|completed\|note\|delegated\|returned> --pipeline <p> [--step <s>] [--agent <a>] [--goal <g>] [--author <a>] [--status DONE\|NEED_HELP\|BLOCKED] [--slug <slug>] [--note <text>] [--stdin] [--json] | Append one row to the run ledger .vesper/.tracking/runs/YYYYMMDD-<slug>.jsonl (atomic append; --slug defaults to adhoc). --stdin reads a Claude Code hook's JSON. Exit 2 on a missing required field. |
| vespercli log tail [--slug <slug>] [-n 5] [--json] | Print the last run-ledger rows — what the orchestrator reads on resumption. |
| vespercli story move <slug> <refining\|backlog\|inprogress\|inreview\|completed> | Move a story between status folders (found by substring of the file name; refuses on 0 or >1 matches; git mv when tracked and clean, plain rename otherwise). Exit 1 refused, 2 usage. |
| vespercli story list [--folder <f>] [--json] | List stories: folder, slug and title. |
| vespercli worktree bootstrap [--running-host <host>] [--quiet] | Regenerate local state for this worktree. |
| vespercli reset [--host <host>] [--purge-knowledge] [-y] | Remove a host's artifacts. |
| vespercli update [--host <host>] | Refresh the checkout's host: the Claude committed fallback and playbook, or the Copilot surface. |
| vespercli add [type] [name] [--host <host>] [--override] | See opt-in instructions. |
| vespercli list [type] [--host <host>] (ls) | List what is enabled here. |
| vespercli catalog | Show the catalog from the installed package (offline). |
| vespercli packs [list\|enable <pack>\|disable <pack>\|sync\|upstream] [--host <host>] | See Packs: list what is shipped/enabled/needed, enable or disable one pack (styles also writes/removes its pointer rule), enable every pack the Team Roster needs, or check the third-party pack skills against their upstreams (network). |
| vespercli mcp <subcommand> [--host <host>] | See MCP servers. |
| vespercli settings <add\|remove\|list> [key] [value] | Manage Vesper's VS Code settings (e.g. read-access paths). |
On every host-scoped command --host names the host and the checkout decides when it is omitted; --target is a hidden, deprecated alias everywhere.
Re-initialize with vespercli init. The /reinit maintenance skill (Claude) or prompt (Copilot) is a different thing: it asks the Vesper agent to re-scan the codebase and refresh the Team Roster and whitepaper. There is no vespercli reinit command.
Evals (development)
The playbook and the specialists are prompts, so two eval layers guard their behavior, both real model calls and both kept out of the deterministic test gate: npm run eval drives claude -p --agent vesper-orchestrator through a fixture workspace and grades the orchestrator's classification, tiers, degraded mode, host-mismatch warning, delegation law and gate wiring (evals/orchestrator/*.json; exit 3 = skipped loudly when claude or a credential is missing); npm run eval:plugin runs the core plugin's claude plugin eval suite (assets/claude/evals/) over the specialists' output and write-location contracts. .github/workflows/evals.yml runs both weekly and on demand, skipping with a visible warning when ANTHROPIC_API_KEY is not set. See docs/releasing.md.
The other maintenance workflows are /compact-knowledge (archives completed tracking stories, trims the memory graph), /revalidate-knowledge (re-validates captured knowledge against the code) and /revalidate-team (a roster-only review). Vesper suggests them and never runs them itself.
Gates
.vesper/gates.json is the project's committed, host-neutral gate policy. vespercli init seeds it once on either host and never overwrites it; from then on it is the project's own file, like the Team Roster (reset --host <host> keeps it unless you pass --purge-knowledge). The seed detects package.json scripts — test, lint, typecheck/tsc/type-check, and build — and turns each into a check, written for the project's package manager: the packageManager field in package.json decides first ([email protected] → pnpm), then the lockfile (pnpm-lock.yaml, yarn.lock, bun.lockb/bun.lock, package-lock.json), then npm. Commands follow each manager's style — pnpm test, yarn lint, bun run build, npm test / npm run lint. A project without package.json starts with no checks. Delete the build row if next build is too slow for a gate.
{
"schemaVersion": 1,
"checks": [{ "name": "test", "command": "npm test", "detected": true }],
"diffSizeCap": { "files": 40, "lines": 1500 },
"protectedPaths": []
}vespercli gate runs every check in order (sh -c <command> from the workspace root, inheriting your environment; a failure does not stop the next check), then looks at the changed files — the working tree against HEAD by default, or against --base <ref>, untracked files included:
--plan <file>: every changed file must be named in the plan (backticked paths, and the lists under aFiles/File Operationsheading; a plan's sibling-details.mdis read too). Changes under.vesper/.tracking/are always allowed.diffSizeCap: more files, or more added+deleted lines, than the cap fails.protectedPaths: a changed file matching any glob fails.
The report is fixed-format: one line per check, then plan:, size:, protected:, and a final GATE PASS or GATE FAIL: <reasons> line. --json prints one object instead. The orchestrator reads only the exit code and the headline — 0 pass, 1 fail, 2 no or invalid .vesper/gates.json — and never the work itself. Outside a git repository the plan, size and protected checks are skipped with a note. The command writes nothing; only the checks themselves touch the tree. vespercli doctor reports gates-missing (INFO) on an initialised workspace that has no policy yet.
License
MIT
