Moe1177/cairn

A map of all your repos for coding agents: one index, one card per repo, an MCP server. Works with Claude Code, Codex, Gemini CLI and Cursor.

Python

0

146 commits

updated Oct 6, 2026

See the code

See what people are saying

SourceMessageScoreDate

Show HN: cairn, a map of all your repos for coding agents

2

Oct 6, 2026

[showcase] cairn: multi-repo index as an MCP server for coding agents (r/mcp)

I built cairn, a CLI that maps every git repo in a folder and serves it to coding agents over MCP. Tools it exposes: resolve\_repo (which repo is "the payments service"), repo\_card (per-repo card, re-scanned if HEAD moved), related (everything connected to a repo, with file:line evidence),…

1

Oct 6, 2026

README

cairn

CI PyPI Python License: Apache-2.0

A map of all your repos for coding agents. cairn scans every git repo in a folder once, works out how they connect, and gives your agent:

  • a tiny always-loaded index;
  • a short card per repo.

The agent knows where things live without re-exploring.

Cairns are the stacked-stone markers that guide hikers along a trail. cairn leaves small, cheap markers that guide coding agents to the right repo, and then to the right file.

Works with Claude Code, Codex, Gemini CLI, and Cursor, on Windows, macOS, and Linux.


Why

Coding agents work inside one repo. Real work spans many: a web app, an API, a shared types package, a payments service, a reporting job. Say "add a tip amount to trips end to end" and the agent has to:

  1. discover that sibling repos exist;
  2. grep through all of them;
  3. read a pile of files;
  4. guess which service owns the trips table.

That costs tokens and turns, and weaker models guess wrong.

Hand-maintained "related repos" docs help until they go stale. cairn builds that map from the code itself and keeps it fresh:

~/code/                          ~/code/.cairn/
├── rider-web/        cairn      ├── INDEX.md           ← ~25 tokens per repo, always loaded
├── trips-svc/        ─────▶     ├── cards/trips-svc.md ← read on demand (≤ 800 tokens)
├── payments-svc/     scan       ├── workspace.json     ← the full graph
├── analytics-etl/               └── authored/          ← your summaries and decisions
└── …

What your agent sees

The index is a few lines, loaded into every session through CLAUDE.md or an equivalent:

# Workspace repos (cairn)
- admin-console (@fleetline/admin-console): Next.js back-office for ops staff; queries trips… · nextjs
- analytics-etl: Nightly SQL reporting jobs over trips, payments, drivers… · python
- payments-svc (payments): Go service that charges completed trips and records weekly payouts · go
- trips-svc: FastAPI service owning the trip lifecycle and the trips/trip_events tables · fastapi · used by admin-console
- ui-kit (@fleetline/ui-kit): Shared React components (Button, Card) · react · used by admin-console

used by lists the repos that depend on, call, or reference that one, so a change's blast radius is visible before any card is opened.

A card is read only when the agent needs that repo:

# trips-svc
> FastAPI service owning the trip lifecycle and the trips/trip_events tables.
`./trips-svc` · python, fastapi · HEAD 1fee22d

## Relates
← infra: references path trips-svc (extracted · infra/docker-compose.dev.yml:3)
→ admin-console: shares tables trips (inferred · admin-console/lib/db.ts:4)
→ analytics-etl: shares tables trips (inferred · analytics-etl/jobs/daily_revenue.sql:2)
→ payments-svc: shares tables trips (inferred · payments-svc/internal/charge/charge.go:3)

## Run
test `pytest`

## Layout
app/ → routes/pages
migrations/ → DB migrations

Every relationship comes with evidence (file and line) and a trust level:

  • extracted: found directly, such as a package dependency or a path reference;
  • inferred: strong signals, such as a table that only one repo creates;
  • ambiguous: hidden until you confirm it.

Install

You need Python 3.11+ and git.

ToolCommand
uv (recommended)uv tool install cairnmap
pipxpipx install cairnmap
run once, no installuvx --from cairnmap cairn init
pippip install cairnmap

The package is called cairnmap; the command is cairn.

Quick start

cd ~/code              # the folder that CONTAINS your repos
cairn init             # scan, then add the index to ./CLAUDE.md (asks first)
cairn install all      # optional: Codex, Gemini CLI, Cursor, the /cairn skill, the MCP server

Open your agent in any repo under that folder. It now knows about every sibling repo.

