Skip to content

chrisbanes/grove

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

39 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Grove

CI Release Go License macOS

Instant workspaces with warm build caches, powered by copy-on-write clones.

The Problem

When you need multiple copies of a repository -- for parallel features, code review, or experimentation -- the standard approaches (git worktree, git clone) create fresh working trees. They share git objects, but lose all gitignored local state: build caches, compiled outputs, dependency metadata, node_modules/, .gradle/, build/ directories.

Every new working copy starts cold. For large projects, rebuilding takes minutes.

How Grove Works

Grove maintains a golden copy of your repository -- a checkout with warm build state (compiled outputs, dependency caches, everything). When you need a new workspace, Grove creates a copy-on-write filesystem clone of the entire golden copy, including all gitignored files.

Regardless of repo size, CoW clones complete in under a second and share disk blocks with the original. Blocks duplicate only when one side writes to them. Each workspace gets its own git branch, working tree, and build state.

Golden Copy (warm build state)
    |
    +-- grove create --> Workspace 1 (CoW clone, own branch)
    +-- grove create --> Workspace 2 (CoW clone, own branch)
    +-- grove create --> Workspace 3 (CoW clone, own branch)

Comparison

Grove git worktree git clone --shared Full git clone
Clone speed ~1s (any size) ~1s Seconds Minutes
Build caches preserved Yes No No No
Gitignored files cloned Yes No No No
Disk usage Shared (CoW) Shared (.git) Shared (objects) Full copy
Isolation Full Shared .git Partial Full
Branch independence Yes Yes (limited) Yes Yes

Grove uses git under the hood. It replaces only the step where you create a new working copy.

Quick Start

Install

# Homebrew (macOS)
brew install chrisbanes/tap/grove

# Go install
go install github.com/chrisbanes/grove/cmd/grove@latest

# Or download a binary from the latest release:
# https://github.com/chrisbanes/grove/releases

Basic Workflow

Start config-less: the first grove create in a git repo auto-initializes .grove/config.json with defaults. Run grove config afterward only if you want to customize settings.

# 1. Create a workspace (first run auto-initializes .grove/config.json)
cd ~/dev/myproject
grove create --branch feature/new-login
# Workspace created: feature-new-login-a1b2
# Path: /Users/you/grove-workspaces/myproject/feature-new-login-a1b2
# Branch: feature/new-login

# 2. Optional: customize settings for future workspaces
grove config --warmup-command "./gradlew assemble"

# 3. Work in the workspace
cd ~/grove-workspaces/myproject/feature-new-login-a1b2

# 4. Clean up when done
grove destroy feature-new-login-a1b2

Commands

grove config [path]

Configure a git repository as a grove-managed golden copy. Creates a .grove/ directory with configuration, hooks, and a .grove/.gitignore for workspace markers (and legacy runtime paths). Defaults to the current directory.

grove config --warmup-command "npm run build" --workspace-dir ~/workspaces/myproject
# Running warmup: npm run build
# Grove configured at /Users/you/dev/myproject
# Workspace dir: /Users/you/workspaces/myproject

# Experimental image backend (macOS): creates a base sparsebundle
grove config --backend image --image-size-gb 200
Flag Description
--warmup-command Shell command to warm build caches (runs during config and update)
--workspace-dir Directory for workspaces (default: ~/grove-workspaces/{project})
--backend Workspace backend (cp default, image experimental)
--image-size-gb Base sparsebundle size in GB for --backend image
--force Proceed even if the golden copy has uncommitted changes

grove create

Create a workspace from the golden copy using the configured backend:

  • clone_backend: "cp" (default): APFS directory clone (cp -c -R)
  • clone_backend: "image" (experimental): attach base sparsebundle with per-workspace shadow

Without --branch, the workspace stays on the golden copy's current branch. With --branch, Grove creates and checks out a new git branch in the workspace.

On first run in an unconfigured git repo, grove create auto-initializes .grove/config.json with default settings, then creates the workspace. Use grove config to customize settings later (warmup command, workspace location, backend, and more).

With --json, Grove writes only JSON to stdout and does not print the auto-init note to stderr.

grove create --branch feature/auth
# Workspace created: feature-auth-f7e8
# Path: /Users/you/grove-workspaces/myproject/feature-auth-f7e8
# Branch: feature/auth

grove create
# Workspace created: main-d9c0
# Path: /Users/you/grove-workspaces/myproject/main-d9c0

For machine-readable output:

grove create --branch agent/fix-bug --json
{
  "id": "agent-fix-bug-f7e8",
  "golden_copy": "/Users/you/dev/myproject",
  "golden_commit": "abc1234",
  "created_at": "2026-02-17T10:30:00Z",
  "branch": "agent/fix-bug",
  "path": "/Users/you/grove-workspaces/myproject/agent-fix-bug-f7e8"
}

For long-running clones in large repos, opt in to progress output:

grove create --progress
# [5%] clone
# [42%] clone
# [95%] post-clone hook
# [100%] done
# Workspace created: main-d9c0

