checkpointer
v0.3.0
Published
Git-isolated checkpoints that humans and AI agents share — save working states, revert anytime, ship a range as one clean commit, and map your TypeScript/JavaScript or Java call graph in the browser.
Downloads
889
Maintainers
Readme
checkpointer
Git-isolated checkpoints that humans and AI agents share — save working states, revert anytime, ship a range as one clean commit, and map your TypeScript/JavaScript or Java call graph in the browser.
When you (or an AI agent) work through a long task, you want save points: a way to
mark "this works", roll back when something breaks, and at the end turn all that
churn into one clean commit. checkpointer does exactly that, in a shadow git
repo kept outside your project — so your real git log stays clean until you ship.
- Shared by humans and agents — same commands, same checkpoints. Works with Claude Code, Cursor, or any tool that can run a shell command.
- Isolated from your repo — checkpoints live in
~/.checkpointer, never in your project's.git. Nothing shows up ingit loguntil youship. - Captures everything — unlike editor undo, it snapshots the whole working tree, including files changed by scripts, formatters, and codemods.
- Ships to one commit — bundle a range of checkpoints into a single commit on your branch, then push when you're ready.
- Maps your code —
checkpointer graphopens an interactive, searchable function call-graph of your TypeScript/JavaScript or Java project in the browser, so you can see how functions reach each other before you change them. Polyglot repos render as one combined graph. Fully offline.
Install
npm install -g checkpointer # then: checkpointer <command>
npx checkpointer <command> # or run without installingRequires Node 18+ and git. Also available as ckpt.
Without npm (git clone)
If you can't access the npm registry, clone the repo — dist/cli.js is committed and is a single self-contained bundle with no runtime dependencies:
git clone https://git.soma.salesforce.com/v-akshay/checkpointer.git
cd checkpointer
node dist/cli.js --helpTo use it like a regular command (ckpt) without typing node dist/cli.js every time:
# macOS / Linux — copy to somewhere on your PATH
sudo cp dist/cli.js /usr/local/bin/ckpt
sudo chmod +x /usr/local/bin/ckpt
ckpt --help
# or add the dist folder to your PATH in ~/.zshrc / ~/.bashrc
export PATH="$PATH:/path/to/checkpointer/dist"
alias ckpt="node /path/to/checkpointer/dist/cli.js"Then cd into any git project and start checkpointing:
cd ~/my-project
node /path/to/checkpointer/dist/cli.js init # or just: ckpt initTo rebuild from source (only needed if you edit the TypeScript):
npm install # installs tsup + typescript — dev tooling only
npm run buildQuick start
checkpointer init # set up checkpointing here
checkpointer save before-refactor # snapshot before risky work
# ... make changes ...
checkpointer save working --status working # mark a known-good state
checkpointer restore before-refactor # something broke? roll back
checkpointer ship -m "Refactor auth module" # bundle it all into one commit
git push # ship never pushes — you doConcepts
- Checkpoint — a full snapshot of your working tree at a moment in time, with a name, a status, and (optionally) who made it and why.
- Status —
working(verified good),wip(in progress, the default), orbroken(known bad).shiprefuses to includebrokencheckpoints unless forced. - Shipping — replaying the state of a chosen checkpoint onto your real repo as a single commit. Everything since the last ship is bundled together.
- Shadow repo — a hidden git repository per project under
~/.checkpointer. It is how snapshots are stored cheaply; it never touches your project's.git.
Commands
| Command | What it does |
| --- | --- |
| init | Initialize checkpointing for the current project |
| save [name] | Snapshot the working tree (auto-names cp-N if no name) |
| list (ls) | List all checkpoints |
| status | Show how many checkpoints are waiting to be shipped |
| changed [id] | Probe whether the working tree differs from a checkpoint (exit 100 if not) |
| show <id> | Show a checkpoint's details and tracked files |
| diff <a> [b] | Diff two checkpoints, or one checkpoint vs current files |
| restore <id> | Reset the working tree to a checkpoint (auto-saves first) |
| tag <id> <status> | Mark a checkpoint working, wip, or broken |
| ship | Bundle unshipped checkpoints into one commit on your repo |
| graph | Visualize the project's functions and call graph in your browser |
| info | Show where checkpoints are stored and which paths are excluded |
| skill install | Install the Claude Code skill so agents know how to use this |
Reference any checkpoint by name (login), sequence (#4), or short sha.
Run checkpointer <command> --help for full options and examples.
Machine-readable output (--json)
Add --json to any command for a stable, versioned envelope on stdout:
// success
{ "schema": "checkpointer/v1", "ok": true, "data": { /* command payload */ } }
// failure (also exits non-zero)
{ "schema": "checkpointer/v1", "ok": false, "error": { "message": "...", "code": "..." } }Branch on ok (and on the exit code) rather than parsing prose. Fields inside
data are additive; schema only changes on a breaking envelope change.
Exit codes
Commands exit 0 on success. On failure the exit code is stable and tells you
why, so scripts and agents can react without parsing the message:
| Code | Meaning | Examples |
| --- | --- | --- |
| 1 | generic / unexpected | a git plumbing failure |
| 3 | environment not ready | not initialized, not a git repo, lock held, corrupt metadata |
| 4 | bad input | no such checkpoint, duplicate name, invalid status, missing message |
| 5 | safety refusal — a human decision is needed | shipping a broken checkpoint without --force; shipping onto a detached HEAD; shipping a checkpoint a restore set aside |
| 6 | benign no-op — nothing to do | nothing unshipped, repo already matches the checkpoint |
Key options
checkpointer save v1 --status working -m "first working version"
checkpointer save --intent "extracting the parser" # record why (for agents)
checkpointer ship --dry-run # preview what will commit
checkpointer ship --upto v1 -m "Ship first version" # ship up to a checkpoint
checkpointer ship --force -m "..." # include broken checkpointsSafety
- Restore can't lose work. Every
restorefirst auto-saves your current state, so you can always restore that to undo. These auto checkpoints are marked and don't count toward what gets shipped. Restoring an earlier checkpoint never deletes the later ones — they stay inlistso you can jump straight back to them; they are only "set aside" (marked under a divider) so a latershipwon't bundle the line of work you rolled away from. - Ship follows the live line of work.
shipdefaults to the latest checkpoint a restore hasn't set aside, and refuses an explicit--uptoon a set-aside one — so after a rollback it always commits the tree you're actually on, never a stale or abandoned snapshot. To ship set-aside content,restoreit (orsaveyour current state) first, which makes it live again. - Ship only adds a commit.
shipbuilds the commit from a checkpoint's snapshot using git plumbing — it never resets your working tree, so uncommitted edits and untracked files are left exactly as they were. - Secrets stay put.
.env,*.pem,*.key,secrets.*,node_modules, and your.gitare never snapshotted — so a restore never overwrites them. Seecheckpointer infofor the full list; add your own patterns in the shownignorefile. - Saving is idempotent. A plain
savewith nothing changed reuses the latest checkpoint instead of creating an empty duplicate, so saving often is cheap. Pass--allow-empty(or a name/message/status) to force a checkpoint anyway. - Nested git repos are skipped, not silently broken. A submodule or accidental inner
repo can't be snapshotted faithfully, so checkpointer warns and skips it (listing it in
nestedReposunder--json) rather than recording a dangling pointer. The directory is left on disk untouched; a restore simply won't recreate it. - Nothing leaves your machine. No telemetry. Checkpoints are local git objects.
Visualizing your code (graph)
Debugging an unfamiliar codebase is easier when you can see how functions call
each other. checkpointer graph parses your TypeScript/JavaScript or Java project
(so calls resolve accurately — methods, this., inheritance, and imports, not just
name matches) and opens an interactive map in your browser:
checkpointer graph # analyze + serve on localhost + open the browser
checkpointer graph --no-open # just print the localhost URL
checkpointer graph --out graph.html # write a standalone, shareable HTML file
checkpointer graph --json # emit the raw graph (nodes, edges, roots)
checkpointer graph --root ../other # analyze a different projectIn the viewer you can:
- Read top-down from each entrypoint (a "root" — a function nothing in the project calls) and click any function to expand what it calls, drilling as deep as you like.
- Search any function by name or file; jump straight to it.
- Focus a function to see its ancestors (everyone who reaches it, up to the entrypoints) beside its descendants (everything it calls) — both expandable.
- See each function's full signature with type-checker-resolved parameter and
return types — so you learn what it takes and returns even when there's no comment —
alongside its description, a per-parameter table (name, type, optional, JSDoc
@paramprose when present), kind (function/method/constructor), async flag, andfile:line, with recursion and cycles flagged inline. - Hit "view code" on any function to read its source inline, embedded in the page — nothing is fetched.
The page is fully self-contained and offline — no CDN, no telemetry, the graph data is
embedded inline. --out produces a single file you can commit or share. Analysis is
read-only and never creates a checkpoint.
graph analyzes TypeScript/JavaScript (.ts/.tsx/.js/.jsx/.mjs/.cjs) and
Java (.java). TS/JS calls resolve through the TypeScript compiler; Java is parsed
with java-parser and resolved through a
symbol table (fields, locals, parameters, and the superclass chain) so this.m(),
obj.m(), Type.staticM(), inherited methods, and new X() all map to the right
declaration. A repo with both languages renders as one combined graph; support for
more languages is planned. In a project with no analyzable sources it says so cleanly —
the other commands (save/restore/ship) work in any repository regardless of language.
Using it with AI agents
checkpointer is just a CLI, so any agent that can run shell commands can use it.
Agents identify themselves with environment variables so their checkpoints are
attributed:
export CHECKPOINTER_AGENT=claude-code
export CHECKPOINTER_SESSION="<session-id>"
export CHECKPOINTER_AUTHOR="<name>" # optional: override the attributed authorThen a checkpointer save records the agent and session automatically.
Claude Code
Install the bundled skill once and Claude Code will know when and how to checkpoint:
checkpointer skill installThis copies a skill into ~/.claude/skills/checkpointer. Claude will then save
checkpoints before risky changes, roll back on failure, and offer to ship when you're
done.
Cursor and other agents
Point the agent at the command reference above, or copy the skill's guidance into your
agent rules (checkpointer skill path prints the bundled skill directory; read
SKILL.md inside it). The same env-var protocol applies.
How it works
For each project, checkpointer keeps a bare git repo at
~/.checkpointer/projects/<id>/repo.git. Every checkpoint is a commit there, made with
git --git-dir=<shadow> --work-tree=<project> so your project's .git is never read or
written. ship transfers the chosen checkpoint's objects into your real repo and builds
one ordinary commit from its tree — without resetting your working tree, so your
uncommitted work is untouched. It never pushes.
Set CHECKPOINTER_HOME to store checkpoints somewhere other than ~/.checkpointer.
License
Apache-2.0
