All posts

REPO.md: a file convention for repos that own their truth

A mature repository has a small canon of files waiting at its root.

README.md introduces the project to a person deciding whether to use it. CONTRIBUTING.md explains the path from a proposed change to an accepted one. AGENTS.md or CLAUDE.md tells an agent how to behave while it works.

None of them answers a simpler operational question: What does this repo own, and what does it only point at?

A human can usually infer the answer after opening enough directories. An agent can search until it finds a likely queue or content folder. A portfolio tool can guess from common names. Each visitor repeats the same tour, and each can reach a different conclusion about which copy is authoritative.

REPO.md fills that open niche. It is a short map at the repository root: what the repo owns, the paths where each thing lives, and the truths it only points at because they are kept elsewhere.

Think of it as the directory in a building lobby. It does not explain how the finance team does its job. It says finance is on the fourth floor. One glance routes a visitor without pretending to replace the rooms, their people, or their working rules.

Nouns here, verbs elsewhere

The boundary with agent instruction files is deliberate:

REPO.md lists nouns; AGENTS.md and CLAUDE.md give verbs.

“The work queue lives in TODO/ and uses RepoTodo” belongs in REPO.md. “Before editing a task, set its status to doing” belongs in AGENTS.md or CLAUDE.md. The first statement inventories a system of record. The second directs behavior.

Keeping the modes separate makes the file useful to everyone, including tools that should discover paths but must not interpret repository-specific prose as commands. README remains descriptive, CONTRIBUTING remains procedural, agent files remain imperative, and REPO.md is declarative.

The whole convention at a glance

Here is the canonical example:

# REPO.md

## In this repo
- **Work queue** — `TODO/` — [repotodo](https://repotodo.com) — `todo ls`
- **Blog** — `content/posts/` — the site renders from here; no CMS copy

## Outside this repo
- **Prices & plans** — Stripe dashboard — code holds only the price IDs
- **Secrets** — 1Password vault `acme` — `.env.example` lists the names

*Format: [repo.md](https://repo.md)*

Each line reads name, location, hint, separated by em dashes. The split between the two headings is mechanical: if it has a path in this repo, it goes under “In this repo”; everything else goes under “Outside this repo”. Anyone applies that test and gets the same answer.

The rule that keeps the file worth reading: a line earns its place only if it settles a question with more than one plausible answer. “Payments: Stripe” teaches nothing; your .env.example already names every vendor. “Prices live in the Stripe dashboard, not in code” is a claim that could be wrong, so it is information: it tells the next visitor where a search ends and which copy wins. A repository may own its content while depending on a CDN to serve it, or own its mail archive while depending on a provider for delivery. Repo-native is about where truth lives, not where all computation happens.

Version 0 stops there. There is no YAML schema, validator, registry of system names, or required metadata. A convention that costs a migration project has missed the point. We can formalize the parts independent tools prove they need after those tools exist.

A contract for tools

The interesting rules apply to software that wants to add itself to the directory. A useful convention can be ruined by a tool that treats a human's file as scratch space.

First, append; do not restructure. A tool may offer its own line, but it must preserve every existing byte it did not add. It does not reorder systems, normalize whitespace, or clean up prose on the way past.

Second, offer; do not insist. Initialization may ask, “Add this system to REPO.md?” The answer can be no. Silent enrollment would turn an ownership declaration into advertising inventory.

Third, remove only your own line, and only when asked. An uninstall has no authority over neighboring systems.

Fourth, read before writing. REPO.md exists so a tool can discover sibling systems and their declared paths instead of assuming defaults. Writing from a stale read, duplicating an existing system, or guessing where a missing heading should go breaks that promise. Ambiguity is a reason to ask, not a license to repair.

These rules apply the manifesto's invariants at file scale: nothing required, no uninvited rewrite, no private state, and a clean exit. The file can remain useful even if no specialized REPO.md tool is ever installed.

A map humans and agents share

When an agent enters an unfamiliar checkout, REPO.md gives it a routing table. A request about the roadmap goes to the declared planning path. A request about posts goes to the content system and its named tool. The agent still reads its instruction file for permissions and process; the inventory only tells it which door to approach.

The same file helps a new teammate, a shell script, or a portfolio dashboard. That shared audience is the point. The repository gets one plain statement of ownership instead of another integration-specific configuration file.

It also creates a useful moment of honesty. Writing the outside list reveals how many facts still have two homes. The product list may be in the repo but also copied into a spreadsheet. The plan may claim to live in markdown while the real decisions happen in a private workspace. Naming that split is not failure. It is the first reliable inventory from which to reduce the truth count.

You can do that inventory now: open the REPO.md convention, copy the ten lines, name the truths already in your repository, list what remains outside, and send the file as a pull request. That is a small, reversible start.