Skip to content
Conduit

mcp server

repokeeper

Repository automation and policy tooling.

skaphos/repokeepercommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

This project does not publish an install method we can turn into a config. Follow the README instead.

README

synced Oct 21, 2026

RepoKeeper

A cross-platform multi-repo hygiene tool for developers who work across multiple machines and directory layouts.

RepoKeeper inventories your repositories, reports drift and broken tracking, and performs safe sync actions (remote refresh by default, optional local update when explicitly enabled) — without hidden working-tree mutation or submodule recursion.

Features

  • Discover repositories across configured root directories (Git by default, optional experimental Hg)
  • Report per-repo health: dirty/clean, branch, tracking status, ahead/behind, stale upstreams
  • Sync safely with fetch/prune-first behavior; optional --update-local uses pull --rebase under explicit conditions
  • Registry is stored in .repokeeper.yaml with staleness detection
  • Repo-local metadata can be read from .repokeeper-repo.yaml or repokeeper.yaml and surfaced in JSON, describe output, and MCP repository context
  • Remote-ref hygiene reports stale remote-tracking refs in status, sync plans, and JSON/MCP output without pruning them during inspection
  • CLI-first with tabular (table/wide) and JSON output formats
  • Cross-platform — macOS, Windows, Linux (incl. WSL)

Multi-VCS (Experimental)

RepoKeeper is Git-first. The Mercurial (hg) adapter is available as an experimental backend.

  • Default backend remains git
  • Opt in per command with --vcs git,hg
  • Mixed roots are auto-detected per repo path when multiple backends are selected

Current experimental limits:

  • hg: discovery/status and safe pull-based fetch are supported
  • hg: reconcile --update-local (rebase/push/stash flows) is intentionally unsupported and is skipped with a reason
  • Repair and remote mismatch reconciliation flows remain Git-oriented

Install

See INSTALL.md for full install and upgrade instructions.

Homebrew (cask)

brew tap skaphos/tools
brew install --cask skaphos/tools/repokeeper

Linux packages

# Debian/Ubuntu
sudo dpkg -i repokeeper_<version>_amd64.deb

# Fedora/RHEL
sudo rpm -i repokeeper-<version>-1.x86_64.rpm

Download from the Releases page. There is no hosted apt or yum repository — a .deb on a release page is not an implied apt repo. Upgrading means downloading the next release's package.

From release binaries

Download the latest release from the Releases page.

From source

go install github.com/skaphos/repokeeper/v2@latest

The /v2 suffix is required from 2.0.0 onward — Go encodes the major version in the module path. Staying on v1 needs no change: the unsuffixed path keeps resolving the newest v1 release, so go install github.com/skaphos/repokeeper@latest continues to install the latest v1.

Container (MCP server)

docker pull ghcr.io/skaphos/repokeeper:latest

See MCP Server for the client configuration and its workspace contract.

Upgrading

RepoKeeper ships no update subcommand. Upgrade through the channel you installed from — the table below is the complete list. The reasoning is recorded in ADR-0016: a self-updater that cannot be skipped must verify in-process, and the dependency that makes that possible more than doubles the binary for every user on every channel to serve one install path.

Installed viaUpgrade with
Homebrew caskbrew upgrade --cask skaphos/tools/repokeeper
.debDownload the next release's .deb, then sudo dpkg -i repokeeper_<version>_amd64.deb
.rpmDownload the next release's .rpm, then sudo rpm -U repokeeper-<version>-1.x86_64.rpm
go installgo install github.com/skaphos/repokeeper/v2@latest — note the /v2; the unsuffixed path stays on v1
Release archiveDownload the next archive and replace the binary on your PATH
Container imagedocker pull ghcr.io/skaphos/repokeeper:latest

Not sure which you have? repokeeper version reports whether the binary was produced by the release pipeline or installed from source, which narrows it.

Quick Start

# Bootstrap config and run initial scan for this directory
repokeeper init

# Scan your roots for git repos
repokeeper scan

# Scan/get across mixed git + hg roots
repokeeper scan --vcs git,hg
repokeeper get --vcs git,hg

# Check the health of all repos
repokeeper get

# Preview repo-local metadata for one tracked repo
repokeeper index github.com/org/repo

# Write repo-local metadata after preview
repokeeper index github.com/org/repo --write

# Fetch/prune all repos safely
repokeeper reconcile

Expected User Flow

  1. From the directory you want to manage, run repokeeper init.
  2. init creates .repokeeper.yaml, sets that directory as the default root, and performs an initial scan.
  3. Run repokeeper get to review repo health and identify issues (dirty worktrees, gone upstreams, missing repos).
  4. Run repokeeper reconcile to safely fetch/prune across registered repos.
  5. Re-run repokeeper scan whenever clones are added, moved, or removed so the embedded registry stays current.
  6. If needed, widen scope for a specific run with repokeeper scan --roots <dir1,dir2,...>.

Scans preserve each known checkout's identity and local labels, including when another checkout of the same repository is temporarily unavailable. Missing checkouts remain registered. A new path gets a distinct checkout_id; a matching directory name and an absent old path do not establish a move. After an unrecorded move, scan retains the old entry as missing and registers the new path separately.

Scan treats each discovered repository root as a boundary and does not descend into it. Nested repositories or linked worktrees stored inside an already discovered working tree are therefore not registered separately and do not require extra exclude patterns.

Commands

Detailed command breakdown moved to docs:

Quick highlights:

  • repokeeper get and repokeeper reconcile are direct command forms (... repos aliases still supported).
  • repokeeper edit <selector> opens a single repo entry YAML in your editor ($VISUAL/$EDITOR), validates, then saves.
  • describe, label, edit, delete, move, and index accept an absolute path, bare checkout_id, repo_id, or qualified repo_id@checkout_id. Relative paths work from the current directory or config root; symlink paths are resolved when available. Ambiguous IDs list matching qualified IDs and absolute paths.
  • repokeeper label <selector> manages machine-local labels via --set key=value and --remove key.
  • repokeeper index <selector> interactively proposes repo-local metadata and writes it only when --write is passed.
  • repokeeper index repos --local-selector ... --promote-local-labels --write explicitly bulk-promotes machine-local labels into repo-local metadata for selected repos.
  • Running repokeeper with no subcommand prints help. There is no interactive dashboard; see ADR-0017. Use label for repo labels and index for repo-local metadata.
  • repokeeper install registers repokeeper mcp with your agent runtime (Claude Code, Codex, OpenCode, or Grok); repokeeper install list shows registration state; repokeeper uninstall removes the entry.
  • MCP plan_sync matches CLI reconcile --dry-run for equivalent health filters and update options, including skip/failure reasons and repositories after a failed entry. Sync does not accept label filters; unsupported MCP label_selector requests are rejected.
  • Adapter JSON uses a versioned object with named payloads; see the adapter contract for fields and migration notes. MCP config and registry resources use snake_case fields and omit unknown observation timestamps. describe -o json reads live metadata without updating the on-disk cache. Batch commands may return valid JSON alongside a nonzero exit when individual repositories fail.
  • get reports repository errors on stderr, includes their count in the completion summary, and adds ERROR_CLASS to the default table when errors are present. Error diagnostics remain visible with --quiet; JSON stays on stdout.
  • get supports shared label filtering with -l/--selector and machine-local label filtering with --local-selector (key and key=value, comma-separated AND).
  • add supports metadata on create with --label and --annotation (repeatable key=value).

MCP Server (Agent Integration)

RepoKeeper includes a built-in MCP server for agent runtimes that support the Model Context Protocol (Claude Code, Codex, OpenCode, Grok, Cursor, Windsurf, etc.). MCP is the preferred integration path for inspection and planning workflows — it provides typed tool schemas, structured JSON responses, and automatic tool discovery.

The fastest setup is repokeeper install, which auto-detects Claude Code, Codex, OpenCode, and Grok and writes the server entry into the runtime's config:

repokeeper install            # register with every detected runtime
repokeeper install list       # inspect registration state
repokeeper uninstall          # remove entries (prompts unless --yes)

For runtimes without a flat-file MCP config RepoKeeper can adapter (Cursor, Windsurf), repokeeper install --manual prints the snippet you paste into their settings UI. See docs/mcp-setup.md for per-runtime paths, the full install/uninstall flag reference, and the MCP tool catalog.

The MCP server is primarily intended for inspection and planning workflows with browsable resources, but the current tool surface also includes some explicit state-changing operations. Those mutation-capable tools follow the same safety gates and opt-in behavior as their CLI counterparts, so agents and users should treat them as execution surfaces when enabled. The server reloads workspace configuration and registry state from disk for every request, keeping long-running sessions current without silently reverting manual configuration edits.

RepoKeeper is published to the MCP registry as io.skaphos/repokeeper.

Running the MCP server from a container

{
  "mcpServers": {
    "repokeeper": {
      "command": "docker",
      "args": [
        "run", "-i", "--rm",
        "-v", "/home/me/work/skaphos:/home/me/work/skaphos:ro",
        "ghcr.io/skaphos/repokeeper:latest",
        "mcp", "--config", "/home/me/work/skaphos/.repokeeper.yaml"
      ]
    }
  }
}

Four things about this configuration are deliberate, and each is a constraint rather than a preference:

  • The workspace is mounted at its identical host path. .repokeeper.yaml records absolute paths, so mounting anywhere else makes every registry entry miss and the inventory reports every repository as missing. There is no path remapping — the registry means the same thing inside and outside the container.
  • The mount is read-only by default. All nine inspection tools work normally. The five mutating tools (scan_workspace, execute_sync, set_labels, add_repository, remove_repository) refuse and name the read-only mount as the reason. They stay advertised rather than disappearing, so a refusal is explainable rather than a silently missing capability. For read-write use, drop the :ro suffix, add --user "$(id -u):$(id -g)" so files land with your ownership, and supply git credentials.
  • One configured entry serves exactly one workspace root. The CLI finds its registry by walking upward from the current directory, which is what lets several purpose-specific roots self-select by where you are. A container's working directory and mounts are fixed before any tool call, so that cannot be reproduced. If you keep several roots, add one server entry per root — and for a position-sensitive multi-root workflow, the native binary remains the better path.
  • The container supports Git only. Mercurial is not installed in the image.

Repo-local metadata

RepoKeeper can read optional repo-root metadata from either .repokeeper-repo.yaml or repokeeper.yaml.

  • .repokeeper.yaml remains the machine-local workspace config and registry.

  • .repokeeper-repo.yaml is the source-controlled repo metadata surface.

  • Reads are automatic and read-only in scan, get, describe, and the MCP repository resources.

  • label remains machine-local only.

  • index --promote-local-labels --write explicitly promotes machine-local labels into shared repo metadata.

  • index repos --selector ... / --local-selector ... previews or writes that promotion across multiple selected repos.

  • Inspection refreshes repo-metadata snapshots in memory when on-disk metadata changes. Plain get / status reads do not persist those refreshes; explicit scan and reconciliation operations save the registry.

  • --yes skips the final write confirmation, but does not change the requirement to pass --write.

  • Existing repo_metadata.labels win on key conflicts; promoted local labels only fill missing keys.

RepoKeeper keeps two identity layers:

  • repo_id: stable upstream identity, used as the cross-machine join key.
  • checkout_id: machine-local checkout identity, used to distinguish multiple local clones of the same repo_id.

Example:

apiVersion: repokeeper/v1
kind: RepoMetadata
repo_id: ops-runbooks
name: Ops Runbooks
labels:
  role: runbooks
  domain: ops
entrypoints:
  readme: README.md
paths:
  authoritative:
    - runbooks/
    - templates/
  low_value:
    - generated/
provides:
  - runbook-templates
related_repos:
  - repo_id: internal-docs
    relationship: references

Global flags

  • --verbose / -v — increase verbosity (repeatable: -vv for debug)
  • --quiet / -q — suppress non-essential output
  • --config <path> — override config file location
  • --no-color — disable colored output (also respects NO_COLOR env var)
  • --yes — accept mutating actions without interactive confirmation

Configuration

By default, repokeeper init writes .repokeeper.yaml in your current directory.

Runtime commands (scan, get, reconcile) resolve config in this order:

  1. --config <path>
  2. REPOKEEPER_CONFIG environment variable
  3. Nearest .repokeeper.yaml found by walking upward from the current directory
  4. Global fallback in platform config dir:
  • Linux: $XDG_CONFIG_HOME/repokeeper/config.yaml (default ~/.config/repokeeper/config.yaml)
  • macOS: ~/Library/Application Support/repokeeper/config.yaml
  • Windows: %APPDATA%\\repokeeper\\config.yaml

Flag precedence (highest to lowest):

  1. Explicit command flags (--config, --only, --field-selector, --selector, etc.)
  2. Environment variables where supported (REPOKEEPER_CONFIG, NO_COLOR)
  3. Values loaded from the resolved config file
  4. Built-in command defaults

