Skip to content

ADR-011: Mono-Repo Manifest as a Dedicated File, Auto-Detected by repo init

Field Value
Date 2026-07-23
Status Accepted
Deciders Holger Zahnleiter
Supersedes
Superseded by

Context and Problem Statement

This generator's default philosophy is one repository per artifact (see mono_repo_spec.md, root). Some situations still call for one Git repository hosting several artifacts — e.g. an electronics project pairing PCB production files with firmware, or a service bundled with its frontend for pragmatic reasons. CLI flags describe exactly one artifact and cannot express a list of them, so a YAML-based manifest is needed for the mono-repo case.

Today, a single artifact is described by RepoConfig (internal/repo_config.go), persisted as .repo.yaml in the artifact's own directory: written by repo init when flags are used, then read by both repo init (when flags are absent) and repo update. Introducing multi-artifact generation raises two questions: how the new list-of-artifacts format relates to this existing per-artifact format, and how the CLI triggers it.

Decision Drivers

  • Backward compatibility: existing single-repo behavior (.repo.yaml shape, repo init/repo update semantics) must not change.
  • Minimal blast radius: reuse the existing capability registry (internal/templating/capability/) and generation pipeline unchanged; mono-repo support should be an outer loop, not a parallel generation mechanism.
  • No new ambiguity: a single file must not carry two structurally different meanings depending on which keys happen to be present.
  • Consistency with existing CLI conventions: repo init already auto-detects .repo.yaml when --name is absent; the mono-repo case should follow the same pattern rather than introduce a parallel one.

Considered Options

  1. Reuse .repo.yaml for both cases, discriminated by the presence of a repos: key
  2. A new, dedicated manifest file .mono-repo.yaml at the mono-repo root, with each generated sub-repo still receiving its own ordinary .repo.yaml ← chosen
  3. A new dedicated CLI subcommand (e.g. repo init-mono) instead of extending repo init's auto-detection ← rejected in favor of extending repo init

Decision Outcome

Chosen option: Option 2 — a dedicated .mono-repo.yaml manifest, detected by the existing repo init command.

.mono-repo.yaml is a header (name, copyrightStatement, ci) plus a repos: list. Each list entry has the same shape as RepoConfig (language, type, name, description.brief/detailed, artifact.name/module/registry), plus a new authors field. It is, per the initiative's own framing, "a container for" the existing single-repo format — each entry converts to a ProjectDescriptor via the same RepoConfig.ToProjectDescriptor logic already in use today.

repo init's existing rule — "no --name flag ⇒ read a config file from --targetpath" — gets one more file checked before falling back to .repo.yaml: .mono-repo.yaml. If found, the generator, for each entry:

  1. Validates the entry exactly as a single-repo spec is validated today (language, type, CI, name, description — see business rules below).
  2. Refuses if <targetpath>/<artifact.name> already exists (mirrors the existing single-repo GEN-S03 guard in cmd/init.go, applied per entry).
  3. Builds a ProjectDescriptor and runs the existing templating.NewTemplatingGenerator(...).PopulateRepository() into that subdirectory — unchanged from single-repo generation.
  4. Writes a normal .repo.yaml into that subdirectory, so repo update keeps working per sub-repo exactly as it does today.

This means internal/templating/capability/* needs no changes at all — mono-repo support is a new outer loop around the existing single-repo pipeline, not a new generation mechanism.

copyrightStatement and authors are accepted and validated for shape (present or absent) but not yet rendered into any generated file. Rendering them into file header comments is a distinct future capability (candidate design recorded in mono_repo_spec.md) — every file-writing Action in the capability registry would need to consult it, which is a larger, separate change deliberately deferred.

.mono-repo.yaml is write-once; each .repo.yaml is independently authoritative afterward

.mono-repo.yaml is never modified by generation. It stays exactly as authored — a declarative snapshot of generation intent, not a synchronized twin of the .repo.yaml files it produces. After repo init runs, each <artifact.name>/.repo.yaml becomes the sole, independent source of truth for that artifact, exactly as for a standalone repo today: repo update already only ever reads a directory's own .repo.yaml, never a parent manifest, so there is no runtime consistency hazard between the two files — only a one-time duplication of values at generation time, comparable to a commit message summarizing a diff rather than staying mechanically in sync with it.

Two alternatives were considered and rejected:

  • Rewrite generated entries into references (e.g. repo: ./flight-booking/.repo.yaml) once their subdirectory exists. Rejected: makes the entry schema polymorphic (detail-struct or reference), which is exactly the kind of "invalid state representable" problem this repo's own ADR-007 warns against, and destroys the manifest's main value — a one-page overview of the mono-repo's artifacts, readable without opening every subdirectory.
  • Make the manifest the perpetual master, regenerating every .repo.yaml from it on each mono-repo-level update. Rejected: would silently discard a direct edit made in one sub-repo's .repo.yaml, violating this project's own user_experience.md rule ("never discard user data without explicit confirmation").

Positive Consequences

  • Zero changes to existing single-repo code paths, structs, or file formats — mono-repo support is purely additive.
  • Zero changes to the capability registry; multi-artifact generation is "run the existing generator N times," not a new dispatch mechanism.
  • Follows the CLI's existing "flags absent ⇒ read config file" convention instead of introducing a second, differently-shaped entry point.
  • Each sub-repo remains independently updatable via repo update, unmodified.

Negative Consequences / Risks

  • Two YAML file formats now exist (.repo.yaml, .mono-repo.yaml) instead of one — mitigated by each entry in .mono-repo.yaml being structurally a superset of RepoConfig, so there is only one mental model to learn, not two.
  • repo init's auto-detection now checks two candidate files instead of one — a minor addition to an already-existing code path, not a new one.

Pros and Cons of the Options

Option 1 — Same file, discriminated by repos: key

  • Pro: one filename to document and remember.
  • Con: .repo.yaml's documented contract ("written by repo init, read by repo update, describes one artifact") would silently change meaning based on file shape — a human or script inspecting the file cannot know which contract applies without parsing it.
  • Con: repo update's single-repo read path would need a shape check on every invocation to rule out having been pointed at a mono-repo header by mistake.

Option 2 — Dedicated .mono-repo.yaml (chosen)

  • Pro: .repo.yaml's contract never changes; existing code, tests, and docs describing it stay valid verbatim.
  • Pro: the two file formats are trivially distinguishable by name alone.
  • Con: introduces a second manifest filename to document.

Option 3 — New subcommand (e.g. repo init-mono)

  • Pro: a clearly separate command surface in --help and cli.yaml.
  • Con: duplicates flag/validation plumbing (--targetpath, error messages) that repo init already has, for what is otherwise the same operation at a different granularity.
  • Con: breaks the established "no flags ⇒ read config file" convention users already know from single-repo repo init.
  • mono_repo_spec.md (root) — initiative notes, including the deferred header-comment rendering design for copyrightStatement/authors.
  • internal/repo_config.goRepoConfig, the per-artifact shape each manifest entry mirrors.
  • cmd/init.go — existing initFromCLIArgs/initFromRepoConfig dispatch to be extended with a .mono-repo.yaml check.
  • specs/domain/use_cases/UC005-init_mono_repo.md and specs/domain/features/UC005-init_mono_repo.feature — behavior specification.
  • ADR-007 (ADR-007-identity_vs_capability_based_generation.md) — the capability registry this decision deliberately leaves unchanged.