Skip to content
knewstimekPublic

About

Bounded repository search, durable task evidence, and gated execution for coding agents.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

RepoPlane

RepoPlane is a local Model Context Protocol server for coding agents: bounded repository search, durable task evidence, and gated execution of registered commands. It also explains paths and queries structured data. A partial scan reports what it did not observe instead of presenting a guess as a complete result.

Discovery tools read workspace content. Record tools write local evidence; Runner executes only registered commands after preparation, preflight checks, and any required approval. The server includes both read and gated write or execution tools.

When an agent needs to... Without RepoPlane With RepoPlane
Search a large repository Inspect tool output and infer whether a scan finished Get bounded matches with scope, count relation, and partial-state evidence
Resume work across sessions Reconstruct prior decisions from conversation or files Query durable checkpoints, memos, and verification records
Run a known project check Find and invoke the command separately Inspect a registered plan, then run it through preflight and approval gates

Text search and file inspection support UTF-8, CP949, and EUC-KR for repositories with legacy Korean encodings. Filename and text search use ripgrep (rg).

Releases · Changelog

Install and try it

Download a Windows or Linux amd64 archive and SHA256SUMS.txt from Releases. Verify the checksum and put repoplane on PATH. Starting with v1.1.7, the Windows archive includes rg.exe; keep it next to repoplane.exe. On Linux, install ripgrep as rg to enable filename and text search. Starting with v1.1.7, RepoPlane also starts without rg: those search modes report unsupported with ripgrep_unavailable, while Git history, configured symbol search, records, and Runner remain available. path_explain marks its basename scan partial. Git on PATH enables optional history search. Building from source requires Go 1.26 or newer:

go test ./...
go build -trimpath -o bin/repoplane ./cmd/repoplane

On Windows, build to bin/repoplane.exe. If the installed executable is serving an active MCP session, rename that copy to old_repoplane_<timestamp>.exe before replacing it, and retain the old copy until its process exits. New sessions use the replacement.

Register the default local stdio server with Codex:

codex mcp add repoplane -- repoplane

With no flags, the server uses its process working directory as the workspace and the user cache for private state. To pin both locations in a generic MCP client, use paths specific to your host; the state directory must be outside the workspace:

{
  "mcpServers": {
    "repoplane": {
      "command": "repoplane",
      "args": ["--workspace", "WORKSPACE", "--state-dir", "STATE_DIRECTORY"]
    }
  }
}

For local stdio, if the state directory is inside the workspace (including a resolved link), MCP still connects and lists its tools, but repository operations return workspace_state_overlap. No repository services or private databases are opened in this blocked mode. Call runtime_config with {"action":"status"} to inspect status: "blocked" and its warning, then recover on the same connection with either of these requests, using an absolute host path:

{"action":"select","target":"workspace","values":["ABSOLUTE_PROJECT_DIRECTORY"]}
{"action":"select","target":"state_dir","values":["ABSOLUTE_STATE_DIRECTORY_OUTSIDE_WORKSPACE"]}

Only status and these recovery selections are available while blocked. Invalid selections leave it blocked; a valid selection activates the normal tools without reconnecting. HTTP startup continues to reject overlapping paths. No additional enable flag is needed.

Start a new MCP session. Call catalog_query with {"mode":"status"} to see configured catalog sources and whether registered execution is available. Then call workspace_search:

{"mode":"exact","pattern":"TODO","root":".","item_limit":20}

Search, list, and query replies state their scope, count relation, truncation, warnings, and next cursor. A stopped scan reports a lower bound or unknown count. Do not treat partial as complete.

Codex durable-memory skill

The canonical repo-memory Codex skill is included at skills/repo-memory. Install it by copying that directory to $CODEX_HOME/skills/repo-memory, then start a new Codex session. Treat the repository copy as the source of truth and resync installed copies after it changes. The skill retains verified feature and configuration knowledge before completion and handles write recovery: correct invalid inputs, accept time warnings as saved, read before retrying uncertain writes, and merge conflicts only when content differs. Changes to memo kind/schema or topic identity require a successor; permission denials are never bypassed.

Tools and common tasks

The default typed.v1 interface exposes 14 tools:

Task Tool
Find registered checks, commands, and missing catalog roots catalog_query
Search names, text, Git history, and configured symbol indexes workspace_search
Explain Git, symlink, encoding, newline, and repository-rule facts path_explain
Read bounded ranges and query JSON, JSONL, logs, CSV, or TSV data_query
Search durable verification, checkpoint, memo, and run evidence project_records
Write intentions or import a local verification report checkpoint_write, memo_write, check_report_import
Inspect and change session grants or live configuration runtime_access, runtime_config
Prepare, execute, and inspect a registered capability run_prepare, run_execute, run_inspect
Export durable memory and retained Runner evidence memory_backup

The typed tool schemas expose fixed choices for common search, query, and write fields. A cursor can replace mode on paginated reads. memo_write requires a source value; use user_asserted for a user-provided fact or llm_proposed for an agent proposal.

project_records(query="terms") infers mode=search. Search now defaults to eight brief cards within 8 KiB: ID, title, one-line summary, topic key when present, validity, and time markers. Clients that relied on the old implicit preview must request response_view=discovery explicitly. It ranks current records first, then records matching more search terms; match_mode=all requires every term. Search supports exact topic_key, scope, and configuration filters, updated_after (inclusive), updated_before (exclusive), and validity. Use next_cursor for the next page. The 30-minute cursor snapshot contains at most 1,000 candidates; a larger match set is explicitly partial. response_view=discovery requests the earlier 320-character preview, and response_view=full or payload_fields requests payload data. list keeps its existing 50-item, 64 KiB default. An empty call returns mode required.

Use project_records for background knowledge, including saved code, design decisions, and past verification. Use catalog_query for registered commands/checks, and workspace_search or data_query to inspect present source when a claim depends on its current state. A saved code example can answer why an approach was chosen; it does not by itself prove that the current implementation still matches. Check record evidence, scope, and invalidation conditions before relying on it for that claim. validity=current on a memo means active, not freshly verified code.

Find a topic key with a brief search, then open one record with mode=get and id. If the key is already known, mode=get_topic with an exact topic_key fetches the one current memo directly; add exact scope and configuration when the key names several current memos. Ambiguity returns short candidates rather than selecting one. A record's validity says whether the record is active; temporal_kind and as_of say whether its content was a historical observation or asserted as current guidance, and when. Older memos without this information say unknown. Creating a non-host memo without both time fields succeeds but returns memo_time_unknown in structured warnings and the short text receipt. It is valid to leave the content time unknown. If evidence later establishes it, read the memo by ID and update it with expected_revision, temporal_kind, and as_of. Updates replace the memo payload and evidence refs, so resubmit the existing content, identity, invalidation condition, discovery fields, and evidence refs as well. Never copy automatic created_at or updated_at into as_of without evidence that the content was confirmed then. Typed host facts use their separate confirmed_at field. Reading a superseded memo points to its successor and preserves the old content and evidence. When a detailed memo cites a git:<commit> evidence ref, its read warns if the current commit differs, the worktree is dirty, or Git cannot be observed. This compares the cited basis only; it does not claim that relevant code changed or that the memo is incorrect. Brief searches do not run this check, and source should be inspected before using an old procedure.

checkpoint_write can store a concise change_summary alongside the next action, evidence refs, and background_refs to relevant current memos. project_records with mode=resume and an exact checkpoint id, or a distinctive goal query, returns those fields in one call. resume first requires every query term to match, while general record search ranks partial matches. If no checkpoint matches every term, resume returns short partial-match candidates with a warning; match_mode=any explicitly requests the broader match. If the goal matches several checkpoints, it returns short candidates. change_summary is written by the caller and is never inferred from Git: committed history, uncommitted changes, and workspaces without Git can all be described. If it was not recorded, change_state=unknown says so. Verify present files and checks before relying on a previous agent's summary.

Checkpoint, memo, and report-import writes now default to response_view=receipt, returning the record ID and revision without echoing the payload. Request response_view=full for the full write result; clients that relied on full write echoes must opt in. Typed MCP replies include repoplane/usage.v1 metadata with serialized structured result bytes, text-content bytes, and server duration in milliseconds. These are server measurements, not model-token counts or end-to-end network time. Compact search, resume, and write replies use a short text fallback rather than duplicating the whole structured result. Detailed reads carry the full JSON in text in the default compatible mode for text-only clients. This is not proof that a host includes both representations in model input or bills both as tokens.

