{"slug": "show-hn-cairn-a-map-of-all-your-repos-for-coding-agents", "title": "Show HN: cairn, a map of all your repos for coding agents", "summary": "A developer released cairn, an open-source tool that scans every git repo in a folder once and generates a shared map so coding agents can locate the right repo and file without re-exploring. The tool, installed via the Python package cairnmap (Python 3.11+ and git required), produces a roughly 25-token-per-repo always-loaded index plus per-repo cards capped at 800 tokens, and works with Claude Code, Codex, Gemini CLI, and Cursor on Windows, macOS, and Linux. Each relationship carries evidence with file and line plus a trust level of extracted, inferred, or ambiguous, with ambiguous links hidden until confirmed.", "body_md": "**A map of all your repos for coding agents.** cairn scans every git repo in a folder once, works\nout how they connect, and gives your agent:\n\n- a tiny always-loaded index;\n- a short card per repo.\n\nThe agent knows where things live without re-exploring.\n\nCairns 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.\n\nWorks with **Claude Code, Codex, Gemini CLI, and Cursor**, on **Windows, macOS, and Linux**.\n\nCoding agents work inside one repo. Real work spans many: a web app, an API, a shared types\npackage, a payments service, a reporting job. Say *\"add a tip amount to trips end to end\"* and the\nagent has to:\n\n1. discover that sibling repos exist;\n2. grep through all of them;\n3. read a pile of files;\n4. guess which service owns the `trips` table.\n\nThat costs tokens and turns, and weaker models guess wrong.\n\nHand-maintained \"related repos\" docs help until they go stale. cairn builds that map from the code itself and keeps it fresh:\n\n```\n~/code/                          ~/code/.cairn/\n├── rider-web/        cairn      ├── INDEX.md           ← ~25 tokens per repo, always loaded\n├── trips-svc/        ─────▶     ├── cards/trips-svc.md ← read on demand (≤ 800 tokens)\n├── payments-svc/     scan       ├── workspace.json     ← the full graph\n├── analytics-etl/               └── authored/          ← your summaries and decisions\n└── …\n```\n\nThe **index** is a few lines, loaded into every session through `CLAUDE.md` or an equivalent:\n\n```\n# Workspace repos (cairn)\n- admin-console (@fleetline/admin-console): Next.js back-office for ops staff; queries trips… · nextjs\n- analytics-etl: Nightly SQL reporting jobs over trips, payments, drivers… · python\n- payments-svc (payments): Go service that charges completed trips and records weekly payouts · go\n- trips-svc: FastAPI service owning the trip lifecycle and the trips/trip_events tables · fastapi · used by admin-console\n- ui-kit (@fleetline/ui-kit): Shared React components (Button, Card) · react · used by admin-console\n```\n\n`used by` lists the repos that depend on, call, or reference that one, so a change's blast\nradius is visible before any card is opened.\n\nA **card** is read only when the agent needs that repo:\n\n```\n# trips-svc\n> FastAPI service owning the trip lifecycle and the trips/trip_events tables.\n`./trips-svc` · python, fastapi · HEAD 1fee22d\n\n## Relates\n← infra: references path trips-svc (extracted · infra/docker-compose.dev.yml:3)\n→ admin-console: shares tables trips (inferred · admin-console/lib/db.ts:4)\n→ analytics-etl: shares tables trips (inferred · analytics-etl/jobs/daily_revenue.sql:2)\n→ payments-svc: shares tables trips (inferred · payments-svc/internal/charge/charge.go:3)\n\n## Run\ntest `pytest`\n\n## Layout\napp/ → routes/pages\nmigrations/ → DB migrations\n```\n\nEvery relationship comes with evidence (file and line) and a trust level:\n\n- `extracted` : found directly, such as a package dependency or a path reference;\n- `inferred` : strong signals, such as a table that only one repo creates;\n- `ambiguous` : hidden until you confirm it.\n\nYou need Python 3.11+ and git.\n\n| Tool | Command | \n|---|---|\n| [uv](https://docs.astral.sh/uv/) (recommended) | `uv tool install cairnmap` | \n| pipx | `pipx install cairnmap` | \n| run once, no install | `uvx --from cairnmap cairn init` | \n| pip | `pip install cairnmap` | \n\nThe package is called `cairnmap`; the command is `cairn`.\n\n```\ncd ~/code              # the folder that CONTAINS your repos\ncairn init             # scan, then add the index to ./CLAUDE.md (asks first)\ncairn install all      # optional: Codex, Gemini CLI, Cursor, the /cairn skill, the MCP server\n```\n\nOpen your agent in any repo under that folder. It now knows about every sibling repo.\n\nTo give each repo a good one-line summary, run `/cairn` inside your agent. It writes the summaries\nwith `cairn set-summary` and settles uncertain links. Until then, the index says \"no summary yet\"\nfor that repo. README text is deliberately kept out of always-loaded context.\n\n```\ncairn status               # repos, relationships, unconfirmed links, missing/stale summaries\ncairn refresh              # re-read only repos that changed (fast)\ncairn hooks install        # optional: refresh automatically after each commit/merge\nphp\ncairn annotate-edge \"web->api:shares_db\" --confirm\ncairn annotate-edge \"shop->blog:shares_db\" --reject --why \"different databases\"\ncairn set-summary payments-svc \"Charges completed trips and pays drivers weekly.\" --alias payments\n```\n\nThese decisions live in `.cairn/authored/` and `.cairn/relations.yaml`. They survive every re-scan,\nand you can commit them so your team shares them.\n\n`cairn install <harness>` registers cairn's MCP server. Agents can call these tools:\n\n| Tool | What it answers | \n|---|---|\n| `resolve_repo` | \"Which repo is 'the payments service'?\" | \n| `repo_card` | The card for a repo (re-scanned first if its HEAD moved) | \n| `related` | Everything connected to a repo, with evidence | \n| `find_across` | Which repos expose or use a table, package, or path | \n| `query` | Where inside a repo: `symbol — file:line` from a deep index, else which folders to start in | \n| `refresh` | Update the map now | \n\n| Signal | Examples | \n|---|---|\n| HTTP calls | Next.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 | \n| gRPC | Go, Python, TypeScript and Java servers matched with their client stubs | \n| Service names | Calls addressed to a sibling's service name, as Docker and Kubernetes DNS do: `http://catalogue` ,`http://carts:8080/carts` ,`*.svc.cluster.local` ,`Hostname(\"payment\")` | \n| Pub/sub topics | Kafka, NATS, Redis and RabbitMQ (Spring AMQP) publishers matched with subscribers ( `trip.completed` ) | \n| docker-compose | `depends_on` between services built from (or named after) your repos | \n| Deploy repos | compose, Kubernetes and Helm files that run your repos' images ( `image: acme/catalogue:1.2` ) | \n| Package dependencies | npm ( `workspace:*` , scoped packages), PyPI, Go modules, Cargo | \n| Packages inside monorepos | npm/yarn/pnpm, Cargo, `go.work` and uv workspaces, listed on the card and resolvable by name | \n| Shared database tables | SQL migrations and queries, Prisma, Drizzle, Supabase, MongoDB (Mongoose models, `db.collection(\"x\")` ) | \n| Copies of one app | Repos 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 | \n| Path references | `../trips-svc` in docker-compose, tsconfig, and other config files | \n| Documentation | READMEs and docs that mention a sibling repo | \n| Shared env vars | Specific names read by both sides. These only back up another link; they never make one on their own | \n\nLook-alikes are deliberately ignored:\n\n- health-check routes;\n- calls to other companies' APIs;\n- vague topic names;\n- `.proto` files with no implementer;\n- generic env vars such as `PORT` ;\n- `localhost` , public domains, and URLs in comments;\n- public images (`mongo:3.4` ) and look-alike names (`catalogue-db` is not`catalogue` );\n- a queue that a repo declares but never consumes;\n- the same schema in two copies of one app (copies often use a database each, so no link);\n- Firestore's `db.collection(...)` , which looks like MongoDB's.\n\nEvaluation workspaces in the test suite keep every one of these at precision 1.0.\n\nThe map tells an agent *which* repo to open. A deep index tells it *where inside*: the `query` MCP\ntool answers \"where is login handled?\" with `login() — src/auth.py:12` hits and their neighbours,\ninstead of a list of folders.\n\nDeep indexes are optional and built per repo with [graphify](https://github.com/Graphify-Labs/graphify):\n\n```\nuv tool install 'cairnmap[graphify]'   # or: pip install 'cairnmap[graphify]'\ncairn deep build trips-svc             # one repo (or --all)\ncairn deep status                      # size, build sha, fresh or stale\ncairn refresh --deep                   # after changes: rebuild only the stale indexes\ncairn deep clear trips-svc             # delete it\n```\n\n- graphify always runs `--code-only` : no LLM, no network, and an allowlisted environment, so no\nAPI key reaches it. It writes only to`.cairn/deep/<repo>/` , never into the repo.\n- cairn answers queries itself from the saved graph, offline, so serving needs no graphify.\n- Building is never automatic (the first build of a large repo can take minutes). When an index\nfalls 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.\n\ncairn ships a benchmark harness (`cairn bench`). It runs real tasks through headless Claude Code\nunder five conditions, each in a fresh, isolated copy of a multi-repo workspace:\n\n|  | Condition | \n|---|---|\n| **A** | No map (the agent explores) | \n| **B** | A hand-written \"related repos\" doc | \n| **C** | cairn's index only | \n| **D** | Index + repo cards | \n| **E** | D + cairn's MCP server | \n\nTasks cover:\n\n- **orientation:** \"who owns the trips table?\";\n- **localization:** \"which files change to add a tip amount end to end?\";\n- **cross-repo impact:** \"what breaks if`drivers.license_no` is renamed?\";\n- **control:** questions answerable within one repo.\n\nAnswers are graded deterministically on the files and facts they must name.\n\n**Results** (2026-10-06): 840 runs over four workspaces, two of them real open-source systems:\nSock Shop (9 microservice repos; cairn's newest link types were developed on it) and the\nSupabase JS client family (6 repos, held out: never used to tune cairn). 28 tasks, 3 runs per\ncell, Haiku 4.5 and Sonnet 5.5. A result is called significant only after Holm adjustment.\n\n| Per task, cairn INDEX (C) | Haiku 4.5 | Sonnet 5.5 | \n|---|---|---|\n| Fresh tokens vs no map | **-19%** (significant) | -10% (n.s.) | \n| Cost vs no map | -25% (n.s. after adjustment) | -12% (n.s.) | \n| Cost vs a hand-written doc | -6% (n.s.) | **-12%** (significant) | \n| Cost vs no map, Sock Shop only | -27% (n.s.) | -26% (borderline, adjusted p = 0.055) | \n\nWhat this shows:\n\n- **cairn tends to make cross-repo work cheaper** : fewer fresh tokens for Haiku, and cheaper than\na hand-written related-repos doc for Sonnet. The largest raw savings were on Sock Shop.\n- **It doesn't measurably raise success.** Sonnet answers 99-100% of these tasks in every\ncondition; Haiku rises from 86% to 93% with the MCP server, which isn't significant.\n- **It isn't a win everywhere.** On fleetline, Sonnet cost 7-10% more with cairn than without\n(n.s.); the held-out Supabase effects are small and not significant.\n- **Correction:** our first, smaller run (2026-10-05) reported Haiku reaching 100% with the\nINDEX. With more runs that doesn't replicate (89-92%, the same as no map).\n\nMethods, every table with 95% intervals, paired Wilcoxon tests (raw and Holm-adjusted), the\nregressions, and the held-out results:\n[bench/published/2026-10-06-real-world.md](https://github.com/Moe1177/cairn/blob/main/bench/published/2026-10-06-real-world.md).\nThe first run is kept at\n[2026-10-05-shopverse-fleetline.md](https://github.com/Moe1177/cairn/blob/main/bench/published/2026-10-05-shopverse-fleetline.md).\n\nRun the benchmarks yourself from a source checkout. They use your Claude usage.\n\n```\ncairn bench bench/suites/sockshop --runs 3 --model haiku    # fetches the pinned repos once\ncairn bench bench/suites/shopverse --conditions A,C --tasks gift-message\ncairn bench bench/suites/sockshop --resume bench/results/<stamp>.jsonl   # after a usage limit\n```\n\ncairn 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):\n\n|  | 0.3 | 0.4 | \n|---|---|---|\n| First scan | 156 s | 53 s | \n| Refresh, nothing changed | 15 s | 5.4 s | \n\nMost of a refresh on Windows is git process start-up; Linux and macOS start processes faster.\nReproduce with `uv run python bench/perf_scan.py --out <dir>`.\n\ncairn treats every scanned repo as untrusted input. It:\n\n- **never** modifies your repos, apart from the opt-in hooks and Cursor's`--per-repo` rule (both\nmarked blocks, removed cleanly);\n- **never** opens`.env` files, private keys, or credential files, and never reads through a symlink\nor junction inside a repo;\n- **never** runs code from a repo: its git calls turn off fsmonitor, hooks, and the repo's own\nfilters.\n\nIt also:\n\n- **redacts** common secret formats from every stored snippet, and stores git remotes without\ncredentials;\n- **keeps README text out** of always-loaded context. Repo names that do appear are flattened and\ncapped, so they can't inject instructions or break cairn's blocks.\n\ncairn makes **no network calls and collects no telemetry**. `cairn bench` is the only feature that\nruns another program that does (the `claude` CLI).\n\nSee [SECURITY.md](https://github.com/Moe1177/cairn/blob/main/SECURITY.md) for the threat model and\nhow to report a vulnerability.\n\n| Command | What it does | \n|---|---|\n| `cairn init` | Scan, then offer to add the index to Claude Code | \n| `cairn scan [--full] [--verbose]` | Map every repo under the folder into `.cairn/` | \n| `cairn refresh [--deep]` | Re-read only repos whose HEAD or working tree changed ( `--deep` : also rebuild stale deep indexes) | \n| `cairn deep build REPO…\\|--all\\|--stale [-w PATH]` | Build graphify code indexes so `query` answers with file:line (optional extra) | \n| `cairn deep status` /`cairn deep clear [REPO…]` | List deep indexes (fresh or stale) / delete them | \n| `cairn status` | What cairn knows, unconfirmed links, missing or stale summaries | \n| `cairn annotate-edge KEY --confirm\\|--reject [--why TEXT]` | Settle a relationship | \n| `cairn set-summary REPO TEXT [--alias NAME]` | Save a summary ( `-` reads stdin) | \n| `cairn install <claude\\|codex\\|gemini\\|cursor\\|all>` | Load cairn into an agent harness | \n| `cairn uninstall <name\\|all>` | Remove it again | \n| `cairn hooks install\\|uninstall` | Opt-in git hooks that refresh after commits and merges | \n| `cairn serve` | The MCP server (harnesses start it for you) | \n| `cairn bench SUITE` | Run the benchmark harness | \n| `cairn doctor` | Check git, Python, the map, write access, harnesses and graphify; exits 1 on a failure | \n| `cairn --install-completion` | Tab completion for your shell (bash, zsh, fish, PowerShell) | \n| `cairn --version` | Versions of cairn, Python, the platform, and mcp | \n\n**Exit codes:** `0` means success, `1` an error (one line on stderr), and `2` a usage error.\n\n| Path | What it is | \n|---|---|\n| `<folder>/.cairn/INDEX.md` | One line per repo: name, aliases, summary, stack | \n| `<folder>/.cairn/cards/<repo>.md` | One card per repo | \n| `<folder>/.cairn/workspace.json` | The full graph | \n| `<folder>/.cairn/cache/` ,`logs/` ,`.lock` | Scan cache, last scan log, lock file | \n| `<folder>/.cairn/relations.yaml` ,`authored/` | **Yours:** aliases, manual links, summaries, decisions | \n| `<folder>/CLAUDE.md` | A marked block holding the index ( `cairn install claude` ) | \n| `~/.claude/skills/cairn/` and Claude's user MCP config | `/cairn` skill and MCP server | \n| `~/.codex/AGENTS.md` ,`config.toml` ,`skills/cairn/` | Codex pointer, MCP server, skill | \n| `~/.gemini/GEMINI.md` ,`settings.json` ,`commands/cairn.toml` | Gemini CLI pointer, MCP server, command | \n| `~/.cursor/mcp.json` ,`commands/cairn.md` | Cursor MCP server and command | \n| `<repo>/.git/hooks/post-commit` ,`post-merge` | Only with `cairn hooks install` | \n| `~/.cairn/registry.json` ,`backups/` | Mapped workspaces; one private backup of each config file cairn first edited | \n\ncairn only edits its own key or marked block in other tools' files, and refuses to touch a file it can't parse.\n\n```\ncairn uninstall all          # harness entries, skills, commands, Cursor rules\ncairn hooks uninstall        # if you installed hooks\nuv tool uninstall cairnmap   # or: pipx uninstall cairnmap\n```\n\nThen delete `<folder>/.cairn/` and `~/.cairn/`.\n\n| Symptom | Fix | \n|---|---|\n| Something seems off | Run `cairn doctor` : it checks each dependency and says what to fix | \n| \"No git repos found under …\" | Run cairn from the folder that *contains* your repos | \n| \"… is inside the git repository …\" | Same: run from the parent folder, not inside a repo | \n| \"git not found on PATH\" warning | Install git; without it, remotes, HEAD and caching are off | \n| \"another cairn process is still updating this workspace\" | A scan or hook refresh is running; retry in a moment | \n| An agent says it can't find the workspace | Run `cairn init` in the workspace folder, then restart the agent session | \n| `cairn install codex/gemini/cursor` refuses a config | That file isn't valid TOML/JSON; fix it and re-run (cairn never guesses) | \n| Hooks did nothing | `cairn hooks install` lists the repos it skipped and why | \n\n1. ✅ Core map, precision pass, MCP server, harness integrations, freshness, benchmarks, release hardening (0.1).\n2. ✅ HTTP, gRPC, pub/sub, compose and env-var relationships; packages inside monorepos (0.2).\n3. ✅ Deep per-repo queries via [graphify](https://github.com/Graphify-Labs/graphify) (0.3).\n4. ✅ Efficiency: 3x faster scans, faster and more reliable CI, `cairn doctor` , shell completion,\nand \"used by\" on INDEX lines (0.4).\n5. ✅ Real-world reach (service DNS, deploy repos, RabbitMQ) and benchmarks on real open-source workspaces with significance testing (0.5).\n6. More harnesses in the benchmark (Codex), and more ecosystems.\n\nIssues and pull requests are welcome. See\n[CONTRIBUTING.md](https://github.com/Moe1177/cairn/blob/main/CONTRIBUTING.md) for setup and checks,\nand the [changelog](https://github.com/Moe1177/cairn/blob/main/CHANGELOG.md) for what's new. By\nparticipating you agree to the\n[code of conduct](https://github.com/Moe1177/cairn/blob/main/CODE_OF_CONDUCT.md).\n\ncairn is licensed under the **Apache License 2.0**: use it, modify it, and ship it, commercially\ntoo. Keep the license and the [NOTICE](https://github.com/Moe1177/cairn/blob/main/NOTICE) file\nwith any redistribution. Full text: [LICENSE](https://github.com/Moe1177/cairn/blob/main/LICENSE).\n\nRuntime dependencies and their licenses:\n\n| Package | License | \n|---|---|\n| [typer](https://github.com/fastapi/typer) | MIT | \n| [pydantic](https://github.com/pydantic/pydantic) | MIT | \n| [PyYAML](https://github.com/yaml/pyyaml) | MIT | \n| [pathspec](https://github.com/cpburnz/python-pathspec) | MPL-2.0 (used unmodified as a library) | \n| [mcp](https://github.com/modelcontextprotocol/python-sdk) | MIT |", "url": "https://wpnews.pro/news/show-hn-cairn-a-map-of-all-your-repos-for-coding-agents", "canonical_source": "https://github.com/Moe1177/cairn", "published_at": "2026-10-06 19:54:59+00:00", "updated_at": "2026-10-06 20:20:10.071066+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "ai-tools", "agent-protocols"], "entities": ["cairn", "cairnmap", "Claude Code", "Codex", "Gemini CLI", "Cursor"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/show-hn-cairn-a-map-of-all-your-repos-for-coding-agents", "markdown": "https://wpnews.pro/news/show-hn-cairn-a-map-of-all-your-repos-for-coding-agents.md", "text": "https://wpnews.pro/news/show-hn-cairn-a-map-of-all-your-repos-for-coding-agents.txt", "jsonld": "https://wpnews.pro/news/show-hn-cairn-a-map-of-all-your-repos-for-coding-agents.jsonld"}}