When combined with --json, progress is written to stderr and final JSON is written to stdout.

Flag Description
--branch Create and checkout a new git branch in the workspace (default: golden copy's current branch)
--force Proceed even if the golden copy has uncommitted changes
--json Output workspace info as JSON
--progress Show progress output for long-running create operations (written to stderr)

grove list

List active workspaces.

grove list
# ID                     BRANCH              CREATED    PATH
# feature-auth-f7e8      feature/auth        5m ago     /Users/you/grove-workspaces/myproject/feature-auth-f7e8
# feature-new-login-a1b2 feature/new-login   2h ago     /Users/you/grove-workspaces/myproject/feature-new-login-a1b2
Flag Description
--json Output workspace list as JSON

grove destroy <id|path>

Remove a workspace. Takes a workspace ID or absolute path.

  • cp backend workspaces are removed by deleting the directory.
  • image backend workspaces are detached first, then shadow + metadata are removed.
# Destroy a single workspace
grove destroy feature-auth-f7e8
# Destroyed: feature-auth-f7e8

# Push the branch to origin before destroying
grove destroy --push feature-auth-f7e8

# Destroy all workspaces
grove destroy --all
# Destroyed: feature-auth-f7e8
# Destroyed: feature-new-login-a1b2
Flag Description
--push Push the workspace branch to origin before destroying
--all Destroy all workspaces

grove update

Pull the latest changes and re-run the warmup command on the golden copy.

grove update
# Pulling latest...
# Running warmup: ./gradlew assemble
# Refreshing image backend...   # when clone_backend is "image"
# Golden copy updated to abc1234

If clone_backend is image, update performs an incremental base refresh. For safety, refresh is refused while image-backed workspaces are active.

grove status

Show golden copy info and workspace summary.

grove status
# Golden copy: /Users/you/dev/myproject
# Branch:      main
# Commit:      abc1234
# Status:      clean
#
# Workspaces:  2 / 10 (max)
# Directory:   /Users/you/grove-workspaces/myproject

grove version

Print the grove version.

grove version
# grove v0.1.0

Configuration

Grove stores its configuration in .grove/config.json inside the golden copy:

{
  "warmup_command": "./gradlew assemble",
  "workspace_dir": "~/grove-workspaces/{project}",
  "max_workspaces": 10,
  "exclude": ["*.lock", "__pycache__", ".gradle/configuration-cache"],
  "clone_backend": "cp"
}
Field Description Default
warmup_command Shell command to warm build caches. Runs during grove config and grove update. (none)
workspace_dir Where workspaces are created. {project} expands to the golden copy's directory name. ~/grove-workspaces/{project}
max_workspaces Maximum concurrent workspaces. Prevents disk exhaustion. 10
exclude Glob patterns for files/directories to skip when cloning. See Exclude Patterns. []
clone_backend Workspace backend: cp (default) or image (experimental, macOS). cp

Backend Comparison

Characteristic cp (default) image (experimental, macOS)
Create speed Fast APFS clone via cp -c -R; can become metadata-bound in very large repos Very fast create via base image attach + per-workspace shadow
Disk space CoW shared blocks with low operational overhead Higher overhead from base image + shadows; can approach ~2x in worst case
Operational complexity Simple lifecycle, no extra backend state More complex mount/attach/detach state and metadata handling
Update behavior grove update does git pull + optional warmup Adds incremental base image refresh during grove update
Safety constraints Fewer backend-specific guards Refresh/migration guarded while image-backed workspaces are active
Platform support macOS/APFS macOS-only and still experimental
Best for Most repositories and teams prioritizing predictability Very large repos where cp -c -R clone time is the bottleneck

Experimental Image Backend

The image backend is for large repositories where raw cp -c -R clone time is dominated by filesystem metadata work.

Model:

  • runtime root: <workspace_dir>/runtimes/<runtime-id>/
  • base image: <runtime-root>/images/base.sparsebundle
  • per-workspace overlay: <runtime-root>/shadows/<id>.shadow
  • workspace metadata: <runtime-root>/workspaces/<id>.json

Runtime data lives under your configured workspace_dir, not inside the repo. The runtime ID is stored in .grove/.runtime-id (gitignored).

grove create attaches the base image with a workspace-specific shadow, which makes create time mount-time fast.

grove update incrementally refreshes the base via rsync and is blocked while any image-backed workspace is active.

Warmup command examples by ecosystem:

Ecosystem Warmup command
Gradle ./gradlew assemble
npm npm run build
Cargo cargo build
Go go build ./...

Exclude Patterns

Exclude patterns prevent specific files or directories from being copied during workspace creation. Unlike post-clone hooks, excluded paths are never touched at all -- they aren't copied and then deleted, they're skipped entirely. This matters for performance (large caches) and correctness (lock files, PID files, sockets).

{
  "exclude": [
    "*.lock",
    "__pycache__",
    ".gradle/configuration-cache"
  ]
}

Patterns use Go's filepath.Match syntax:

  • Simple patterns (no /) match against the basename at any depth. *.lock matches yarn.lock, packages/foo/yarn.lock, etc. __pycache__ matches any directory with that name.
  • Path patterns (contain /) match against the full relative path from the repo root. .gradle/configuration-cache matches only that exact path.

The .grove directory is never excluded, regardless of patterns.

Exclude patterns vs. post-clone hooks: Use exclude patterns for files that should never be cloned (large caches, lock files). Use hooks for cleanup that requires logic (e.g., patching config files, running commands).

Hooks

Grove runs executable scripts from .grove/hooks/ at specific lifecycle points.

post-clone

Runs inside each new workspace after the CoW clone completes, before branch checkout. Use it to clean up non-relocatable state (lock files, caches with embedded absolute paths).

Gradle example (.grove/hooks/post-clone):

#!/bin/bash
# Clean Gradle lock files and non-relocatable caches
find . -name "*.lock" -path "*/.gradle/*" -delete
rm -rf .gradle/configuration-cache

Python example (.grove/hooks/post-clone):

#!/bin/bash
# Remove bytecode caches (they embed absolute paths)
find . -type d -name "__pycache__" -exec rm -rf {} +
find . -name "*.pyc" -delete

Hooks must be executable (chmod +x .grove/hooks/post-clone). Grove errors if a hook file exists but lacks execute permission. Commit your hooks to the repo so all contributors share them.

Use with AI Agents

Grove targets multi-agent AI workflows where each agent needs an isolated workspace with warm build state. The --json flag on create and list provides machine-readable output for programmatic consumers.

Typical agent workflow:

# Agent creates its own workspace
grove create --branch agent/fix-login --json
# Parse JSON for workspace path

# Agent works in the isolated workspace
cd ~/grove-workspaces/myproject/agent-fix-login-a1b2
# ... make changes, run tests ...

# Push branch and clean up
grove destroy --push agent-fix-login-a1b2

Multiple agents can work in parallel, each in its own workspace. Every workspace starts with the same warm build state from the golden copy.

Beyond AI agents, Grove serves any workflow that needs parallel workspaces: CI pipelines, code review, and experimentation.

Claude Code Skills

Grove includes a Claude Code plugin with skills that integrate Grove into Claude Code workflows. When installed, these skills replace the built-in worktree-based skills with Grove-aware equivalents.

Install

Claude Code:

# Add the Grove marketplace
/plugin marketplace add chrisbanes/grove

# Install the plugin
/plugin install grove@chrisbanes-grove

Other agents (Codex, Cursor, etc.):

npx skills add chrisbanes/grove

This copies the skill files to your agent's skills directory (e.g., ~/.claude/skills/ or ~/.agents/skills/).

Skills

Skill Description
grove:using-grove Creates a warm workspace before feature work. Replaces superpowers:using-git-worktrees.
grove:finishing-grove-workspace Guides completion and cleanup when work is done. Replaces superpowers:finishing-a-development-branch.
grove:grove-config Opinionated first-time setup with build system detection. Also available as /grove-config.
grove:grove-doctor Diagnoses Grove setup issues (APFS, CLI, hooks, disk space).
grove:grove-multi-agent Orchestrates parallel agents across isolated workspaces.

How it works

A SessionStart hook runs at the beginning of each conversation. It detects whether you're in a Grove golden copy or workspace and tells Claude which skills are relevant. In a golden copy, Claude will use grove:using-grove to create workspaces. In a workspace, it knows to use grove:finishing-grove-workspace when done.

The skills integrate with the superpowers workflow -- brainstorming, subagent-driven-development, and executing-plans all invoke grove:using-grove instead of using-git-worktrees when Grove is detected.

Platform Support

Platform Filesystem Status
macOS APFS Supported
Linux Btrfs / XFS (reflink) Planned
Linux ext4 Not supported (no CoW)
Windows NTFS / ReFS Not supported

Grove requires a filesystem with copy-on-write support. All modern Macs (macOS High Sierra / 2017 and later) use APFS. Linux support for Btrfs and XFS reflink is planned -- the Cloner interface and filesystem detection scaffolding are already in place.

Grove errors with a clear message on unsupported filesystems. It never silently falls back to a regular copy.

How It Works

On macOS, Grove uses cp -c -R to create an APFS clone. This filesystem-level operation shares disk blocks between the clone and the original; blocks duplicate only when one side writes to them.

Before cloning, Grove verifies APFS support by querying diskutil info at runtime.

All state lives in .grove/ within the repo -- no daemon, no global config, no database. Each workspace contains a .grove/workspace.json marker file, which grove list discovers by scanning the workspace directory.

Contributing

Contributions are welcome. See CONTRIBUTING.md for development setup, testing, and PR guidelines.

License

Apache 2.0

About

No description, website, or topics provided.

Resources

License

Contributing

Stars

Watchers

Forks

Sponsor this project

 

Packages

 
 
 

Contributors