Automatic result deduplication

For a host that consumes structuredContent, start RepoPlane with --result-content=deduplicated. Each ordinary MCP tools/call then automatically removes plain text blocks containing the same JSON as the structured result, after SDK output processing. No LLM decision, extra tool call, shell command, or response file is required. The typed surface stays at 14 tools and the toolbox surface at five; both stdio and HTTP use the same middleware.

{
  "mcpServers": {
    "repoplane": {
      "command": "repoplane",
      "args": ["--workspace", "WORKSPACE", "--state-dir", "STATE_DIRECTORY", "--result-content=deduplicated"]
    }
  }
}

For a TOML host configuration, add the flag to the existing server arguments (preserving any other arguments):

[mcp_servers.repoplane]
command = "repoplane"
args = ["--result-content=deduplicated"]

Restart the MCP connection after changing startup arguments. The initialization instructions report the effective result content mode. Only enable this mode after confirming that the host reads structuredContent: a text-only consumer would lose the removed copy. Omit the flag or use --result-content=compatible for the previous wire behavior. Client/protocol version alone is not treated as evidence of structured-result consumption.

Structured payloads retain their original types and exact numbers. Unique text, compact search cards, annotated/extended content, media, warnings, errors, approval handshakes, and metadata remain. An all-duplicate content array becomes []. Typed per-call text_bytes reflects retained text; the server measurements remain byte counts, not model-token accounting. Ambiguous JSON is preserved. The mode is startup-only and does not change authorization or execution gates.

Captured-result filter

render-result remains available for captured responses and client-wrapper development; ordinary MCP calls in deduplicated mode do not need it. To filter a captured whole CallToolResult:

repoplane render-result < result.json

From PowerShell, use native input redirection without re-encoding the captured JSON:

cmd /c "repoplane render-result < result.json"

The command reads one UTF-8 JSON CallToolResult object (not a JSON-RPC envelope) from stdin, with a fixed 16 MiB input limit. It does not start the server, access a workspace, or open a database. The output retains structuredContent and removes only plain text blocks containing equivalent JSON; object key order and whitespace may differ. Unique text, media/resource blocks, annotated or extended text blocks, errors, approval fields, and all envelope metadata remain. Numbers are compared without floating-point conversion; ambiguous JSON with duplicate keys is kept. An all-duplicate content array becomes []. Invalid or oversized input fails without partial output. This filter changes wrapper display output, not MCP wire replies, and is not automatically installed into an external host. Route captured results through the filter; do not pipe the live MCP protocol stream through it.

Select payload data when the full body is unnecessary, independently of result deduplication. For example:

{"mode":"get_topic","topic_key":"build-policy","payload_fields":["content","invalidation_condition"]}

payload_fields selects exact top-level fields: checks/counts for a verification record, content/invalidation_condition for a memo, or title/summary when recorded. It does not select sections inside prose or nested JSON paths. Record metadata, evidence refs, and warnings remain available. These are separate savings from eliminating an identical text echo. Records do not infer semantic similarity between differently worded memos. A caller can assign a stable topic_key to a memo and explicitly update or supersede that identity. update is a partial edit: supply id, expected_revision, and only the fields being changed. Omitted memo and checkpoint fields, evidence refs, discovery metadata, and temporal evidence are preserved. Explicit empty strings or arrays clear optional mutable fields; required content and topic identity cannot be cleared. An explicitly supplied host object replaces that whole object and must satisfy its complete validation contract. For example, a content-only edit needs no repeat of scope, topic key, memo kind, title, or evidence:

{"mode":"update","id":"MEMO_ID","expected_revision":1,"content":"Revised guidance."}

The same partial-edit rules apply to checkpoint_write; stale revisions are rejected rather than overwriting newer content. Source provenance, memo kind, and supersession links are preserved. A topic memo's scope, configuration, and topic_key form an immutable identity. To replace a memo, create its successor with supersedes and the old expected_revision; that atomically supersedes the old record and links the new one.

Do not switch to supersession to work around an update error: inspect its field-specific message and read the current record when necessary. mode=supersede by itself only retires the record; it does not write a successor. Use successor creation only for an intentional identity/kind/schema change or replacement. No new tools, flags, or database migration are needed for partial updates.