To give each repo a good one-line summary, run /cairn inside your agent. It writes the summaries with cairn set-summary and settles uncertain links. Until then, the index says "no summary yet" for that repo. README text is deliberately kept out of always-loaded context.

How to use it

Day to day

cairn status               # repos, relationships, unconfirmed links, missing/stale summaries
cairn refresh              # re-read only repos that changed (fast)
cairn hooks install        # optional: refresh automatically after each commit/merge
cairn annotate-edge "web->api:shares_db" --confirm
cairn annotate-edge "shop->blog:shares_db" --reject --why "different databases"
cairn set-summary payments-svc "Charges completed trips and pays drivers weekly." --alias payments

These decisions live in .cairn/authored/ and .cairn/relations.yaml. They survive every re-scan, and you can commit them so your team shares them.

From your agent (MCP)

cairn install <harness> registers cairn's MCP server. Agents can call these tools:

ToolWhat it answers
resolve_repo"Which repo is 'the payments service'?"
repo_cardThe card for a repo (re-scanned first if its HEAD moved)
relatedEverything connected to a repo, with evidence
find_acrossWhich repos expose or use a table, package, or path
queryWhere inside a repo: symbol — file:line from a deep index, else which folders to start in
refreshUpdate the map now

What cairn detects

SignalExamples
HTTP callsNext.js routes, Express/Fastify/Hono, FastAPI/Flask, Go (net/http, chi, gin, echo) and OpenAPI routes, matched with fetch/axios, requests/httpx, and Go http client calls
gRPCGo, Python, TypeScript and Java servers matched with their client stubs
Service namesCalls addressed to a sibling's service name, as Docker and Kubernetes DNS do: http://catalogue, http://carts:8080/carts, *.svc.cluster.local, Hostname("payment")
Pub/sub topicsKafka, NATS, Redis and RabbitMQ (Spring AMQP) publishers matched with subscribers (trip.completed)
docker-composedepends_on between services built from (or named after) your repos
Deploy reposcompose, Kubernetes and Helm files that run your repos' images (image: acme/catalogue:1.2)
Package dependenciesnpm (workspace:*, scoped packages), PyPI, Go modules, Cargo
Packages inside monoreposnpm/yarn/pnpm, Cargo, go.work and uv workspaces, listed on the card and resolvable by name
Shared database tablesSQL migrations and queries, Prisma, Drizzle, Supabase, MongoDB (Mongoose models, db.collection("x"))
Copies of one appRepos that share their first commit (an app cloned per event or per client): "change one, check the other". The same package name only suggests it
Path references../trips-svc in docker-compose, tsconfig, and other config files
DocumentationREADMEs and docs that mention a sibling repo
Shared env varsSpecific names read by both sides. These only back up another link; they never make one on their own

Look-alikes are deliberately ignored:

  • health-check routes;
  • calls to other companies' APIs;
  • vague topic names;
  • .proto files with no implementer;
  • generic env vars such as PORT;
  • localhost, public domains, and URLs in comments;
  • public images (mongo:3.4) and look-alike names (catalogue-db is not catalogue);
  • a queue that a repo declares but never consumes;
  • the same schema in two copies of one app (copies often use a database each, so no link);
  • Firestore's db.collection(...), which looks like MongoDB's.

Evaluation workspaces in the test suite keep every one of these at precision 1.0.

Deep queries

The map tells an agent which repo to open. A deep index tells it where inside: the query MCP tool answers "where is login handled?" with login() — src/auth.py:12 hits and their neighbours, instead of a list of folders.

Deep indexes are optional and built per repo with graphify:

uv tool install 'cairnmap[graphify]'   # or: pip install 'cairnmap[graphify]'
cairn deep build trips-svc             # one repo (or --all)
cairn deep status                      # size, build sha, fresh or stale
cairn refresh --deep                   # after changes: rebuild only the stale indexes
cairn deep clear trips-svc             # delete it
  • graphify always runs --code-only: no LLM, no network, and an allowlisted environment, so no API key reaches it. It writes only to .cairn/deep/<repo>/, never into the repo.
  • cairn answers queries itself from the saved graph, offline, so serving needs no graphify.
  • Building is never automatic (the first build of a large repo can take minutes). When an index falls behind the repo (a new commit or an uncommitted edit), query and cairn deep status say so; the card's Deeper section flags new commits.

Benchmarks

