claude-code-plugins / vellum
AGENTS.md6 starsChanged 11 days agoMITView on GitHub
A Claude Code plugin: /vellum:start enters a mode the hooks module holds, where Claude writes plan.md and calls mcp__vellum__submit; the plan and its artifacts open in the browser, and the reviewer's answer reaches Claude as a prompt. /vellum:stop leaves the mode. Five kinds of folder, each with its own job. Only the parts share a form: - the parts, src/steps/ and src/formats/, a folder each: a step Vellum follows, or the proposal that offers them; a format a document is…
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,774 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository9 other files it gives its agents.
- AGENTS.mdAGENTS.md
- atlas/AGENTS.mdAGENTS.md
- atlas/CLAUDE.mdCLAUDE.md
- CLAUDE.mdCLAUDE.md
- vellum/CLAUDE.mdCLAUDE.md
- field-testSkill
- landSkill
- leads-gateSkill
- leads-scanSkill
The file
We split this AGENTS.md into the rules it gives an agent
2 rules. About 0 are common, 1 is distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 98
Every command runs from the repository root.
- Very specific: line 103
Only the hooks module posts the heartbeat, the page does not: alone, an open tab keeps it for a while, or post the heartbeat in a loop with …
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Vellum
A Claude Code plugin: /vellum:start enters a mode the hooks module holds, where Claude
writes plan.md and calls mcp__vellum__submit; the plan and its artifacts open in the
browser, and the reviewer's answer reaches Claude as a prompt. /vellum:stop leaves the mode.
Shape
Five kinds of folder, each with its own job. Only the parts share a form:
- the parts,
src/steps/andsrc/formats/, a folder each: a step Vellum follows, or the proposal that offers them; a format a document is read in. Every one is a slice, read the same way: acontract.tsthat declares it (defineSlice) and a half per runtime it plugs into (hooks.ts,server.ts,page.tsx); one with a part of the workflow adds its pure model, named after the folder, and itswalk.ts, one that parses what it did not produce itsparse.ts; its helpers and their specs sit beside.formats/image/is the smallest, a contract and a page half. A part reads the review throughreview/contract.tsas types and takes a value of it from its frozen surface; the folder takes the glossary's word (CONTEXT.md); - the review,
src/review/, the frame the parts plug into, which does what no part does: it owns the state every part reads (the draft, the Send and its batches, the versions, the approval), assembles each part's share of a Send, and hands the formats their composer and selection. It keeps a slice's files but is no slice: the runtimes reach it by name, its part first in the table, never through a registry. It stays one frame, never a slice per feature (gate, Send, approval): those share one state machine and one directory layout, and the first review's bugs were exactly cross-feature state; - the runtimes,
src/runtime/: the hooks module in Claude Code, the server, the page. Each owns a process and its IO, runs the review by name and the parts through its registry alone, and turns each part's half into its own representation of it (*Extension); - a pure machine,
src/workshop/: the workflow judged against a table of rows (next), the view a reader takes of it, and the machinery the review and the parts are built with; it names no event of anybody's and imports nothing outside itself; - the platform,
src/platform/: the repository's file, path and process helpers, a copy oftools/platform/thatbun ./scripts/gates/sync-platform.ts --writewrites, never edited here. A leaf, it imports nothing of the plugin:pure.tsnames no Node and no Bun, so the hooks module may load it; the other modules the server andscripts/alone.
hooks/hooks.json Claude Code's folder: it names the hooks module, nothing else lives there
skills/start, skills/stop the way in and the way out
src/workshop/ pure, no IO: the workflow (`next`, the table), view, rows, waits, plugs
(`defineSlice`), paths, workspace, channel, slug, links, vellum-build
src/review/ the frame: contract.ts its events, rows and types; surface.ts and composer.tsx
what a part may use of it; review, feedback, diff its domain; events.ts its
model; server.ts, routes.ts, draft.ts, parse.ts its server half; hooks.ts its
tool and turn-end gate; comments, composer, decision-bar, editor, anchoring,
selection, caret its page
src/runtime/hooks/ the hooks module: register.ts spells `$`, the rest takes a `Host`; client.ts the server's
│ HTTP, token header, down; the server's stdout, up
src/runtime/server/ the server: cli.ts `serve`, preview.ts the page alone on any directory, queue.ts the
queue and the step, effects.ts, http/ routes and serve, every IO, slice.ts
src/runtime/page/ the React page
src/runtime/protocol.ts what crosses HTTP, the server's stdout and a part's boundary; JSON
src/runtime/extension.ts what a part's page and server halves fill (a hooks half: runtime/hooks/extension.ts), and
the runtimes' own representation of a part, which the registries make from them
src/runtime/*/slices.ts each runtime's registry, the only way it reaches a part
src/platform/ a copy of tools/platform/, never edited here: pure.ts quoting, errno, messages and
lexical paths; fs.ts writes and renames; process.ts processes; git.ts git
src/steps/<name>/ a step Vellum follows, or the proposal that offers them: grill/, agent-review/, proposal/
src/formats/<name>/ a format a document is read in: markdown/, html/, image/
src/proof.ts the proof of the table, the tests' alone: each part's walk.ts, alone, then in pairs
scripts/contract-diff.ts what a reviewer reads of a range before its code, as Markdown for the PRDependencies point toward src/workshop/, which imports nothing outside itself, not even a
type. The review imports the workshop and the runtimes, never a part. A part imports the
workshop, what a runtime's folder offers the halves it fills, the review's contract and another
slice's contract.ts as types, and the review's surface; a runtime reaches a part through its
slices.ts alone; the hooks module imports the platform's pure.ts, the server and scripts/
the platform whole, and no other folder any of it, as src/boundaries.integration.spec.ts holds. The rules of each zone load with its
files, from .claude/rules/: workshop.md, engine.md, server.md, page.md,
extensions.md, slices.md, tests.md.
The tree is drawn here and nowhere else: a rule names the files of its own zone, every other
text points at this section. A fact about Claude Code's engine goes to the relevant page linked from
Plugin testing, once, and is pointed at from here. An example is code that compiles
and is tested (extensions.md names the ones to copy), never a snippet kept in a doc.
Commands
bun install --cwd vellum --frozen-lockfile # once; Claude Code does it at the plugin's cache
bun test vellum # the server's and the page's `*.spec.ts` suites
bun test vellum/src/workshop/slug.spec.ts # one suite; `-t <pattern>` filters by test name
bun run --cwd vellum e2e -- kit.e2e.ts --project=light-1024 # one suite, one window, about 10 s: how a lot is worked on, and how a red test is reproduced
bun run --cwd vellum e2e -- --project=light-1440 # the whole suite at one window: once, before a push
bun run --cwd vellum e2e # the five windows of `e2e/playwright.config.ts`: CI's, a job per window (`--project=<window>`), on request (the `e2e` label on a PR), not a local one
bun run --cwd vellum e2e:install # Chromium, once per machine and per pinned Playwright
bun run --cwd vellum contract-diff <base>..<head> # for a PR: the contracts' diffs, what Claude reads, the rules changed, the test titles, the bodies changed, the boundary and walk suites at <head>
claude plugin test vellum # the hooks module's `*.test.ts` (runtime/hooks, review, steps/<name>), through the engine's kit
claude plugin validate vellum # what the hooks module hooks and calls
command claude --permission-mode default --plugin-dir vellum # a live session from source
bun vellum/src/runtime/server/cli.ts serve --session <id> --project <dir> --workdir plans/<date>/wip-<sid8>/ # the server alone, for page work; the trailing slash is required; `--port <n> --token <t> --existing` revives one where it was
bun vellum/src/runtime/server/preview.ts <dir holding plan.md> [--minutes <n>] [--port <n>] # the same page on any directory, working or final: it prints the URL and serves a copy it takes away on the way out
bun ./scripts/headless/claude-code-types.ts regenerate # types/ from the installed Claude Code, in this checkout; a session start instead rolls them into the chore/claude-code-types pull request (docs/plugin-testing/hooks.md)Every command runs from the repository root; lint, types and format are the repository's
gates, listed in its AGENTS.md. The server alone prints its ready line, port and token, then
a JSON line for each entry of the channel and each change of the review, and serves the page
at http://127.0.0.1:<port>/t/<token>/; it exits once its last POST /api/heartbeat is past
the grace and no tab holds the event stream (WATCHDOG in http/serve.ts holds both delays).
Only the hooks module posts the heartbeat, the page does not: alone, an open tab keeps it for
a while, or post the heartbeat in a loop with the header
x-vellum-token: <token>. A POST /api/gate or POST /api/open sent while no tab holds the
event stream calls the opener (streams.open === 0 in routes.ts), so a gate driven by curl
opens a tab on the human's desktop (openerOf in browser.ts); bun test alone is spared, by NODE_ENV.
preview.ts takes that heartbeat off your hands: nothing beats it, so --minutes is its whole
lifetime, thirty by default, and a tab holds it no longer. It copies the directory into a scratch
plans/<date>/wip-<sid8>/ rather than linking it, since a listing keeps files alone and a link is
not one, and since the server writes .review/ and an edited plan.md where it serves: an edit
of a source document reaches the next run, never the one in flight.
The copy carries .review/, so the page opens where the source left the review: drafting on a
directory that has none, inReview v<last> on any plan, final or not, with the batches sent on
that version counted, and the page takes comments in both. The source's own .review/draft.json
stays behind, its annotations naming the paths of a directory nobody serves here. Run the command
from the repository root: the project is the current directory, and anywhere else the plan's cited
files resolve against the wrong root and plans/ is made where you stand. Ctrl-C, SIGTERM, the
lifetime running out and an approval in the page all take the copy away, under the name the
approval renamed it to; SIGKILL leaves it, and find plans/<date>/ -type f -delete then
find plans/<date>/ -depth -type d -empty -delete finishes the job.
For live hook sessions, read Hook tests.
For engine observations, read its linked runtime reference and plans/2026-09-15/plan-review-rewrite/.
Boundaries
types/claude-code.d.tsandtypes/claude-code-tools.d.tsare Claude Code's own, copied from the.claude-plugin/types/it writes, never edited;vellum/types/**is ignored by the linters.package.json+bun.lockcarry every runtime dependency; a new one is a decision to propose first, not a convenience.- The plugin stands alone: installed, it is a copy of this folder with no repository around it.
tsconfig.jsonhere names React as the JSX runtime, and without it the installed page answers 500.src/standalone.integration.spec.tsserves the page from a copy under the temp directory; what the page needs to build lives in this folder, never in a parent.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

