ADR-0024 — A project can span several repositories
A project made of several repositories lists them in its catalogue, with one primary, and gives each its own folder in each track.
- Status: Approved
- Date: 2026-11-01
- Deciders: @puncoz
- Approved by: @puncoz
- Approved on: 2026-11-01
- Supersedes: ADR-0013 decision 4, its last sentence only ("A project that ships releases may use lines like a package"); ADR-0010 decision 2, for projects with several repositories (who edits what inside the one folder)
- Superseded by: —
- Design documents:
../app-flow/multi-repository-projects.mdx,../prd/multi-repository-projects.mdx,../state/package-version-lifecycle.mdx,../architecture/system.mdx,PROJECT_AGENTS.md
Context
A project is often more than one repository: a frontend, a backend and an API, or any other split. Today the site assumes one repository per entry:
- One folder, one repository. ADR-0010 gives each project one folder,
content/<project-slug>/, written by the repository that includes this one as a submodule, and that repository edits only its own folder. Several repositories writing one folder would all edit the samemeta.json, overview and track files. - One repository link.
catalog.repositoryis a single optional URL (src/lib/catalog-schema.ts); the planet header shows it as one Repository link (src/components/docs/package-hero.tsx). - Project versions. ADR-0013 lets a project version its Use track like a package, with any
number of lines, older (
legacy) ones included. The build checks lines the same way for both kinds (src/lib/catalog-plugin.ts).
No project is documented yet, so nothing has to move. The owner decided on 2026-11-01:
- one entry per project, with a folder per repository inside each track;
- a project has at most a
devline and one released line, for the whole project, and this holds for every project; several versions are for packages only; - one repository is primary and owns the shared files.
Decision
-
The catalogue lists a project's repositories. A project with more than one repository sets
catalog.repositoriesinstead ofcatalog.repository:"catalog": { "kind": "project", "visibility": "public", "repositories": [ { "id": "frontend", "name": "Frontend", "url": "https://github.com/example/acme-web", "primary": true }, { "id": "backend", "name": "Backend", "url": "https://github.com/example/acme-server" }, { "id": "api", "name": "API" } ] }idis the repository's folder name: lowercase letters, digits and hyphens, unique in the list, and notdev,latest,adror the URL segment of one of the project's lines.nameis what readers see; without it theidis shown.urlis an optionalhttpsURL to the repository.- Exactly one repository has
primary: true. - The list holds two or more repositories, only on
kind: "project", and never together withrepository. A one-repository project keepsrepository, as today. Packages are one repository each.
-
Each repository has its own folder in each track. Its pages go in a folder named its
id:use/<id>/andmaintain/<id>/. Either may be missing; a repository with nothing for readers has nouse/<id>/. Pages about the project as a whole (an architecture that spans the repositories, how they talk to each other) sit in the track beside those folders, as pages or folders of their own. In the sidebar a repository folder is an orbit station like any other, titled with the repository'snameunless itsmeta.jsonsets a title.content/acme/ ├── meta.json # catalog.repositories ├── index.mdx # the project overview ├── use/ │ ├── frontend/ │ ├── backend/ │ └── api/ └── maintain/ ├── architecture.mdx # spans the repositories ├── frontend/ ├── backend/ └── api/ -
Projects have at most two lines, for the whole project. For every project, one repository or several:
- Unversioned by default:
use/holds pages (and, with several repositories, the repository folders) directly. - A project that ships releases has
use/dev/(channel: "dev") and at most one released line (channel: "latest"). The build fails on alegacyline in a project. Packages keep every rule of ADR-0013 and ADR-0015, older lines included. - With several repositories, each line holds the repository folders:
use/dev/<id>/anduse/<line>/<id>/. A release copies the whole ofuse/dev/to the new released line and removes the previous one; a link to the removed line redirects to the same path in the new one (ADR-0015). - Maintain is not versioned (ADR-0013).
- Unversioned by default:
-
The primary repository owns the shared files. In a project with several repositories:
- The primary repository edits the root
meta.json,index.mdx,use/meta.json,maintain/meta.json, every line'smeta.json, every page outside a repository folder, and releases (decision 3). - Every other repository edits only its own folders:
use/<id>/(in each line, when the project has lines) andmaintain/<id>/, and its diagrams, nameddiagrams/<project-slug>/<id>-<slug>.jsonand.html(ADR-0022). - A repository that needs a change outside its folders asks the primary repository's maintainers or the owner. The slug, the list of repositories and which one is primary are agreed with the owner.
- The build cannot know which repository wrote a file;
PROJECT_AGENTS.mdstates the rule and each repository's agent instructions name itsid.
- The primary repository edits the root
-
Readers see the repositories.
- Planet header: the single Repository link becomes one link per repository, primary
first, each with the repository's name (Frontend ↗, Backend ↗); a repository without
a
urlis named without a link. The fact sheet shows Repositories: Frontend, Backend, API. - Home page: the planet card's kind line adds the count, Project · 3 repositories.
- Sidebar: repository folders are stations (decision 2), under ADR-0023's one open station at a time.
- Search records,
llms.txtand per-page Markdown are unchanged: a page in a repository folder is a page of the project, and its breadcrumb names the folder.
- Planet header: the single Repository link becomes one link per repository, primary
first, each with the repository's name (Frontend ↗, Backend ↗); a repository without
a
Options considered
| Concern | Option | Pros | Cons | Verdict |
|---|---|---|---|---|
| Layout | A — one entry, a folder per repository inside each track | One overview and one planet per project; repository folders are ordinary stations; smallest change to the one shape of ADR-0015 | Shared files need an owner (decision 4) | Chosen (owner) |
| Layout | B — one entry with a repository switch: each repository its own Use and Maintain | Each repository fully independent | A new navigation level beside the track switch and the moon picker; departs from ADR-0015's one shape | Rejected (owner) |
| Layout | C — separate entries linked by a group | No shared files at all | No project overview; one project shows as several planets | Rejected (owner) |
| Versions | A — dev and one released line, for the whole project, for every project | One rule for projects; one moon picker per project | Repositories that release separately can't say so; a project's older release is not kept | Chosen (owner) |
| Versions | B — lines per repository | Independent releases | A moon picker per station; much more to build and check | Rejected (owner) |
| Versions | C — as today: projects version like packages | No change | Older project lines, which the owner does not want | Rejected (owner) |
| Shared files | A — a primary repository owns them | Clear ownership; no two repositories edit one file | The others depend on it for the overview | Chosen (owner) |
| Shared files | B — any repository edits them | Flexible | Two repositories can change the overview in different directions | Rejected |
| Shared files | C — only the owner, in this repository | Strict | Every overview change waits for the owner | Rejected |
| Catalogue | A — repositories beside repository, never both | A one-repository project is unchanged | Two fields for one idea | Chosen |
| Catalogue | B — only repositories, for every entry | One field | Every existing entry, and packages, change for nothing | Rejected |
Consequences
Positive
- A project of several repositories reads as one project: one overview, one planet, one search scope, with each repository's pages in its own station.
- Each repository's agent has a folder it alone edits, so their changes don't collide.
- One version rule for projects; the moon picker for a project shows at most
devand the released line.
Negative / accepted trade-offs
- The primary repository is a bottleneck for the overview, the tracks' order and releases.
- A project's previous release is not kept on the site once a new one is released.
- Ownership inside the folder is a convention the build cannot check.
- A repository
idcan't be named like a line (dev,latest) oradr.
Content invariants
- §6.1 Private stays private. Unchanged:
visibilitymust bepublicfor the whole project, so every repository's pages are public. A project whose repositories are partly private documents only what is public, or nothing, until the private-documentation ADR. - §6.2 Checked against the code. Each repository's pages are checked against that
repository's source; the
urlin the catalogue is the repository's own. - §6.3 Every package page says which version it documents. Unchanged for packages; a project's line states its version as today.
- §6.4, §6.5. Unchanged.
Security considerations
- New data: repository names and
httpsURLs in the catalogue, all public. The schema refuses a non-httpsURL, as it doesrepositorytoday; links open withrel="noreferrer noopener". - No private repository by accident. A repository listed in a public project is named on
the site; listing a private repository's URL publishes its name and location.
PROJECT_AGENTS.mdsays to list a private repository withouturl, or not at all, and to ask the owner. - Several writers, one folder. More repositories can now write into one project's folder. The rules of ADR-0010 still apply to each: edit only, a human commits, nothing private. The owner reviews every change before committing; ownership (decision 4) narrows what each repository should touch.
- Nothing new leaves the system: search records gain no field.
Implementation notes
-
src/lib/catalog-schema.ts:repositories(id, name, url, primary) with its checks: projects only, two or more, unique ids, reserved ids,https, exactly one primary, not withrepository. -
src/lib/catalog-plugin.ts: nolegacyline in a project, at most one released line; a repository id that is a line's segment fails; a repository folder's title defaults to the repository'sname. -
src/lib/catalog.ts: entries carryrepositories(primary first) for the planet header and the home card. -
src/components/docs/package-hero.tsx: a link per repository and the Repositories fact;src/components/home/planet-cards.tsx: the repository count. - Tests (ADR-0007): the schema (a valid list; one repository; no primary, two primaries;
a duplicate, reserved or upper-case id; an
httpURL;repositorieson a package; withrepository), the plugin (a project withdevand a released line passes; alegacyline in a project fails; a package keeps its older lines; a repository folder titled from its name; an id clashing with a line segment), the planet header (links, primary first, a repository without a URL) and the home card count. - Checked in Chrome on localhost with a test project in a scratch copy of
content/, never committed: invented names only. Done with a temporarycontent/acme/(three repositories,devand a latest line), deleted afterwards: the header links and the unlinked API, the Repositories fact, a station per repository titled from the catalogue, ameta.jsontitle winning over the name, and shared Maintain pages above the stations. -
bun run test,bun run types:checkandbunx next buildpass;bun run lintis reported while it stays blocked.
Docs to update on implementation
-
../app-flow/multi-repository-projects.mdxand../prd/multi-repository-projects.mdx: Current. -
../state/package-version-lifecycle.mdx: a project's lines (no Legacy state). -
../architecture/system.mdx: the catalogue's repositories. -
ADR-0013andADR-0010: Superseded by: this ADR, for the parts named above. -
PROJECT_AGENTS.md: §1's snippet names the repository'sidand whether it is primary; §3 the folders, the catalogue'srepositories, project lines and who edits what. -
AGENTS.md§1 (one shape for every entry) and §10 (the repository map);README.mdwhere it describes an entry's folder. -
./index.mdx: status.
ADR-0023 — Sidebar stations open one at a time
At most one sidebar station is open at each level, so opening one closes the one beside it and the sidebar stays short.
ADR-0025 — The overview has the same sidebar as its tracks
An entry's overview shows the sidebar of its default track, and every track lists Overview as its first item.