Imported from camyujistark/vpr (
AGENTS.md). Install upstream withnpx skills add camyujistark/vpr. Copyright stays with the author.
Project agent memory
Project-intrinsic agent knowledge for vpr (Virtual Pull Request manager):
build, test, architecture, and sharp-edge notes that travel with the code.
Build / test
- Run tests with
npm test— it globstest/*/*.test.mjs. Plainnode --test test/fails (wrong glob → MODULE_NOT_FOUND). - Run one file:
node --test test/commands/<name>.test.mjs. - Pure ESM (
"type": "module"), Node's built-in test runner, no build step.
Architecture
- VCS backend seam (
src/core/vcs.mjs):createVcs()returns a backend (jjBackendinjj.mjs,gitBackendingit.mjs) implementing theVcsBackendtypedef. git (v2) is the default; jj (v1) is opt-in (jj-colocated repos auto-detect via.jj/, or pin withVPR_VCS=jj/.vpr/config.json"vcs":"jj"). - State split: the backend owns the commit graph;
.vpr/meta.jsonowns active metadata (in-flight items, VPR stories/output, hold, event log). Bookmark/branch name is the join key..vpr/is gitignored. - Terminal archive (
src/core/archive.mjs): sent VPRs and done items leave the active pool for a SQLite store at.vpr/archive.db(Node's built-innode:sqlite, no dep). One row per terminal record keyed byname(branch for sent, item name for done). Keepsmeta.jsonlean —meta.sentis retired as a growth vector.archive.mjsopens a fresh handle per call (soprocess.chdirin tests never targets a stale dir) and the read path returns empty without creating the db file.src/commands/archive.mjsis the CLI glue:migrateArchive()+vpr archive ls|get|stats|migrate. - Chain ordering lives in
src/core/state.mjscomputeChainState()— it decorates each VPR withblocked/nextUp/cascadeTarget.sendreadscascadeTargetto target each PR at the branch below it in the stack.buildState()reconstructs the chain-anchoringsentview by unioningarchiveSentMap()(from the archive) with any legacymeta.sent— so sent VPRs still anchor cascade targeting + the sent-bookmark barrier without living inmeta.json. Tests that seedmeta.sentdirectly still work via the union.
Sharp edges
- Planning must be commit-free. Materialize slices as branch POINTERS, never
empty scaffold commits — scaffolds ride in every PR's commit list and can't be
stripped once a branch is checked out in a worktree.
vpr plan slicesand the pointer path usemoveBookmark(git branch --force/jj bookmark set), both pure ref updates. jj'saddBookmarkstill does ajj newempty-commit dance on collision (needed for the partition walk of worked slices) — don't route planning through it. test/skills/*.test.mjsread files OUTSIDE the repo (~/.claude/skills/).vpr-skill.test.mjscan fail purely from skill-doc drift on the machine — it is not a code regression. Don't "fix" it by editing files outside the worktree.- git worktrees:
bin/vpr.mjsrefuses to run inside a.git/worktrees/*checkout (jj can't colocate there). Run vpr from the primary checkout. sendgates on a non-empty story and on no conflicts, and refuses to send a VPR whose predecessor is unsent (sequential push) or that would drag another VPR's commits into the PR (stowaway check;--forceoverrides).
Send flow (send.mjs)
send(query)— one slice: gate → rename tofeat/<wi>-<slug>→ push →provider.createPR(...)→ archive the VPR (archiveTerminal, statussent) and drop it frommeta.items. Branch name viasliceBranchName()(single source of truth).ticketDone(name)likewise archives (statusdone) instead of just deleting the item.sendAll({...})— batch: loopresolveNextUpBookmark→sendoldest-first; stop at first blocker, skip already-sent on re-run.dryRunpreviews only.- Locked planning decisions in
.vpr/config.json(vpr plan lock/vpr initflags):provider,workItemModel(one-pbilinks the feature PBIitem.parentWion every PR;per-slicelinks the slice's own wi),storySource.resolveWorkItemId()inplan-lock.mjsapplies the model.