Save the boundary of each task while you work. Turn the boundaries into clean Git commits later.
$ git stone "add login" -d "Adds login(). Covers the /login route."
Stone 1 created: add login
1 file changed, +1 -0
Adds login(). Covers the /login route.
$ git stone "add route guard"
Stone 2 created: add route guard
2 files changed, +2 -0
$ git stone commit-all
[feature/auth e06a1bc] add login
1 file changed, 1 insertion(+)
Committed Stone 1 as e06a1bc: add login
[feature/auth 1d5c432] add route guard
2 files changed, 2 insertions(+)
Committed Stone 2 as 1d5c432: add route guard
No stones are left.
You (or an AI coding agent) work through many tasks on one branch without stopping. To end up with atomic commits today, you have two choices:
- Commit after every task. That breaks your flow, and you must decide the commit message and contents while your head is still in the code.
- Split everything at the end. That is slow by hand, and an agent often deletes and rewrites its own code just to make the pieces come out atomic.
A Stone saves the state of your whole working tree at the moment a task ends. It costs one command and about 100 ms. Your files, your staging area and your branch are not touched. Later you turn the stones into ordinary commits, one by one, without rewriting any code.
HEAD ─ commit A ──────────────────────────────────────────────► working tree now
│
├─ Stone 1 "add login" (everything up to the end of task 1)
├─ Stone 2 "add route guard" (everything up to the end of task 2)
└─ Stone 3 "rate limit" (everything up to the end of task 3)
Diff of a stone = the stone before it → this stone.
- A stone is a full snapshot of the working tree, kept as a Git tree object under
refs/stones/. - A stone's diff starts at the previous stone, so it shows only that task's changes.
git stone 1 commitmakes a realgit commitwhose content is exactly Stone 1.
Four things to remember:
- A Stone is a boundary, not a branch.
- Dropping a Stone removes the boundary, not the code.
- Stones are sequential and may depend on earlier Stones.
- Final commits are ordinary Git commits.
A Stone is not a Git commit, a branch, a stash, or a patch that must be replayed onto later code. It is a complete snapshot of the working tree at the end of a task. Each branch has its own ordered chain. A stone's diff is taken against the previous surviving stone (or the original branch commit, for the first one). So Stone 2 can include the finished work of Stone 1 without pretending the two tasks are independent.
No. It does not move the branch, touch your files, or modify your real index. Git Stone copies the index to a temporary
file and lets Git build the snapshot there. The result is what git add -A plus git write-tree would give you,
without staging anything. The final commit is made later by a real git commit against a temporary index, so hooks,
signing, identity and the message editor all behave normally.
You need Git on your PATH (developed and tested with Git 2.53; repair needs 2.40 or newer).
macOS and Linux, prebuilt binary:
curl -fsSL https://raw.githubusercontent.com/HarshKothari88/git-stone/main/install.sh | shThis puts git-stone in ~/.local/bin (set PREFIX to change that) together with its man page. Git runs any
git-<name> program on your PATH as git <name>, so git stone works right away. Check with git stone version.
Homebrew: brew install HarshKothari88/tap/git-stone
With Rust: cargo install --git https://github.com/HarshKothari88/git-stone
Windows: download git-stone-x86_64-pc-windows-msvc.zip from the
releases page and put git-stone.exe on your PATH.
Windows builds are not tested yet.
Let your AI agent use it (Claude Code, Codex). One command adds a short, marked block to the agent's memory file and keeps everything else in it as it is:
git stone setup --agent claude # this project: CLAUDE.md
git stone setup --agent both --global # every project: ~/.claude/CLAUDE.md and ~/.codex/AGENTS.mdDetails and other tools: docs/ai-agents.md.
You are on a branch and just finished the first task.
$ echo "export function login() {}" > src/auth.ts
$ git stone "add login" -d "Adds login(). Covers the /login route. Tests come in the next stone."
Stone 1 created: add login
1 file changed, +1 -0
Adds login(). Covers the /login route. Tests come in the next stone.
The second task builds on the first. Nothing special is needed.
$ echo "export function guard() {}" > src/guard.ts
$ echo "// guard" >> src/auth.ts
$ git stone "add route guard"
Stone 2 created: add route guard
2 files changed, +2 -0
You keep working. Where do things stand?
$ echo "// wip" >> src/app.ts
$ git stone
Branch: feature/auth
Stones:
1 add login
2 add route guard
Current changes since Stone 2:
M src/app.ts
Unstoned changes: 1 file
Review what a stone holds, or all of them at once:
$ git stone review
Stone 1 add login
Adds login(). Covers the /login route. Tests come in the next stone.
1 file changed, +1 -0
+1 -0 src/auth.ts
Stone 2 add route guard
2 files changed, +2 -0
+1 -0 src/auth.ts
+1 -0 src/guard.ts
CURRENT
1 file changed, +1 -0
+1 -0 src/app.ts
Commit the first stone. Only that stone's content goes in; your unstoned work stays where it is.
$ git stone 1 commit -m "feat: add login"
[feature/auth e06a1bc] feat: add login
1 file changed, 1 insertion(+)
Committed Stone 1 as e06a1bc: feat: add login
Stone 2 is now next.
Or commit everything in order, using the stone names as messages:
$ git stone commit-all
git status now shows only src/app.ts, the work you had not stoned.
Instead of committing, you can change your mind about a boundary and drop it. No file changes; the next stone just grows to cover it.
$ git stone 2 drop
Dropped Stone 2: add route guard
No files were changed.
Your unstoned changes now include all changes from the old Stone 2 boundary.
Say you are adding invoice exports to an existing app.
- Implement CSV export that uses the invoice list's current filters and permission checks.
- Review it (with your agent, if you use one). Change what is wrong or awkward. Rerun the relevant tests.
- Happy with the task? Record its boundary.
- Start a separate task for date and timezone handling, review it, record another boundary.
- Review everything together, then make commits when you are ready.
git stone "invoice CSV export" \
-d "Exports invoices with existing list filters and permission checks."
git stone "timezone-safe export dates" \
-d "Formats exported dates using the billing timezone and tests boundary cases."
git stone review
git stone commit-all # all of them, names as messages
git stone 1 commit -m "feat: export invoices to CSV" # or one at a time, your own messageThe -d text keeps what the task did, why it was needed, and what was left for the next task. You read it later,
when the details are no longer in your head.
Git Stone separates implementing and reviewing from the decision to commit. You ask the agent for a task, inspect the result, change the approach, and keep talking until it is right. The stone is made when the task is finished, not after every reply. The agent never has to guess the task boundaries from a final diff.
- Setup.
git stone setup --agent claude(orcodex, orboth --global) adds a short, marked block to the agent's instruction file. It tells the agent to make a stone after each finished task, to write a real-ddescription, to rungit stone reviewat hand-over, and never to rewrite code to make commits atomic or usedropto undo code.git stone setup --updaterefreshes it after an upgrade.git stone agentsprints it for other tools (Gemini CLI, Cursor, Copilot, Windsurf). It owns only the lines between its markers, so the rest of your file is kept. - Who commits. By default the block leaves commits to you. The agent makes stones; it commits only when you ask.
When you are ready, ask it to run
git stone review, explain what it saved, and then rungit stone <id> commitorgit stone commit-allas you direct. - Enforcement. Instructions alone do not stop a plain
git commit. Turn on the guard for that. - Not an approval. A stone is not an automatic review. You still decide when a task is done and when it becomes a commit.
Machine-readable output for scripts and agents: git stone status --porcelain, git stone list --porcelain.
| Command | What it does |
|---|---|
git stone <name> [-d <text>] |
Make a stone. -d says in detail what the task was and why. |
git stone <name> -x <pattern> [--remember] |
Leave matching paths out of this stone. Globs work ('*.log', scratch/). --remember keeps the rule for every later stone in this repository, on every branch. |
git stone guard [block|warn|off] |
While a branch has stones, refuse (or warn about) plain git commands that move it, so agents must use git stone <id> commit. See The guard. |
git stone exclude [--remove <pattern> | --clear] |
Show the remembered rules, or remove one or all of them. |
git stone -- <name> |
Same, for a name that looks like a command, a number, or starts with -. |
git stone / git stone status [--porcelain] |
Stones, current changes, warnings. |
git stone list [--porcelain] |
One line per stone. |
git stone review |
Per stone: description, files, +/- counts; then the current changes. |
git stone diff <id|current> [git diff args] |
A stone's diff, or the unstoned changes. Extra arguments go to git diff. |
git stone show <id> |
Header, description, stat and diff of one stone. |
git stone <id> commit [options] |
Commit the first stone. Accepts -m, -F, -e, --no-verify, -S, --author, --date, -s. |
git stone commit-all [--edit] |
Commit every stone in order, with the stone name as the message. |
git stone <id> drop / git stone drop all |
Remove a boundary, or every boundary. No file changes. Works also after the branch moved. |
git stone repair [--apply] |
After the branch moved: show (or do) the replay of the stones onto the new HEAD. |
git stone adopt <branch|detached> |
Move a chain to the branch you are on (after a rename, or after git switch -c). |
git stone prune [--apply] [--chain <ref>] |
List (or remove) chains of deleted branches and removed worktrees. |
git stone setup --agent <claude|codex|both> [--global] |
Add the AI-agent block to CLAUDE.md / AGENTS.md (this project, or with --global your own agent folder). |
git stone setup --update |
Refresh the block in every CLAUDE.md / AGENTS.md that already has it (this project, ~/.claude, ~/.codex). Creates nothing. git stone status tells you when a block is older than your install. |
git stone agents |
Print that block, to paste into any other tool. |
create also takes --allow-empty and --force. git stone <id> <verb> and git stone <verb> <id> both work.
Agents that make stones sometimes still run a plain git commit. That moves the branch under the stones, and later
repair may conflict because the branch already holds the work. The guard stops it before it happens:
git stone guard block # while a branch has stones, a plain git command that moves it is refused
git stone guard warn # allow it, but print a warning
git stone guard off # remove it- It is a small
reference-transactionhook in.git/hooks, and a setting (stone.guard) in this clone's.git/config. Git runs that hook also forgit commit --no-verify, so the agent cannot skip it that way. - Refused:
commit,commit --amend,reset,rebase,pull,merge... any update that moves a branch that has stones. Always allowed:git stone <id> commitandcommit-all, making stones, tags, new branches, and branches without stones. The message names the way out: commit the stones with Git Stone, orgit stone drop <id>/drop all. - It stops accidents, not intent: anyone can run
git stone guard off. The agent block in docs/ai-agents.md tells agents to leave it alone. - If you already have a
reference-transactionhook, orcore.hooksPathpoints to another folder (husky and similar), Git Stone does not touch it. It prints the three lines to add yourself. - A detached HEAD is not guarded.
Show the table: what each Git command does to your stones
Stones live in refs, not in your working tree, so most Git commands do not affect them.
| You do | Result |
|---|---|
git add, restore, stash, reset --hard, clean |
Stones are untouched. After a reset --hard, bring work back with git restore --source=refs/stones/heads/<branch>:trees/<id> --worktree -- . |
| Make the commit yourself that equals the first stone | Recognised. The stone is marked as committed. |
Any other commit, pull, rebase, reset, amend, merge |
The branch moved. Commands that change stones refuse; reading and drop still work. git stone repair puts the stones back on the new HEAD. If it conflicts because the branch already holds that work, git stone drop <id> or git stone drop all clears the stones. |
| Merge or rebase in progress, or conflicts | create, commit, repair --apply refuse. Reading and drop work. |
git switch other and back |
Every branch has its own stones. Nothing is lost. |
git branch -m old new |
git stone warns; git stone adopt old moves the stones. |
| Detached HEAD | Works. Stones stay with that worktree. git stone adopt detached after git switch -c. |
git gc |
Live stones are kept. Dropped stones are collected once their reflog expires. |
Details and examples: docs/git-interactions.md.
Show the limits
- Stones are local.
git pushandgit clonedo not carry them (git push --mirrorwould). - A snapshot is what
git add -Awould stage. Ignored files are not in it. Files marked assume-unchanged or skip-worktree keep their index version. A submodule is recorded at its checked-out commit; uncommitted changes inside it are not captured (git stone statuswarns). Renames are not stored; diffs detect them. - Excluded paths are not in the stone (
-x,--remember). A tracked file that matches stays at its version from the previous stone, so your edits to it are not captured andgit reset --hardcannot restore them from a stone. Rules live in this clone's.git/config(stone.exclude): they apply on every branch, and they are not pushed. Every stone prints what was left out, so a rule that matches nothing (0 files) is easy to spot. - The
post-index-changehook can fire on the temporary index used for a snapshot. - A big unignored folder (for example
node_modules) ends up in the snapshot. Making a stone warns when it adds more than 1000 new paths. - Only the first stone can be committed. Commit it, or drop it to fold it into the next one.
- Stone names are one line (up to 200 bytes); descriptions up to 10000 bytes. Both are immutable once made.
Show the timings
Measured on an Apple M5 laptop, one stone per task of 10 changed files, median times
(see docs/design.md and bench/bench.sh):
| Files in the repo | 10 | 1 000 | 5 000 | 20 000 |
|---|---|---|---|---|
git status (for comparison) |
5 ms | 8 ms | 12 ms | 27 ms |
git stone <name> |
77 ms | 120 ms | 127 ms | 144 ms |
git stone status |
58 ms | 97 ms | 125 ms | 136 ms |
git stone list |
33 ms | 32 ms | 38 ms | 32 ms |
git stone review (100 stones) |
497 ms | 575 ms | 596 ms | 608 ms |
git stone 1 commit |
86 ms | 118 ms | 166 ms | 155 ms |
Each command starts a handful of Git processes (about 4 ms each), which is most of the time in small repositories.
A stone costs about the same as git add -A on a copy of your index, so it grows with the number of files like
git status does, and an unchanged 100 MB binary adds nothing.
Show the questions
Is it a replacement for Git? No. It uses Git's own objects, refs, diffs and commit path. Commits, branches and remotes stay as they are. Stones are local records of task boundaries: teammates receive the eventual commits, not your stones.
Can I commit stones in any order? No. Only the first surviving stone can be committed next, so each commit matches its recorded snapshot and the chain stays in order. Work you have not stoned yet is kept out of the commit.
What if I change my mind about a boundary? git stone 2 drop removes the boundary, not the code. The next stone
simply covers the changes the dropped one used to separate. To change code, change the working tree and make a new
stone. IDs are not reused in a chain, even after a drop.
Branches, detached HEAD, worktrees? Each branch has its own chain, and switching away and back mixes nothing.
Detached HEAD works, including linked worktrees. git stone adopt moves a chain after a rename or git switch -c.
git stone prune finds chains of deleted branches and removed worktrees.
Secrets, local config, generated files? Use -x '.env.local' on the stone. Add --remember to make it a
persistent rule for this clone. Left-out files are not in the snapshot and cannot be recovered from it, so check the
Left out: line each stone prints. See Limits.
Show how it is built
Git Stone is written in Rust, with no runtime dependencies. It runs the git binary instead of keeping its own VCS code.
Snapshots and diffs are Git trees. Stone history lives under refs/stones/. Each chain update writes a new state object
and moves its ref with compare-and-swap, so an interrupted run leaves either the old state or the new one, never half.
The most useful things to challenge: the snapshot model, recovery after the branch moves, the guard and its hook integration, the agent instructions, path exclusions, and how this fits your habits around commits and review. It is open source and needs no account or signup.
- docs/design.md: the design and the reasons behind it.
- docs/internals.md: the exact ref and object layout, and how to read or recover stones with plain Git.
- docs/git-interactions.md: what every normal Git action does to your stones.
- docs/ai-agents.md: a short block to paste into an AI agent's memory (global or per project), so it makes stones on its own.
cargo fmt --check
cargo clippy --all-targets -- -D warnings
cargo test
bench/bench.sh # needs a release build; see the script headerGit Stone has no runtime dependencies. All Git work is done by running the git binary with argument lists, and
only machine-readable (-z or fixed-format) output is parsed.
Licensed under either of Apache License 2.0 or MIT license, at your option.