New decision or procedure memos can carry title, summary, temporal_kind, and as_of for reliable discovery. Host facts retain their separate schema. Memo titles and summaries allow at most 160 and 240 Unicode characters, respectively, matching the schema's maxLength rules. Memo text fields together have a separate 32 KiB UTF-8 byte budget. A rejected write identifies the exceeded field or budget and reports the observed limit.

A typed host fact uses memo_kind=host_fact and is stored as memo.v2 (not memo.v3). Its host object requires alias, role, os, tier, services, paths, and an RFC3339 confirmed_at; the memo also requires invalidation_condition. services and paths may be null when unknown, but when supplied their entries must be bounded, unique, and non-empty. A host fact cannot use topic_key. Each list allows 64 entries independently, with at most 1024 Unicode characters per entry. For example:

{
  "mode": "create",
  "memo_kind": "host_fact",
  "scope": "operations/hosts",
  "source": "user_asserted",
  "host": {
    "alias": "host-a",
    "role": "worker",
    "os": "linux",
    "tier": "production",
    "services": ["worker"],
    "paths": ["/srv/worker"],
    "confirmed_at": "2026-01-01T00:00:00Z"
  },
  "invalidation_condition": "the host is rebuilt or its role changes",
  "response_view": "receipt"
}

Tool failures return a JSON object in the MCP error text with stable code, safe message, and correlation_id fields. Mutation failures also include mutation_state: not_applied means the write was rejected before commit, while unknown means a storage, deadline, or unexpected failure requires a read-back before retrying. Durable writes distinguish invalid_argument, invalid_transition, revision_conflict, and storage_failure; for storage_failure, internal_error, and deadline_exceeded, server diagnostics log the correlation ID and the failing stage on stderr without exposing database details or host paths to the client.

Catalog entries are YAML, JSON, or Markdown with YAML frontmatter under a configured catalog root. A minimal discovery-only entry is:

id: project.check
revision: 1
summary: Check the repository
use_when: [before a release]
tags: [verification]
cache_policy: disabled

A runnable entry additionally declares its executable, argument template, working directory, inputs, outputs, limits, and trusted_for_run: true; see catalog/dev.verify.yaml for a working example. RepoPlane never accepts an arbitrary executable or shell command from an MCP request. Runner validates a registered ID with run_prepare, executes the returned plan ID once with run_execute, and retains status and bounded evidence for run_inspect. Local execution requires user approval unless the host pre-authorized Runner.

run_prepare returns a concise plan. run_inspect(action=status) returns a compact run summary. detail, stdout, stderr, and artifact return stored references and sizes by default; set response_view=bytes only when a full receipt or bounded output page must enter the MCP response. A state: file reference is relative to the server's local state directory, available from runtime_config(action=status). HTTP clients cannot open that server-local file directly. Captured process output is raw and is not automatically redacted.

Runner launches executable_ref and passes argv_template as arguments from cwd; it does not infer a Python interpreter from a .py file association. For a Python script with cwd: ., register an available Python interpreter, for example executable_ref: python and argv_template: [tools/example.py, "{message}"]. Verify the interpreter on the host first.

On Windows, register PowerShell explicitly as the executable and put the workspace-relative script path in argv_template, for example executable_ref: powershell with argv_template: [-NoProfile, -File, tools/Example.ps1]. On Windows, direct .py and .ps1 executable references are rejected during run_prepare with unsupported_script_type; the MCP error explains which interpreter to register and where to put the script path and arguments. Input and output globs are bounded; a matched-file limit failure includes the responsible pattern, configured maximum, observed lower bound, and a source-only-glob hint. Gitignored and build-output paths are included unless the manifest narrows its patterns; RepoPlane only excludes its internal metadata directories.

Access and configuration

Local stdio reads stay inside the resolved workspace unless an external path receives an exact, read-only session grant. Record writes, report import, Runner, cache reuse, memory export, and external reads require the relevant host setting or user approval. Grants last only for the MCP process and can be inspected or revoked with runtime_access.