Selector precedence:

  1. --field-selector when set
  2. --only when --field-selector is not set
  3. Providing both in one command is rejected
  4. -l/--selector is applied as an additional shared-label filter on the resulting repo set
  5. --local-selector is applied as an additional machine-local label filter on the resulting repo set

Example config:

apiVersion: "skaphos.io/repokeeper/v1beta1"
kind: "RepoKeeperConfig"
exclude:
  - "**/node_modules/**"
  - "**/.terraform/**"
  - "**/vendor/**"
registry:
  updated_at: "2026-02-14T10:00:00Z"
  repos: []
defaults:
  concurrency: 8
  timeout_seconds: 60

The default scan/display root is inferred from the directory containing the active config file.

Safety

RepoKeeper is designed to be safe to run on repos with dirty working trees:

  • By default, sync does not run checkout, reset, merge, or other hidden branch-navigation behavior
  • Never updates or recurses into submodules
  • Fetch uses --no-recurse-submodules and -c fetch.recurseSubmodules=false as belt-and-suspenders
  • All mutating commands support --dry-run
  • scan, get, describe, add, and import do not create or rewrite repo-local metadata files
  • label and edit do not write repo-local metadata files
  • index --write is the explicit repo-local metadata write path

Optional local checkout update:

  • repokeeper reconcile --update-local adds pull --rebase after fetch, but only when all of these are true:
  • working tree is clean (or --rebase-dirty is set)
  • branch is not detached
  • branch tracks an upstream
  • branch is not ahead
  • branch is not diverged unless --force is set
  • branch is not matched by --protected-branches (default: none) unless --allow-protected-rebase is set
  • --rebase-dirty stashes changes, rebases, then pops the stash
  • --push-local pushes local commits when a branch is ahead (instead of skipping with "local commits to push")
  • --continue-on-error keeps processing all repos after per-repo failures (default true)
  • In dry-run/preflight mode, these checks are evaluated up front so the plan calls out which repos are candidates for fetch + rebase versus skip local update (...).

Branch switching and prune execution are separate workflow areas rather than hidden sync side effects.

Documentation

Development

Spec Kit includes repo-local Codex skills in .agents/skills/speckit-*/SKILL.md. Open Codex in this repository and invoke $speckit-specify, $speckit-plan, $speckit-tasks, or $speckit-implement to use the specification workflow. The skills share the constitution and templates in .specify/ and feature artifacts in specs/. Claude remains the default Spec Kit integration; Codex skills can be invoked directly without changing that default. If the skills do not appear in an existing Codex session, restart the session.

With the Specify CLI installed, run specify integration status to check the setup and specify integration upgrade codex to refresh the generated skills.

The fast unit tier remains go test ./.... Process-level coverage is integration-tagged and excluded from the ordinary package graph:

go -C tools tool task test-integration
go -C tools tool task test-integration-race
go -C tools tool task test-default-excludes-e2e

See test/e2e/README.md for recipe authoring, focused CLI/MCP runs, isolation guarantees, compatibility matrix commands, and failure diagnosis.

The Ginkgo unit command (also used by task test) explicitly excludes E2E packages because Ginkgo discovers test files without evaluating their integration build tags. Use test-integration above to run those packages.

# List all task targets without installing task globally
go -C tools tool task --list

# Run unit tests
go run github.com/onsi/ginkgo/v2/ginkgo@v2.33.1 --skip-package=e2e ./...

# Run with coverage
go test -coverprofile=coverage.out ./...

# Run with coverage and enforce per-package thresholds
go -C tools tool task test-cover-check

# Run coverage and print lowest-covered packages/functions
go -C tools tool task coverage-report

# Run performance benchmarks and append historical record
go -C tools tool task perf-bench

# Lint
go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.14.0 run ./...

# Build locally
go build -o repokeeper .

# Build locally (task runner)
go -C tools tool task build

# CI-style full platform build (task runner)
go -C tools tool task build-ci

# Run standard CI pipeline locally (lint/test/staticcheck/vuln/build-ci)
go -C tools tool task ci

# Format imports + code
go -C tools tool task fmt

Security

Report suspected vulnerabilities privately using the security policy. Please do not use public issues for security reports.

License

MIT

Third-Party Notices

RepoKeeper includes third-party open source components.

When dependencies change, regenerate the notice artifacts before opening a PR:

go -C tools tool task notices

Skaphos is a project of Rillan AI LLC, a Missouri limited liability company. © 2026 Rillan AI LLC. Released under the MIT License.

↑↓ navigate openesc close