Several Repositories¶
A project usually spans several repositories: infrastructure as code, a service, a data pipeline, sometimes an API and a demo app. The kit plans it once, in one of them, and lands each task in the repository its change belongs to. Nothing is copied between repositories, so there's nothing to keep in sync by hand.
Lay the clones out side by side¶
Clone every repository of the project side by side, in one directory named after the project, each clone named after its repository:
shop/ one directory per project
├── shop-api/ planning repository: delivery/, and the kit
├── shop-pipeline/
├── shop-iac/
└── lanes/
└── shop-api/ named after the planning repository, where the launcher runs
├── p-001/ worktree of shop-pipeline, branch task/p-001
└── i-001/ worktree of shop-iac, branch task/i-001
lanes/ sits beside the clones, not inside any of them, and holds one
directory per project, not one per repository: every worktree goes under the
planning repository's name, whichever repository it belongs to. What reaches
the forge is the lane's branch, pushed to its own repository.
flowchart LR
subgraph plan["shop-api, planning repository"]
T[("delivery/tasks/*.md")]
L["launch-lanes.sh"]
end
subgraph pipe["shop-pipeline"]
C2[("origin<br/>task/* claims")]
end
subgraph iac["shop-iac"]
C3[("origin<br/>task/* claims")]
end
subgraph lanes["shop/lanes/shop-api, outside every repository, not in git"]
W2["p-001<br/>worktree of shop-pipeline"]
W3["i-001<br/>worktree of shop-iac"]
end
T --> L
L -->|create-only push| C2 & C3
L -->|on a won claim| W2 & W3
W2 & W3 -.->|"reads task, contracts<br/>(--add-dir)"| T
W2 -->|push task/p-001,<br/>draft PR| C2
W3 -->|push task/i-001,<br/>draft PR| C3
The plan is read in one place, and every lane runs in one place; claims, branches and pull requests happen in the repository each task changes.
What lives where¶
- The planning repository holds
delivery/(requirements, decisions, contracts, task files) and is the only one the kit is installed in. Pick the repository whose lifecycle is the project's, usually the main code repository. When three or more repositories share the work evenly, a small dedicated<project>-hubrepository keeps any one of them from becoming special. - A task names where it lands with
repo: shop-iac, the directory name of the clone. Its claim, branch, worktree and pull request are all in that repository; its task file stays in the planning one. A task withoutrepolands in the planning repository, so a single-repository project changes nothing. - Dependencies cross repositories. A task in the pipeline repository can
wait for the API contract task to merge;
status.shreads every repository the task set names and derives state from each one's branches and pull requests. - Decisions about the project live in the planning repository. A decision confined to one repository's internals lives in that repository's own record.
- Contracts are planned in
delivery/contracts/. Once built, the repository that produces an interface owns its as-built form (an OpenAPI document, a schema, a published package), and consumers pin a version of it. This is how a service consumes a shared library: it pins a published version.
Run everything from the planning repository¶
Run every kit command from the planning repository. The validator checks that
each repo has a clone beside it, so a missing clone fails at decomposition,
not hours later when a lane starts on someone else's machine. Each lane runs
in its target repository with read access to the planning repository
(--add-dir), where it finds its task file, the contracts and the decisions.
Every developer needs the same layout. A missing clone makes status.sh stop
and name it, rather than guess at the state of that repository's tasks.