runtime_config(action=status) shows live catalog roots, candidate roots, rule files, symbol indexes, workspace, state directory, HTTP transport, and tool surface. Local stdio operations apply directly without an additional server approval prompt or enable flag, so unattended clients can change sources or switch the workspace and state directory without waiting for a person. Input/path validation, atomic service replacement, and active Runner/snapshot checks still apply. Source-list action=replace requires an explicit values array: [] intentionally clears the selected list, while omitting values is rejected without changing the configuration. HTTP clients cannot call this tool. Use startup flags for fixed host defaults:

--workspace PATH       primary workspace
--workspace-origin PATH former canonical absolute path; one-time legacy adoption after a move
--state-dir PATH       private local state outside the workspace
--catalog-root PATH    workspace-relative catalog source; repeatable
--candidate-root PATH  executable candidate directory; repeatable
--rule-file NAME       repository rule filename; repeatable
--symbol-index PATH    configured symbol index; repeatable
--transport MODE       stdio (default) or http
--http-profile PATH    ignored local HTTP profile
--tool-surface SURFACE typed.v1 (default) or toolbox.v1

For a private registered deployment capability, check catalog_query(mode=status) and then catalog_query(mode=search, query="deploy"). If the ID is absent, inspect the configured and candidate roots with runtime_config(action=status). The server cannot discover an external private profile whose path was never configured. Supply that path in the host's ignored local configuration, restart or update the catalog root through local stdio runtime configuration, and query the catalog again before calling run_prepare. A missing capability error names these next tools and reports the profile location as unknown when appropriate. Search/list first pages also warn about unconfigured candidate roots and summarize generation-wide audit issues with catalog_issues_present. These issues can explain unavailable items, but do not prove why a particular query missed. Use catalog_query(mode=audit) for file references and exact parse or validation errors. Missing-ID errors include catalog_issue_count when nonzero and recommend that audit for both catalog_query(mode=get) and Runner capability resolution. Cursor pages retain their original result snapshot; diagnostics are reported on the first page. No extra opt-in flag is required. Keep private profile content and host paths out of tracked files. A copyable host configuration is:

{
  "mcpServers": {
    "repoplane": {
      "command": "repoplane",
      "args": ["--workspace", "WORKSPACE", "--state-dir", "STATE_DIRECTORY",
               "--catalog-root", "catalog", "--catalog-root", "PRIVATE_CATALOG_ROOT",
               "--candidate-root", "PRIVATE_TOOLS_ROOT"]
    }
  }
}

The optional toolbox.v1 startup surface exposes five fixed tools: repoplane_read, repoplane_write, repoplane_import, repoplane_runner, and repoplane_state. Describe an allowlisted operation before calling it with the returned content-bound schema handle. Changing surfaces requires a new session.

HTTP is opt-in. The example local and OAuth profiles show loopback binding, authentication, and scopes. Keep actual tokens, key files, host paths, and profiles outside the tracked tree. HTTP authorization is separate from local stdio session grants.

Local state, backup, and usage

Moving a workspace

On the first successful upgraded startup, RepoPlane pins the existing path-derived ID in the private .repoplane/workspace-id file. Existing memos, checkpoints, and other records keep their IDs; no database migration is needed. Keep .repoplane/ ignored by Git in every workspace (add /.repoplane/ to .gitignore) and never publish the identity file. Move this file with the directory, retain the same --state-dir, and update --workspace or use:

{"action":"select","target":"workspace","values":["NEW_ABSOLUTE_WORKSPACE_PATH"]}

runtime_config(action=status) reports workspace_identity: ["pinned", ".repoplane/workspace-id"]. The new directory remains the filesystem trust boundary; read grants do not travel with it. Fresh Git clones without this ignored file remain separate projects. A full directory copy including the file intentionally shares record identity; omit the file for an independent copy. Keep only one active server per identity/state directory. The workspace must be writable for initial pinning. The marker is published only after service initialization, using complete synced bytes without replacing a competing opener's marker. Failed state initialization leaves no pinned ID, so legacy adoption can still be retried. Oversized, non-regular, or damaged files are rejected rather than silently creating a new identity. Only the small identity file is created inside the workspace; databases and process output stay in the external state directory.

If a legacy workspace was already moved before upgrading, start once with the old canonical absolute path, even if that path no longer exists. Use the same original state directory:

repoplane --workspace NEW_WORKSPACE --state-dir ORIGINAL_STATE_DIRECTORY --workspace-origin FORMER_CANONICAL_ABSOLUTE_PATH

Copyable MCP host configuration for this one-time adoption:

{"mcpServers":{"repoplane":{"command":"repoplane","args":["--workspace","NEW_WORKSPACE","--state-dir","ORIGINAL_STATE_DIRECTORY","--workspace-origin","FORMER_CANONICAL_ABSOLUTE_PATH"]}}}

Normal moves need no origin option or agent intervention: move the whole directory, including .repoplane/ even when a copy tool skips dot directories, and keep using the existing state. Agents must not add --workspace-origin on every move. Remove it after the first successful legacy adoption. It cannot replace a conflicting saved ID or merge separately created histories. The former path must match the old resolved path (including the original drive/platform); Git remotes and folder names are not used to guess identity. Retain the state databases: the small identity file does not contain the records. If it is lost after a move, restore it or adopt the original pre-pin canonical path, not an intermediate location. Machine changes also require transferring state or memory export/restore.

RepoPlane keeps regenerable search indexes and durable records in separate SQLite files under --state-dir, never inside the workspace. Runner streams and captured artifacts are retained there under bounded policies. Removing the state directory also removes local memos, checkpoints, imported verification, run receipts, streams, artifacts, and cursors.

Each new query snapshot atomically removes up to 64 snapshots that have expired at the query's creation time, including their result items. Unexpired pagination snapshots and durable records are preserved. Cleanup runs on snapshot writes, not an idle timer; an existing backlog drains across subsequent queries. SQLite reuses the freed pages, so the database file need not shrink. This does not impose a total disk quota or prune durable record revisions.

Catalog refreshes also remove up to 16 retired generations in the current workspace, even when catalog contents are unchanged. A generation must have been replaced for at least one hour and have no unexpired query snapshot references; the current generation is always retained. Retirement is tracked separately from creation, and reactivation starts a fresh grace period on the next replacement. Runner plans retain execution details and revalidate against the current declaration, so they do not depend on retaining an old catalog generation. Durable execution receipts remain.

Retirement metadata uses an additive auxiliary table while preserving the index database's schema 3 compatibility marker and original catalog columns. Database triggers also track replacements and reactivations made by schema-3 publishers. Existing inactive generations start a fresh one-hour grace period when first observed. The known pre-release retirement schema 4 is recovered to this layout without dropping catalog data; unknown future schemas are still rejected. Running processes load the new cleanup code on their next restart. Cleanup runs during catalog refreshes, not an idle timer, and freed pages are reused without automatically compacting the file.

Use memory_backup or the CLI to export durable records and retained Runner evidence before a machine reset:

repoplane memory export --destination BACKUP_DIRECTORY --workspace WORKSPACE --state-dir STATE_DIRECTORY
repoplane memory restore --archive BACKUP_ARCHIVE --workspace RESTORED_WORKSPACE --state-dir NEW_STATE_DIRECTORY

The archive may contain sensitive record or process output; store it privately. It excludes local HTTP tokens, host profiles, regenerable indexes, and audit data.

repoplane usage --workspace WORKSPACE --state-dir STATE_DIRECTORY

usage reports observed operation calls, serialized request/result bytes, duration, errors, approvals, and reused runs. It does not measure model tokens or estimate savings.

Development and releases

go run ./cmd/repoplane-dev preflight
go run ./cmd/repoplane-dev verify
go run ./cmd/repoplane-dev public-release-check

These commands write bounded reports under ignored .tmp/reports. The release workflow builds versioned archives after successful CI and generates release notes from the matching CHANGELOG.md section. RepoPlane is maintainer-led; public issues and pull requests are closed for now. See SECURITY.md for vulnerability reporting.

CI runs on pushes to main and on pull requests, cancelling superseded runs for the same ref. Source, schema, catalog, and workflow changes retain Ubuntu and Windows verification; main also runs race tests and the public-release privacy check. Documentation-only changes, including a release changelog promotion, use the lightweight scope check plus the main privacy check so the release guard still receives a successful CI result for the exact release commit.

License

MIT

About

Bounded repository search, durable task evidence, and gated execution for coding agents.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages