ADR-011: Multi-repo plan format for `/app:build-feature`
Status: Accepted Date: 2026-05-22
Context
Section titled “Context”ADR-007: Cross-repo orchestration — plan-only in v1, execution in v2 made /app:build-feature v1 plan-only. The plan it writes wasn’t given a concrete schema, leaving its producers (the architect agent extended for multi-repo planning) and consumers (compose, promote, and humans running /svc:build-feature per repo) without a contract.
Decision
Section titled “Decision”/app:build-feature writes docs/plan/<slug>.md in the gitops-app repo. The file is a YAML metadata block followed by a Markdown body, mirroring the existing per-service architect plan format.
YAML schema
Section titled “YAML schema”---plan_id: <slug>feature: <one-line description>gitops_app: <org>/<repo> # the gitops-app this plan targetsstatus: draft # draft | in_progress | completed | abandonedrepos: - id: <repo-name> # GitHub repo name (and key in the topo sort) shape: <shape-id> # must be a key in shapes.yml (ADR-015) summary: <one-line> # what this repo's PR does arguments: | # paste-ready prompt for /svc:build-feature <multi-line prompt text> depends_on: [] # list of repo ids this one waits on; empty for roots done: false # flipped to true after the per-repo PR mergesgitops_pin: - service: <repo-name> # which service to pin overlay: dev|staging|prod apply_after: <repo-id> | merge_of_all note: <human-readable rationale>---Lifecycle state machine
Section titled “Lifecycle state machine”The status field tracks the plan’s overall lifecycle. Transitions:
| From | Trigger | To |
|---|---|---|
draft |
/app:build-feature writes the plan |
(initial state) |
draft |
first /svc:build-feature --from-plan invocation against any repo in the plan |
in_progress |
in_progress |
per-repo PR merges → done: true for that repo; all repos[].done: true |
completed |
draft or in_progress |
/app:plans abandon <slug> |
abandoned |
The /app:plans command exposes these states — list shows draft + in_progress by default, --all includes terminal states.
--from-plan consumption pattern
Section titled “--from-plan consumption pattern”/svc:build-feature --from-plan <path-to-plan> [<repo-id>] reads the per-repo block keyed by <repo-id> (defaulting to the current repo resolved via .platform-app.yml from ADR-014: gitops-app composition spec), then runs as if the arguments field had been passed positionally. Side-effect: flips repos[<id>].done: true after the PR merges and advances the plan’s status per the table above.
Markdown body
Section titled “Markdown body”Per-repo sections (one per repos[] entry, ordered by topo-sort):
## <repo-id> — <summary>
**Shape:** `<shape-id>` · **Depends on:** [<repo-id>, ...] · **Run:** `/svc:build-feature` with the `arguments` block above.
<rationale: why this repo needs changes, expected file touches, test concerns>A final section describes the gitops-app PR that pins the resulting image tags.
Topo-sort and execution order
Section titled “Topo-sort and execution order”- The orchestrator sorts
repos[]bydepends_onso that library bumps land before their consumers, frontends land after the APIs they call, etc. - Each level of the topo sort can contain multiple repos that can be worked in parallel (different repos, no shared files by definition).
- v1 plan-only stops here — the user reads the ordered plan and runs
/svc:build-featurein each repo manually, in topo order. - v2 (future ADR) uses the same schema to drive auto-dispatch and PR creation.
Rationale
Section titled “Rationale”- Same shape as the architect’s per-service plan (Agent orchestration §“Parallel implementation via git worktrees”) — agents and humans already know the format.
depends_onis the only structural way to capture “library bumps before consumers”; phase numbers drift, prose narrative gets misread.- Topo-sorted output is paste-ready:
/svc:build-featureinvocations in the printed order produce a working integration.
Consequences
Section titled “Consequences”- The architect agent (or a thin wrapper specifically for app-level planning) emits this format when invoked from a
gitops-apprepo with/app:build-feature. - The
composeandpromoteagents read thegitops_pin[]section to know which overlays to update and when (per theapply_afterfield). - Plan-format validation belongs in a small platform script (
scripts/validate-plan.pyor similar) that CI runs againstdocs/plan/*.mdfiles in gitops-app repos. - v2 execution mode can land without re-designing the format — it just adds an “execute” flag that the orchestrator honors.
References
Section titled “References”- platform-vision.md §6 C2
- ADR-007: Cross-repo orchestration — plan-only in v1, execution in v2 — the plan-only v1 decision this ADR operationalises
- end-to-end-scenario.md Q8/Q9 — the gap this resolves
Amendment (implementation, phase 5)
Section titled “Amendment (implementation, phase 5)”/app:build-featureand/app:plansship in their own marketplace plugin,app(a plugin’s name is the slash-command namespace), enabled by thegitops-apptemplate next togitops,svcandshared.- Validation and lifecycle edits are implemented by
scripts/plan.pyin thegitops-apptemplate (not the platform repo), so every application repo has it:devbox run plan-check(also part ofdevbox run validate, hence CI), andplan.py list|show|start|done|abandon. The platform repo tests it inscripts/test-plan.shagainsttests/fixtures/plans/. /svc:build-feature --from-planruns in a component repo, so it never edits the plan file. The user (or/app:plans) records progress in the gitops-app repo with/app:plans start <slug>and/app:plans done <slug> <repo-id>.
Amendment (ADR-026, app 1.0.0)
Section titled “Amendment (ADR-026, app 1.0.0)”- A plan is made from an approved product spec (
docs/specs/<spec_id>/spec.mdin the gitops-app repo):plan_idis the spec id, the file isdocs/plan/<spec_id>.md, and the new keyspec: <spec_id>links them. - Each repo lists the product criteria it implements,
acs: [AC-<NNN>.<n>, …];plan-checkfails when an active criterion is assigned to no repo, or a withdrawn or unknown one is assigned.argumentsbecomes optional notes for that repo. The contract between repos is the plan body’s## Contractsection (or the spec’sdesign.md). - A component repo consumes its entry with
/svc:spec --from-plan <plan> <repo-id>:cplat spec new --from-planwrites the repo’s own spec (product problem, the assigned criteria verbatim, the contract,parent:), which is approved, planned and built like any other spec./svc:build-feature --from-planno longer exists. - Commands:
/app:spec(product spec),/app:plan(this plan),/app:build(parallel waves, ADR-023) and/app:specsreplace/app:build-feature,/app:run-planand/app:plans. - Plans without
spec:(written before ADR-026) stay valid with a requiredargumentsprompt per repo, and are tracked the same way.
Amendment (spec 045)
Section titled “Amendment (spec 045)”- The gitops-app repo can be an entry of its own plan (
shape: gitops-app). Instead of a prompt it carriesgitops:, a list of operations in execution order:{addon: X},{uses: X, service: S},{expose: S, host: H},{env: {K: V}, service: S}.plan-checkrejects unknown operations, services (not inservices.yamland not a repo of the plan) and addons (not inrender.py’sADDONS). /app:buildbuilds that entry itself withcplat addon addand the newcplat compose set(which changes a service that is already composed), opens its PR first and marks it merge first, and removes any worktree it created.