cairn ships a benchmark harness (cairn bench). It runs real tasks through headless Claude Code under five conditions, each in a fresh, isolated copy of a multi-repo workspace:

Condition
ANo map (the agent explores)
BA hand-written "related repos" doc
Ccairn's index only
DIndex + repo cards
ED + cairn's MCP server

Tasks cover:

  • orientation: "who owns the trips table?";
  • localization: "which files change to add a tip amount end to end?";
  • cross-repo impact: "what breaks if drivers.license_no is renamed?";
  • control: questions answerable within one repo.

Answers are graded deterministically on the files and facts they must name.

Results (2026-10-06): 840 runs over four workspaces, two of them real open-source systems: Sock Shop (9 microservice repos; cairn's newest link types were developed on it) and the Supabase JS client family (6 repos, held out: never used to tune cairn). 28 tasks, 3 runs per cell, Haiku 4.5 and Sonnet 5.5. A result is called significant only after Holm adjustment.

Per task, cairn INDEX (C)Haiku 4.5Sonnet 5.5
Fresh tokens vs no map-19% (significant)-10% (n.s.)
Cost vs no map-25% (n.s. after adjustment)-12% (n.s.)
Cost vs a hand-written doc-6% (n.s.)-12% (significant)
Cost vs no map, Sock Shop only-27% (n.s.)-26% (borderline, adjusted p = 0.055)

What this shows:

  • cairn tends to make cross-repo work cheaper: fewer fresh tokens for Haiku, and cheaper than a hand-written related-repos doc for Sonnet. The largest raw savings were on Sock Shop.
  • It doesn't measurably raise success. Sonnet answers 99-100% of these tasks in every condition; Haiku rises from 86% to 93% with the MCP server, which isn't significant.
  • It isn't a win everywhere. On fleetline, Sonnet cost 7-10% more with cairn than without (n.s.); the held-out Supabase effects are small and not significant.
  • Correction: our first, smaller run (2026-10-05) reported Haiku reaching 100% with the INDEX. With more runs that doesn't replicate (89-92%, the same as no map).

Methods, every table with 95% intervals, paired Wilcoxon tests (raw and Holm-adjusted), the regressions, and the held-out results: bench/published/2026-10-06-real-world.md. The first run is kept at 2026-10-05-shopverse-fleetline.md.

Run the benchmarks yourself from a source checkout. They use your Claude usage.

cairn bench bench/suites/sockshop --runs 3 --model haiku    # fetches the pinned repos once
cairn bench bench/suites/shopverse --conditions A,C --tasks gift-message
cairn bench bench/suites/sockshop --resume bench/results/<stamp>.jsonl   # after a usage limit

Performance

cairn walks each repo once, reads each file once, and scans repos in parallel. A refresh re-reads only repos whose HEAD or working tree changed, and asks git one question per unchanged repo. On a synthetic workspace of 200 repos and 50,000 files (Windows 11, 12 cores):

0.30.4
First scan156 s53 s
Refresh, nothing changed15 s5.4 s

Most of a refresh on Windows is git process start-up; Linux and macOS start processes faster. Reproduce with uv run python bench/perf_scan.py --out <dir>.

Safety and privacy

cairn treats every scanned repo as untrusted input. It:

  • never modifies your repos, apart from the opt-in hooks and Cursor's --per-repo rule (both marked blocks, removed cleanly);
  • never opens .env files, private keys, or credential files, and never reads through a symlink or junction inside a repo;
  • never runs code from a repo: its git calls turn off fsmonitor, hooks, and the repo's own filters.

It also:

  • redacts common secret formats from every stored snippet, and stores git remotes without credentials;
  • keeps README text out of always-loaded context. Repo names that do appear are flattened and capped, so they can't inject instructions or break cairn's blocks.

cairn makes no network calls and collects no telemetry. cairn bench is the only feature that runs another program that does (the claude CLI).

See SECURITY.md for the threat model and how to report a vulnerability.

Reference

Commands

CommandWhat it does
cairn initScan, then offer to add the index to Claude Code
cairn scan [--full] [--verbose]Map every repo under the folder into .cairn/
cairn refresh [--deep]Re-read only repos whose HEAD or working tree changed (--deep: also rebuild stale deep indexes)
cairn deep build REPO…|--all|--stale [-w PATH]Build graphify code indexes so query answers with file:line (optional extra)
cairn deep status / cairn deep clear [REPO…]List deep indexes (fresh or stale) / delete them
cairn statusWhat cairn knows, unconfirmed links, missing or stale summaries
cairn annotate-edge KEY --confirm|--reject [--why TEXT]Settle a relationship
cairn set-summary REPO TEXT [--alias NAME]Save a summary (- reads stdin)
cairn install <claude|codex|gemini|cursor|all>Load cairn into an agent harness
cairn uninstall <name|all>Remove it again
cairn hooks install|uninstallOpt-in git hooks that refresh after commits and merges
cairn serveThe MCP server (harnesses start it for you)
cairn bench SUITERun the benchmark harness
cairn doctorCheck git, Python, the map, write access, harnesses and graphify; exits 1 on a failure
cairn --install-completionTab completion for your shell (bash, zsh, fish, PowerShell)
cairn --versionVersions of cairn, Python, the platform, and mcp

Exit codes: 0 means success, 1 an error (one line on stderr), and 2 a usage error.

What cairn writes, and where

PathWhat it is
<folder>/.cairn/INDEX.mdOne line per repo: name, aliases, summary, stack
<folder>/.cairn/cards/<repo>.mdOne card per repo
<folder>/.cairn/workspace.jsonThe full graph
<folder>/.cairn/cache/, logs/, .lockScan cache, last scan log, lock file
<folder>/.cairn/relations.yaml, authored/Yours: aliases, manual links, summaries, decisions
<folder>/CLAUDE.mdA marked block holding the index (cairn install claude)
~/.claude/skills/cairn/ and Claude's user MCP config/cairn skill and MCP server
~/.codex/AGENTS.md, config.toml, skills/cairn/Codex pointer, MCP server, skill
~/.gemini/GEMINI.md, settings.json, commands/cairn.tomlGemini CLI pointer, MCP server, command
~/.cursor/mcp.json, commands/cairn.mdCursor MCP server and command
<repo>/.git/hooks/post-commit, post-mergeOnly with cairn hooks install
~/.cairn/registry.json, backups/Mapped workspaces; one private backup of each config file cairn first edited

cairn only edits its own key or marked block in other tools' files, and refuses to touch a file it can't parse.

Uninstall

cairn uninstall all          # harness entries, skills, commands, Cursor rules
cairn hooks uninstall        # if you installed hooks
uv tool uninstall cairnmap   # or: pipx uninstall cairnmap

Then delete <folder>/.cairn/ and ~/.cairn/.

Troubleshooting

SymptomFix
Something seems offRun cairn doctor: it checks each dependency and says what to fix
"No git repos found under …"Run cairn from the folder that contains your repos
"… is inside the git repository …"Same: run from the parent folder, not inside a repo
"git not found on PATH" warningInstall git; without it, remotes, HEAD and caching are off
"another cairn process is still updating this workspace"A scan or hook refresh is running; retry in a moment
An agent says it can't find the workspaceRun cairn init in the workspace folder, then restart the agent session
cairn install codex/gemini/cursor refuses a configThat file isn't valid TOML/JSON; fix it and re-run (cairn never guesses)
Hooks did nothingcairn hooks install lists the repos it skipped and why

Roadmap

  1. ✅ Core map, precision pass, MCP server, harness integrations, freshness, benchmarks, release hardening (0.1).
  2. ✅ HTTP, gRPC, pub/sub, compose and env-var relationships; packages inside monorepos (0.2).
  3. ✅ Deep per-repo queries via graphify (0.3).
  4. ✅ Efficiency: 3x faster scans, faster and more reliable CI, cairn doctor, shell completion, and "used by" on INDEX lines (0.4).
  5. ✅ Real-world reach (service DNS, deploy repos, RabbitMQ) and benchmarks on real open-source workspaces with significance testing (0.5).
  6. More harnesses in the benchmark (Codex), and more ecosystems.

Contributing

Issues and pull requests are welcome. See CONTRIBUTING.md for setup and checks, and the changelog for what's new. By participating you agree to the code of conduct.

License

cairn is licensed under the Apache License 2.0: use it, modify it, and ship it, commercially too. Keep the license and the NOTICE file with any redistribution. Full text: LICENSE.

Runtime dependencies and their licenses:

PackageLicense
typerMIT
pydanticMIT
PyYAMLMIT
pathspecMPL-2.0 (used unmodified as a library)
mcpMIT
ai-agents
claude-code
cli
coding-agents
developer-tools
knowledge-graph
mcp
microservices
monorepo
python

Moe1177/cairn

A map of all your repos for coding agents: one index, one card per repo, an MCP server. Works with Claude Code, Codex, Gemini CLI and Cursor.

Python

0

146 commits

updated Oct 6, 2026

See the code

See what people are saying

SourceMessageScoreDate

Show HN: cairn, a map of all your repos for coding agents

2

Oct 6, 2026

[showcase] cairn: multi-repo index as an MCP server for coding agents (r/mcp)

I built cairn, a CLI that maps every git repo in a folder and serves it to coding agents over MCP. Tools it exposes: resolve\_repo (which repo is "the payments service"), repo\_card (per-repo card, re-scanned if HEAD moved), related (everything connected to a repo, with file:line evidence),…

1

Oct 6, 2026

README

cairn

CI PyPI Python License: Apache-2.0

A map of all your repos for coding agents. cairn scans every git repo in a folder once, works out how they connect, and gives your agent:

  • a tiny always-loaded index;
  • a short card per repo.

The agent knows where things live without re-exploring.

Cairns are the stacked-stone markers that guide hikers along a trail. cairn leaves small, cheap markers that guide coding agents to the right repo, and then to the right file.

Works with Claude Code, Codex, Gemini CLI, and Cursor, on Windows, macOS, and Linux.


Why

Coding agents work inside one repo. Real work spans many: a web app, an API, a shared types package, a payments service, a reporting job. Say "add a tip amount to trips end to end" and the agent has to:

  1. discover that sibling repos exist;
  2. grep through all of them;
  3. read a pile of files;
  4. guess which service owns the trips table.

That costs tokens and turns, and weaker models guess wrong.

Hand-maintained "related repos" docs help until they go stale. cairn builds that map from the code itself and keeps it fresh:

~/code/                          ~/code/.cairn/
├── rider-web/        cairn      ├── INDEX.md           ← ~25 tokens per repo, always loaded
├── trips-svc/        ─────▶     ├── cards/trips-svc.md ← read on demand (≤ 800 tokens)
├── payments-svc/     scan       ├── workspace.json     ← the full graph
├── analytics-etl/               └── authored/          ← your summaries and decisions
└── …

What your agent sees

The index is a few lines, loaded into every session through CLAUDE.md or an equivalent:

# Workspace repos (cairn)
- admin-console (@fleetline/admin-console): Next.js back-office for ops staff; queries trips… · nextjs
- analytics-etl: Nightly SQL reporting jobs over trips, payments, drivers… · python
- payments-svc (payments): Go service that charges completed trips and records weekly payouts · go
- trips-svc: FastAPI service owning the trip lifecycle and the trips/trip_events tables · fastapi · used by admin-console
- ui-kit (@fleetline/ui-kit): Shared React components (Button, Card) · react · used by admin-console

used by lists the repos that depend on, call, or reference that one, so a change's blast radius is visible before any card is opened.

A card is read only when the agent needs that repo:

# trips-svc
> FastAPI service owning the trip lifecycle and the trips/trip_events tables.
`./trips-svc` · python, fastapi · HEAD 1fee22d

## Relates
← infra: references path trips-svc (extracted · infra/docker-compose.dev.yml:3)
→ admin-console: shares tables trips (inferred · admin-console/lib/db.ts:4)
→ analytics-etl: shares tables trips (inferred · analytics-etl/jobs/daily_revenue.sql:2)
→ payments-svc: shares tables trips (inferred · payments-svc/internal/charge/charge.go:3)

## Run
test `pytest`

## Layout
app/ → routes/pages
migrations/ → DB migrations

Every relationship comes with evidence (file and line) and a trust level:

  • extracted: found directly, such as a package dependency or a path reference;
  • inferred: strong signals, such as a table that only one repo creates;
  • ambiguous: hidden until you confirm it.

Install

You need Python 3.11+ and git.

ToolCommand
uv (recommended)uv tool install cairnmap
pipxpipx install cairnmap
run once, no installuvx --from cairnmap cairn init
pippip install cairnmap

The package is called cairnmap; the command is cairn.

Quick start

cd ~/code              # the folder that CONTAINS your repos
cairn init             # scan, then add the index to ./CLAUDE.md (asks first)
cairn install all      # optional: Codex, Gemini CLI, Cursor, the /cairn skill, the MCP server

Open your agent in any repo under that folder. It now knows about every sibling repo.

To give each repo a good one-line summary, run /cairn inside your agent. It writes the summaries with cairn set-summary and settles uncertain links. Until then, the index says "no summary yet" for that repo. README text is deliberately kept out of always-loaded context.

How to use it

Day to day

cairn status               # repos, relationships, unconfirmed links, missing/stale summaries
cairn refresh              # re-read only repos that changed (fast)
cairn hooks install        # optional: refresh automatically after each commit/merge
cairn annotate-edge "web->api:shares_db" --confirm
cairn annotate-edge "shop->blog:shares_db" --reject --why "different databases"
cairn set-summary payments-svc "Charges completed trips and pays drivers weekly." --alias payments

These decisions live in .cairn/authored/ and .cairn/relations.yaml. They survive every re-scan, and you can commit them so your team shares them.

From your agent (MCP)

cairn install <harness> registers cairn's MCP server. Agents can call these tools:

ToolWhat it answers
resolve_repo"Which repo is 'the payments service'?"
repo_cardThe card for a repo (re-scanned first if its HEAD moved)
relatedEverything connected to a repo, with evidence
find_acrossWhich repos expose or use a table, package, or path
queryWhere inside a repo: symbol — file:line from a deep index, else which folders to start in
refreshUpdate the map now

What cairn detects

SignalExamples
HTTP callsNext.js routes, Express/Fastify/Hono, FastAPI/Flask, Go (net/http, chi, gin, echo) and OpenAPI routes, matched with fetch/axios, requests/httpx, and Go http client calls
gRPCGo, Python, TypeScript and Java servers matched with their client stubs
Service namesCalls addressed to a sibling's service name, as Docker and Kubernetes DNS do: http://catalogue, http://carts:8080/carts, *.svc.cluster.local, Hostname("payment")
Pub/sub topicsKafka, NATS, Redis and RabbitMQ (Spring AMQP) publishers matched with subscribers (trip.completed)
docker-composedepends_on between services built from (or named after) your repos
Deploy reposcompose, Kubernetes and Helm files that run your repos' images (image: acme/catalogue:1.2)
Package dependenciesnpm (workspace:*, scoped packages), PyPI, Go modules, Cargo
Packages inside monoreposnpm/yarn/pnpm, Cargo, go.work and uv workspaces, listed on the card and resolvable by name
Shared database tablesSQL migrations and queries, Prisma, Drizzle, Supabase, MongoDB (Mongoose models, db.collection("x"))
Copies of one appRepos that share their first commit (an app cloned per event or per client): "change one, check the other". The same package name only suggests it
Path references../trips-svc in docker-compose, tsconfig, and other config files
DocumentationREADMEs and docs that mention a sibling repo
Shared env varsSpecific names read by both sides. These only back up another link; they never make one on their own

Look-alikes are deliberately ignored:

  • health-check routes;
  • calls to other companies' APIs;
  • vague topic names;
  • .proto files with no implementer;
  • generic env vars such as PORT;
  • localhost, public domains, and URLs in comments;
  • public images (mongo:3.4) and look-alike names (catalogue-db is not catalogue);
  • a queue that a repo declares but never consumes;
  • the same schema in two copies of one app (copies often use a database each, so no link);
  • Firestore's db.collection(...), which looks like MongoDB's.

Evaluation workspaces in the test suite keep every one of these at precision 1.0.

Deep queries

The map tells an agent which repo to open. A deep index tells it where inside: the query MCP tool answers "where is login handled?" with login() — src/auth.py:12 hits and their neighbours, instead of a list of folders.

Deep indexes are optional and built per repo with graphify:

uv tool install 'cairnmap[graphify]'   # or: pip install 'cairnmap[graphify]'
cairn deep build trips-svc             # one repo (or --all)
cairn deep status                      # size, build sha, fresh or stale
cairn refresh --deep                   # after changes: rebuild only the stale indexes
cairn deep clear trips-svc             # delete it
  • graphify always runs --code-only: no LLM, no network, and an allowlisted environment, so no API key reaches it. It writes only to .cairn/deep/<repo>/, never into the repo.
  • cairn answers queries itself from the saved graph, offline, so serving needs no graphify.
  • Building is never automatic (the first build of a large repo can take minutes). When an index falls behind the repo (a new commit or an uncommitted edit), query and cairn deep status say so; the card's Deeper section flags new commits.

Benchmarks

cairn ships a benchmark harness (cairn bench). It runs real tasks through headless Claude Code under five conditions, each in a fresh, isolated copy of a multi-repo workspace:

Condition
ANo map (the agent explores)
BA hand-written "related repos" doc
Ccairn's index only
DIndex + repo cards
ED + cairn's MCP server

Tasks cover:

  • orientation: "who owns the trips table?";
  • localization: "which files change to add a tip amount end to end?";
  • cross-repo impact: "what breaks if drivers.license_no is renamed?";
  • control: questions answerable within one repo.

Answers are graded deterministically on the files and facts they must name.

Results (2026-10-06): 840 runs over four workspaces, two of them real open-source systems: Sock Shop (9 microservice repos; cairn's newest link types were developed on it) and the Supabase JS client family (6 repos, held out: never used to tune cairn). 28 tasks, 3 runs per cell, Haiku 4.5 and Sonnet 5.5. A result is called significant only after Holm adjustment.

Per task, cairn INDEX (C)Haiku 4.5Sonnet 5.5
Fresh tokens vs no map-19% (significant)-10% (n.s.)
Cost vs no map-25% (n.s. after adjustment)-12% (n.s.)
Cost vs a hand-written doc-6% (n.s.)-12% (significant)
Cost vs no map, Sock Shop only-27% (n.s.)-26% (borderline, adjusted p = 0.055)

What this shows:

  • cairn tends to make cross-repo work cheaper: fewer fresh tokens for Haiku, and cheaper than a hand-written related-repos doc for Sonnet. The largest raw savings were on Sock Shop.
  • It doesn't measurably raise success. Sonnet answers 99-100% of these tasks in every condition; Haiku rises from 86% to 93% with the MCP server, which isn't significant.
  • It isn't a win everywhere. On fleetline, Sonnet cost 7-10% more with cairn than without (n.s.); the held-out Supabase effects are small and not significant.
  • Correction: our first, smaller run (2026-10-05) reported Haiku reaching 100% with the INDEX. With more runs that doesn't replicate (89-92%, the same as no map).

Methods, every table with 95% intervals, paired Wilcoxon tests (raw and Holm-adjusted), the regressions, and the held-out results: bench/published/2026-10-06-real-world.md. The first run is kept at 2026-10-05-shopverse-fleetline.md.

Run the benchmarks yourself from a source checkout. They use your Claude usage.

cairn bench bench/suites/sockshop --runs 3 --model haiku    # fetches the pinned repos once
cairn bench bench/suites/shopverse --conditions A,C --tasks gift-message
cairn bench bench/suites/sockshop --resume bench/results/<stamp>.jsonl   # after a usage limit

Performance

cairn walks each repo once, reads each file once, and scans repos in parallel. A refresh re-reads only repos whose HEAD or working tree changed, and asks git one question per unchanged repo. On a synthetic workspace of 200 repos and 50,000 files (Windows 11, 12 cores):

0.30.4
First scan156 s53 s
Refresh, nothing changed15 s5.4 s

Most of a refresh on Windows is git process start-up; Linux and macOS start processes faster. Reproduce with uv run python bench/perf_scan.py --out <dir>.

Safety and privacy

cairn treats every scanned repo as untrusted input. It:

  • never modifies your repos, apart from the opt-in hooks and Cursor's --per-repo rule (both marked blocks, removed cleanly);
  • never opens .env files, private keys, or credential files, and never reads through a symlink or junction inside a repo;
  • never runs code from a repo: its git calls turn off fsmonitor, hooks, and the repo's own filters.

It also:

  • redacts common secret formats from every stored snippet, and stores git remotes without credentials;
  • keeps README text out of always-loaded context. Repo names that do appear are flattened and capped, so they can't inject instructions or break cairn's blocks.

cairn makes no network calls and collects no telemetry. cairn bench is the only feature that runs another program that does (the claude CLI).

See SECURITY.md for the threat model and how to report a vulnerability.

Reference

Commands

CommandWhat it does
cairn initScan, then offer to add the index to Claude Code
cairn scan [--full] [--verbose]Map every repo under the folder into .cairn/
cairn refresh [--deep]Re-read only repos whose HEAD or working tree changed (--deep: also rebuild stale deep indexes)
cairn deep build REPO…|--all|--stale [-w PATH]Build graphify code indexes so query answers with file:line (optional extra)
cairn deep status / cairn deep clear [REPO…]List deep indexes (fresh or stale) / delete them
cairn statusWhat cairn knows, unconfirmed links, missing or stale summaries
cairn annotate-edge KEY --confirm|--reject [--why TEXT]Settle a relationship
cairn set-summary REPO TEXT [--alias NAME]Save a summary (- reads stdin)
cairn install <claude|codex|gemini|cursor|all>Load cairn into an agent harness
cairn uninstall <name|all>Remove it again
cairn hooks install|uninstallOpt-in git hooks that refresh after commits and merges
cairn serveThe MCP server (harnesses start it for you)
cairn bench SUITERun the benchmark harness
cairn doctorCheck git, Python, the map, write access, harnesses and graphify; exits 1 on a failure
cairn --install-completionTab completion for your shell (bash, zsh, fish, PowerShell)
cairn --versionVersions of cairn, Python, the platform, and mcp

Exit codes: 0 means success, 1 an error (one line on stderr), and 2 a usage error.

What cairn writes, and where

PathWhat it is
<folder>/.cairn/INDEX.mdOne line per repo: name, aliases, summary, stack
<folder>/.cairn/cards/<repo>.mdOne card per repo
<folder>/.cairn/workspace.jsonThe full graph
<folder>/.cairn/cache/, logs/, .lockScan cache, last scan log, lock file
<folder>/.cairn/relations.yaml, authored/Yours: aliases, manual links, summaries, decisions
<folder>/CLAUDE.mdA marked block holding the index (cairn install claude)
~/.claude/skills/cairn/ and Claude's user MCP config/cairn skill and MCP server
~/.codex/AGENTS.md, config.toml, skills/cairn/Codex pointer, MCP server, skill
~/.gemini/GEMINI.md, settings.json, commands/cairn.tomlGemini CLI pointer, MCP server, command
~/.cursor/mcp.json, commands/cairn.mdCursor MCP server and command
<repo>/.git/hooks/post-commit, post-mergeOnly with cairn hooks install
~/.cairn/registry.json, backups/Mapped workspaces; one private backup of each config file cairn first edited

cairn only edits its own key or marked block in other tools' files, and refuses to touch a file it can't parse.

Uninstall

cairn uninstall all          # harness entries, skills, commands, Cursor rules
cairn hooks uninstall        # if you installed hooks
uv tool uninstall cairnmap   # or: pipx uninstall cairnmap

Then delete <folder>/.cairn/ and ~/.cairn/.

Troubleshooting

SymptomFix
Something seems offRun cairn doctor: it checks each dependency and says what to fix
"No git repos found under …"Run cairn from the folder that contains your repos
"… is inside the git repository …"Same: run from the parent folder, not inside a repo
"git not found on PATH" warningInstall git; without it, remotes, HEAD and caching are off
"another cairn process is still updating this workspace"A scan or hook refresh is running; retry in a moment
An agent says it can't find the workspaceRun cairn init in the workspace folder, then restart the agent session
cairn install codex/gemini/cursor refuses a configThat file isn't valid TOML/JSON; fix it and re-run (cairn never guesses)
Hooks did nothingcairn hooks install lists the repos it skipped and why

Roadmap

  1. ✅ Core map, precision pass, MCP server, harness integrations, freshness, benchmarks, release hardening (0.1).
  2. ✅ HTTP, gRPC, pub/sub, compose and env-var relationships; packages inside monorepos (0.2).
  3. ✅ Deep per-repo queries via graphify (0.3).
  4. ✅ Efficiency: 3x faster scans, faster and more reliable CI, cairn doctor, shell completion, and "used by" on INDEX lines (0.4).
  5. ✅ Real-world reach (service DNS, deploy repos, RabbitMQ) and benchmarks on real open-source workspaces with significance testing (0.5).
  6. More harnesses in the benchmark (Codex), and more ecosystems.

Contributing

Issues and pull requests are welcome. See CONTRIBUTING.md for setup and checks, and the changelog for what's new. By participating you agree to the code of conduct.

License

cairn is licensed under the Apache License 2.0: use it, modify it, and ship it, commercially too. Keep the license and the NOTICE file with any redistribution. Full text: LICENSE.

Runtime dependencies and their licenses:

PackageLicense
typerMIT
pydanticMIT
PyYAMLMIT
pathspecMPL-2.0 (used unmodified as a library)
mcpMIT
ai-agents
claude-code
cli
coding-agents
developer-tools
knowledge-graph
mcp
microservices
monorepo
python