Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
97f4daa
Remove the built-in MCP server from Grove core
nicksong-z Jul 31, 2026
6c090de
Add the machine-readable CLI contract (internal/machine)
nicksong-z Jul 31, 2026
aa07467
Add global --format json machine mode and wire read commands
nicksong-z Jul 31, 2026
ad597d8
Return structured per-repo results from mutating operations
nicksong-z Jul 31, 2026
24e82a5
Add gw context for one-call agent discovery
nicksong-z Jul 31, 2026
5740c0d
Restore cross-agent coordination as gw announce / gw announcements
nicksong-z Jul 31, 2026
c4446c3
Add gw plan / gw apply for reviewable mutations
nicksong-z Jul 31, 2026
1b265eb
Simplify the agent-CLI code without changing behavior
nicksong-z Jul 31, 2026
d761322
Warn about commits that exist nowhere else in delete plans
nicksong-z Jul 31, 2026
7b48232
Stop resolving the same base branch twice in gw context
nicksong-z Jul 31, 2026
3456ac8
Parse git URLs in one place, and recognize ssh:// as a URL
nicksong-z Jul 31, 2026
b36ea5a
Remove dead models.ToJSON and finish the legacy-JSON dedup
nicksong-z Jul 31, 2026
a149a0f
Regenerate OpenWiki for the CLI-only agent interface
nicksong-z Jul 31, 2026
b5e9468
Add e2e coverage for the machine contract, and sandbox the suite prop…
nicksong-z Jul 31, 2026
df16388
Remove code that coverage proved unreachable, and cover the human ren…
nicksong-z Jul 31, 2026
9b24792
Share workspace selection and repo-list parsing across commands
nicksong-z Jul 31, 2026
4a736ef
Decompose createCmd.Run into named steps
nicksong-z Jul 31, 2026
d1fa40f
Record the audit fixes in the changelog
nicksong-z Jul 31, 2026
09f67fc
Add a cleanup path for what the MCP server left behind
nicksong-z Jul 31, 2026
d30ced8
Make plan approval binding on the commands it displayed
nicksong-z Jul 31, 2026
9c8c949
Add a Prompter seam so interactive flows can be tested
nicksong-z Jul 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,17 @@ jobs:
env:
GIT_CEILING_DIRECTORIES: /

# The suite sandboxes itself (own HOME, GIT_CONFIG_GLOBAL, TMPDIR), so it runs
# directly on both platforms. macOS matters on its own: path resolution there
# goes through the /var -> /private/var symlink, and Apple Git differs from
# Debian's.
e2e:
needs: check
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
steps:
- uses: actions/checkout@v7
with:
Expand All @@ -41,3 +49,18 @@ jobs:
run: bash e2e/run.sh
env:
GW_BIN: ${{ github.workspace }}/gw

# Same suite, fully hermetic: nothing from a host is reachable, git is pinned,
# and it runs as a non-root user so permission bugs are not masked.
# --network=none proves the suite needs no network.
e2e-container:
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7

- name: Build e2e image
run: docker build -f e2e/Dockerfile -t grove-e2e .

- name: Run e2e tests in an offline container
run: docker run --rm --network=none grove-e2e
26 changes: 25 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,29 @@ When working in this repository, read the OpenWiki quickstart first, then follow

Git Worktree Workspace Orchestrator — CLI tool invoked as `gw`. Manages multi-repo worktree-based workspaces so developers can spin up isolated branches across several repos at once.

## Agent interface

The `gw` CLI is Grove's only agent interface — there is no MCP server. Add
`--format json` to any command for a versioned response envelope with stable
error codes; see [docs/agent-cli.md](docs/agent-cli.md).

```bash
gw context --format json # where am I, repo git state, announcements, next actions
gw status --format json
gw create feat-x -r repo1,repo2 -b feat/x --format json
```

When several agents work in parallel workspaces on the same repos, coordinate
through announcements:

```bash
gw announce -c breaking_change -m "auth tokens are now opaque strings"
gw announcements --format json
```

Notes from other workspaces about your repos also appear in `gw context` under
`result.announcements`.

## Development

- Go 1.25+
Expand Down Expand Up @@ -56,7 +79,8 @@ Tool-specific integrations (Codex memory sync, Zellij, archive, dashboard) live
- **internal/gitops/** — Thin wrappers around `git` subprocess calls. Includes `ReadGroveConfig()`.
- **internal/lifecycle/** — Runs global lifecycle hooks (`post_create`, `pre_delete`, `on_close`) defined in `[hooks]`. Hooks may be bare command strings or tables with metadata (`stream`, `timeout`, `on_failure`); the global `--no-hooks`/`-n` flag skips them all. Plugins register here.
- **internal/logging/** — Structured logging.
- **internal/mcp/** — MCP JSON-RPC server exposing workspace state to Codex.
- **internal/machine/** — Machine-readable CLI contract: response envelope, stable error codes, exit-code classes.
- **internal/announce/** — Cross-workspace agent coordination. Directory of JSON files under `~/.grove/announcements/`; one file per note, so concurrent agents need no locking.
- **internal/models/** — Data structs with JSON serialization.
- **internal/picker/** — Interactive terminal menus.
- **internal/plugin/** — Plugin install/upgrade/remove from GitHub releases.
Expand Down
66 changes: 66 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,71 @@
# Changelog

## Unreleased

### Breaking

- Removed Grove's built-in MCP server. `gw mcp-serve`, the generated `.mcp.json`
`grove` entry, the announcements SQLite database, and the `announce` /
`get_announcements` MCP tools are gone — the latter two return as the
`gw announce` / `gw announcements` commands below. The `gw` CLI is now the only
first-party agent interface. Run `gw doctor --fix` to strip the stale `grove` entry from
`.mcp.json` files in existing workspaces (other MCP servers are preserved), and
to delete the orphaned `~/.grove/messages.db`. For workspaces Grove no longer
tracks, or trees outside the workspace directory, run
`scripts/cleanup-mcp-migration.sh` — it reports before changing anything and
only touches entries that launch `gw mcp-serve`.

### Features

- `gw plan create` / `gw plan delete` and `gw apply`: preview a mutation, review
every repository, path, and branch it would touch, then execute exactly what
was reviewed. Plans carry a fingerprint of the state they assume (including
each repo's exact uncommitted changes and current commit, and the shell commands
the plan displayed), and `gw apply` refuses with `STATE_CHANGED` if anything
relevant moved — so work created after a plan was reviewed is never destroyed by
it, and a `.grove.toml` setup command edited after review is never executed.
- `gw context`: one read-only call reporting the current workspace, each repo's
live git state, configuration, announcements, and safe next actions.
- Global `--format json` (`-o json`) on every command: a versioned response
envelope with stable error codes, semantic exit codes, and `next_actions`.
stdout carries exactly one JSON document; progress, warnings, and hook output
go to stderr. See [docs/agent-cli.md](docs/agent-cli.md).
- `gw announce` / `gw announcements`: cross-workspace coordination for agents
working in parallel, replacing the MCP server's `announce` /
`get_announcements` tools. Notes are keyed by normalized repo remote, expire
after 30 days, and recent ones surface in `gw context` so an agent receives
them while orienting. Backed by a lock-free directory of JSON files under
`~/.grove/announcements/` — no SQLite.

### Fixes

- `gw create -r ssh://git@host/org/repo.git` now clones the URL. `IsGitURL` did
not recognize `ssh://`, so the URL was treated as a repo name and failed with
"repo not found". `git://`, `git+ssh://`, and `ssh://` with a port are
recognized too.
- `gw plan delete` now warns about commits that were never pushed. It compared
against `origin/<branch>` and ignored the error when that ref did not exist, so
the only truly unrecoverable case — commits that exist nowhere else — produced
no warning at all. An unreadable worktree now warns instead of appearing clean.
- "No workspaces exist" reports one error code across commands. `gw rename`
returned `INTERNAL` (exit 1) where `gw add-repo` and `gw remove-repo` returned
`NO_WORKSPACES` (exit 3).
- `--repos "api,"` no longer produces a repo named "" and the error
`repo not found`; blank entries are dropped.
- `gw status --format json` reports `base_branch`, so `ahead`/`behind` say what
they are relative to.

### Maintenance

- Dropped the `modernc.org/sqlite` dependency tree; the release binary shrank
from 13,030,674 to 9,102,482 bytes (-30%) on darwin/arm64 with `-s -w`.
- `gw context` makes 14 git invocations for a two-repo workspace instead of 20;
the base branch was resolved twice per repo.
- e2e suite: added machine-contract coverage (221 assertions), sandboxed the
suite properly (it pinned `HOME` but not `GIT_CONFIG_GLOBAL`, so a host with
`XDG_CONFIG_HOME` set could have had its real git config modified), and added a
container mode plus a macOS CI job.

## v1.1.11

### Features
Expand Down
26 changes: 25 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,29 @@ When working in this repository, read the OpenWiki quickstart first, then follow

Git Worktree Workspace Orchestrator — CLI tool invoked as `gw`. Manages multi-repo worktree-based workspaces so developers can spin up isolated branches across several repos at once.

## Agent interface

The `gw` CLI is Grove's only agent interface — there is no MCP server. Add
`--format json` to any command for a versioned response envelope with stable
error codes; see [docs/agent-cli.md](docs/agent-cli.md).

```bash
gw context --format json # where am I, repo git state, announcements, next actions
gw status --format json
gw create feat-x -r repo1,repo2 -b feat/x --format json
```

When several agents work in parallel workspaces on the same repos, coordinate
through announcements:

```bash
gw announce -c breaking_change -m "auth tokens are now opaque strings"
gw announcements --format json
```

Notes from other workspaces about your repos also appear in `gw context` under
`result.announcements`.

## Development

- Go 1.25+
Expand Down Expand Up @@ -56,7 +79,8 @@ Tool-specific integrations (Claude Code memory sync, Zellij, archive, dashboard)
- **internal/gitops/** — Thin wrappers around `git` subprocess calls. Includes `ReadGroveConfig()`.
- **internal/lifecycle/** — Runs global lifecycle hooks (`post_create`, `pre_delete`, `on_close`) defined in `[hooks]`. Hooks may be bare command strings or tables with metadata (`stream`, `timeout`, `on_failure`); the global `--no-hooks`/`-n` flag skips them all. Plugins register here.
- **internal/logging/** — Structured logging.
- **internal/mcp/** — MCP JSON-RPC server exposing workspace state to Claude Code.
- **internal/machine/** — Machine-readable CLI contract: response envelope, stable error codes, exit-code classes.
- **internal/announce/** — Cross-workspace agent coordination. Directory of JSON files under `~/.grove/announcements/`; one file per note, so concurrent agents need no locking.
- **internal/models/** — Data structs with JSON serialization.
- **internal/picker/** — Interactive terminal menus.
- **internal/plugin/** — Plugin install/upgrade/remove from GitHub releases.
Expand Down
8 changes: 7 additions & 1 deletion Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,16 @@ staticcheck:
build:
go build -ldflags "-X github.com/nicksenap/grove/cmd.Version=$(git describe --tags --always)" -o gw ./cmd/gw

# Run e2e tests
# Run e2e tests (sandboxed: own HOME, git config, and TMPDIR)
e2e: build
bash e2e/run.sh

# Run the same e2e suite inside a container: fully hermetic, Linux, non-root.
# Use --network=none to prove the suite needs no network.
e2e-docker *args:
docker build -f e2e/Dockerfile -t grove-e2e .
docker run --rm {{ args }} grove-e2e

# Set up dev environment (git hooks)
dev:
git config core.hooksPath .githooks
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,14 @@ Full documentation lives in the [OpenWiki](openwiki/quickstart.md) — start wit
- [Architecture](openwiki/architecture.md) — layered design, data model, concurrency, and key decisions
- [Workflows](openwiki/workflows.md) — how each command maps to code (create, sync, run, delete, presets…)
- [Operations](openwiki/operations.md) — configuration, hooks, state, troubleshooting, and release process
- [Integrations](openwiki/integrations.md) — plugins, the MCP server, and workspace source provenance
- [Integrations](openwiki/integrations.md) — plugins, agent usage, and workspace source provenance

### Focused topic guides

- [Hooks](docs/hooks.md) — global hooks (terminal integration) & per-repo hooks (`.grove.toml`, `gw run`)
- [Plugins](docs/plugins.md) — extend gw with external commands
- [AI coding tools](docs/ai-tools.md) — Claude Code workflows, MCP server
- [AI coding tools](docs/ai-tools.md) — Claude Code workflows, agent usage
- [Agent CLI contract](docs/agent-cli.md) — machine-readable output, error codes, exit codes

## Requirements

Expand Down
38 changes: 12 additions & 26 deletions cmd/addrepo.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ package cmd

import (
"os"
"strings"

"github.com/nicksenap/grove/internal/config"
"github.com/nicksenap/grove/internal/console"
"github.com/nicksenap/grove/internal/discover"
"github.com/nicksenap/grove/internal/gitops"
"github.com/nicksenap/grove/internal/picker"
"github.com/nicksenap/grove/internal/machine"
"github.com/nicksenap/grove/internal/state"
"github.com/nicksenap/grove/internal/workspace"
"github.com/spf13/cobra"
Expand All @@ -34,22 +33,7 @@ var addRepoCmd = &cobra.Command{
}

if wsName == "" {
workspaces, err := state.Load()
if err != nil {
exitError(err.Error())
}
if len(workspaces) == 0 {
exitError("No workspaces")
}
choices := make([]string, len(workspaces))
for i, ws := range workspaces {
choices[i] = ws.Name
}
selected, err := picker.PickOne("Select workspace:", choices)
if err != nil {
exitOnPickerErr(err)
}
wsName = selected
wsName = pickWorkspaceName("Select workspace:")
}
}

Expand All @@ -59,10 +43,7 @@ var addRepoCmd = &cobra.Command{

var repoNames []string
if addRepoRepos != "" {
repoNames = strings.Split(addRepoRepos, ",")
for i := range repoNames {
repoNames[i] = strings.TrimSpace(repoNames[i])
}
repoNames = parseRepoList(addRepoRepos)
// Clone any remote URLs into the first repo_dir
for i, name := range repoNames {
if gitops.IsGitURL(name) {
Expand Down Expand Up @@ -102,18 +83,23 @@ var addRepoCmd = &cobra.Command{
}
}
if len(choices) == 0 {
exitError("All discovered repos are already in the workspace")
fail(machine.Errorf(machine.CodeUsage,
"all discovered repos are already in the workspace — nothing to add").
WithActions(machine.NextAction("Discover more repo directories", "gw add-dir <path>")))
}
selected, err := picker.PickMany("Select repos to add:", choices)
selected, err := prompter.PickMany("Select repos to add:", choices)
if err != nil {
exitOnPickerErr(err)
}
repoNames = selected
}

if err := workspace.NewService().AddRepos(wsName, repoNames, repoMap); err != nil {
exitError(err.Error())
result, err := workspace.NewService().AddRepos(wsName, repoNames, repoMap)
if err != nil {
fail(err)
}
machine.Emit(result,
machine.NextAction("Inspect repo state", "gw status "+wsName+" --format json"))
},
}

Expand Down
Loading