two-folder-integration — A repository that adopts Majordomus gains two directories and two lines, and can give them back
Adoption is a reconciliation, not a template copy. One typed repository model is discovered once and every consumer reads it. A desired integration is computed from that model, diffed against what the repository actually holds, and applied as the smallest mutation that closes the gap — with everything Majordomus owns living under `.ai/` and `.majordomus/`, and anything outside those two namespaces reduced to a small managed bridge whose ownership is recorded. Running the reconciliation twice changes nothing the second time, and uninstalling gives back exactly what was taken and nothing a person authored.
PLANNED 0 of 23 issues done
- ready
- 1
- blocked
- 22
- active
- 0
- verify
- 0
- done
- 0
- cancelled
- 0
Problem
What Majordomus leaves in a repository it adopts is not decided anywhere; it is the sum of what each subsystem happened to write. Provider files at the root, scripts, configuration beside configuration, generated registries that became inputs, and no record of which of them Majordomus put there. The costs compound in three directions at once. A person cannot tell what is theirs and what is the tool's, so nothing can be safely removed. A second provider means a second copy of the same instructions rather than a second adapter. And every subsystem rediscovers the repository for itself — the CLI, the environment entry, the documentation, the server — so the same facts are read four ways and disagree on the fourth.
Outcome
Adoption is a reconciliation, not a template copy. One typed repository model is discovered once and every consumer reads it. A desired integration is computed from that model, diffed against what the repository actually holds, and applied as the smallest mutation that closes the gap — with everything Majordomus owns living under `.ai/` and `.majordomus/`, and anything outside those two namespaces reduced to a small managed bridge whose ownership is recorded. Running the reconciliation twice changes nothing the second time, and uninstalling gives back exactly what was taken and nothing a person authored.
Current state
`.ai/` and `.majordomus/` both exist and carry real material, and directory contracts are already enforced under `.ai/` (ADR 0011). Against that: provider bootstraps are generated to the root, the environment entry and the CLI each discover the repository their own way, `init` writes what it was written to write rather than what the repository lacks, no ledger records what was placed outside the two namespaces, and nothing can undo an adoption.
Desired state
`majordomus init` on a foreign repository adds `.ai/`, `.majordomus/`, a managed block in `AGENTS.md` and — when direnv is wanted — one stable line in `.envrc`. A second run reports no change. `majordomus uninstall` removes what the ledger records and leaves authored knowledge alone. Adding a valid artifact under a discoverable namespace registers it everywhere, and no list anywhere enumerates what discovery already finds.
Scope
- apps/majordomus-cli/src
- .ai/repo/rules/project
- .ai/repo/adrs
- .majordomus
- share/schemas
- share/skeleton
- docs
- test/cases
- .github/workflows
Out of scope
- A configuration format that restates what the repository can be inspected for
- Rewriting a working subsystem for symmetry rather than for a defect
- Deleting user-authored content on any path, under any flag, without an explicit and separate instruction
- A second worktree convention, a second handover format or a second doctor
- Provider-specific semantics anywhere but an adapter
Acceptance criteria
- Everything Majordomus persists in an adopted repository lives under `.ai/` or `.majordomus/`, and every exception outside them is a bridge recorded in the ownership ledger
- One typed repository model is discovered once per process and every consumer — CLI, environment, API, MCP, Cockpit, documentation, providers, completion — reads that model rather than the filesystem
- The desired integration is a value, the plan is a diff against what is there, and the apply is the smallest mutation that closes it
- A second `init` on a conforming repository produces no change, proved by a test rather than by observation
- `uninstall` reverses every recorded mutation, refuses to touch a bridge a person has edited, and leaves `.ai/` authored content in place by default
- Every generated artifact declares its provenance, its generator and the inputs it derives from, and no generated artifact is read back as canonical input
- No hand-maintained list enumerates artifacts that discovery finds, and adding a valid artifact registers it in every consumer with no second edit
- Environment entry stays local and bounded, with no network and no build, and its cost is measured against a declared budget
- Every invariant above is a project rule with a check that CI executes, each proved by a negative test
Validation
- cargo test --workspace --all-features
- bash test/run.sh
- bin/majordomus doctor
- majordomus generate --check
- majordomus init --dry-run
Evidence required
- footprint_bounded
- one_model
- desired_state_diff
- init_idempotent
- uninstall_reverses
- provenance_declared
- no_manual_registry
- entry_budget
- rules_enforced
Risks
- A reconciler that computes a desired state can also impose one. Everything it would change outside the two namespaces has to be visible in a dry run before it is applied, and a bridge a person edited is a conflict rather than an overwrite.
- One repository model is a bottleneck as well as a spine: every consumer that reads it pays its discovery cost. The cost has to be measured against the environment-entry budget before the model becomes mandatory, not after.
- Migrating this repository's own footprint is the most dangerous phase, because the tool is being changed by the thing it is changing. The migration runs through the reconciler with a dry run first, never through a move script.
- An ownership ledger that drifts from the filesystem is worse than none: it authorises deletions it cannot justify. Every recorded mutation carries the hash it was written with, and a mismatch stops the removal.
- Provider adapters invite provider semantics to leak into the model. The test is whether a new provider is an implementation of one trait, or an edit in six places.
Issues
Each one is an execution contract with its own acceptance criteria, validation command and required evidence. The wave column is the layer of the dependency graph it sits in.
| issue | status | wave | depends on | title |
|---|---|---|---|---|
| I1101 | READY | 0 | — | What this tool leaves behind, established by inspection rather than by memory |
| I1102 | BLOCKED | 1 | I1101 | The two namespaces are a typed contract, not a habit |
| I1103 | BLOCKED | 2 | I1102 | Whether a file is authored or derived is a field, not a naming convention |
| I1104 | BLOCKED | 2 | I1102 | Both namespaces carry their contracts, all the way down |
| I1105 | BLOCKED | 1 | I1101 | The repository is discovered once, into one model |
| I1106 | BLOCKED | 2 | I1105 | The registries are views of the model, not separate discoveries |
| I1107 | BLOCKED | 3 | I1106 | Discovery is deterministic, cached, and its cost is known |
| I1108 | BLOCKED | 3 | I1106 | The desired integration is a value, and the plan is a diff |
| I1109 | BLOCKED | 4 | I1108 | Everything placed outside the two namespaces is recorded, with what it was |
| I1110 | BLOCKED | 5 | I1109 | The second run changes nothing, and a test says so |
| I1111 | BLOCKED | 5 | I1109 | Uninstall gives back exactly what was taken |
| I1112 | BLOCKED | 5 | I1109 | What touches the root is two small blocks, and they never change again |
| I1113 | BLOCKED | 6 | I1112 | Entering a directory is local, bounded and silent about the network |
| I1114 | BLOCKED | 6 | I1112 | A provider is an adapter over the model, and adding one is an implementation |
| I1115 | BLOCKED | 7 | I1114 | Completion comes from the command model, not from a second list |
| I1116 | BLOCKED | 6 | I1110, I1112 | This repository's own footprint moves, through the reconciler |
| I1117 | BLOCKED | 3 | I1106 | The hand-maintained lists are removed, not left running beside the derivation |
| I1118 | BLOCKED | 7 | I1110, I1116 | Every invariant of this milestone is a rule with a check that runs |
| I1119 | BLOCKED | 7 | I1113 | The budgets are measured, and a regression fails rather than being noticed |
| I1120 | BLOCKED | 4 | I1108 | A refusal says what to do about it |
| I1121 | BLOCKED | 6 | I1112 | Every surface shows the integration from the one model |
| I1122 | BLOCKED | 7 | I1110, I1111, I1116, I1121 | A foreign repository, adopted, upgraded and given back |
| I1123 | BLOCKED | 8 | I1104, I1107, I1115, I1117, I1118, I1119, I1120, I1122 | The milestone is accepted on its evidence |
The graph
flowchart LR
I1101["I1101<br/>What this tool leaves behind, established by inspection rather than by memory"]:::ready
I1102["I1102<br/>The two namespaces are a typed contract, not a habit"]:::blocked
I1103["I1103<br/>Whether a file is authored or derived is a field, not a naming convention"]:::blocked
I1104["I1104<br/>Both namespaces carry their contracts, all the way down"]:::blocked
I1105["I1105<br/>The repository is discovered once, into one model"]:::blocked
I1106["I1106<br/>The registries are views of the model, not separate discoveries"]:::blocked
I1107["I1107<br/>Discovery is deterministic, cached, and its cost is known"]:::blocked
I1108["I1108<br/>The desired integration is a value, and the plan is a diff"]:::blocked
I1109["I1109<br/>Everything placed outside the two namespaces is recorded, with what it was"]:::blocked
I1110["I1110<br/>The second run changes nothing, and a test says so"]:::blocked
I1111["I1111<br/>Uninstall gives back exactly what was taken"]:::blocked
I1112["I1112<br/>What touches the root is two small blocks, and they never change again"]:::blocked
I1113["I1113<br/>Entering a directory is local, bounded and silent about the network"]:::blocked
I1114["I1114<br/>A provider is an adapter over the model, and adding one is an implementation"]:::blocked
I1115["I1115<br/>Completion comes from the command model, not from a second list"]:::blocked
I1116["I1116<br/>This repository's own footprint moves, through the reconciler"]:::blocked
I1117["I1117<br/>The hand-maintained lists are removed, not left running beside the derivation"]:::blocked
I1118["I1118<br/>Every invariant of this milestone is a rule with a check that runs"]:::blocked
I1119["I1119<br/>The budgets are measured, and a regression fails rather than being noticed"]:::blocked
I1120["I1120<br/>A refusal says what to do about it"]:::blocked
I1121["I1121<br/>Every surface shows the integration from the one model"]:::blocked
I1122["I1122<br/>A foreign repository, adopted, upgraded and given back"]:::blocked
I1123["I1123<br/>The milestone is accepted on its evidence"]:::blocked
I1101 --> I1102
I1101 --> I1105
I1102 --> I1103
I1102 --> I1104
I1104 --> I1123
I1105 --> I1106
I1106 --> I1107
I1106 --> I1108
I1106 --> I1117
I1107 --> I1123
I1108 --> I1109
I1108 --> I1120
I1109 --> I1110
I1109 --> I1111
I1109 --> I1112
I1110 --> I1116
I1110 --> I1118
I1110 --> I1122
I1111 --> I1122
I1112 --> I1113
I1112 --> I1114
I1112 --> I1116
I1112 --> I1121
I1113 --> I1119
I1114 --> I1115
I1115 --> I1123
I1116 --> I1118
I1116 --> I1122
I1117 --> I1123
I1118 --> I1123
I1119 --> I1123
I1120 --> I1123
I1121 --> I1122
I1122 --> I1123
classDef done stroke:#16a34a,fill:#052e16,stroke-width:2px
classDef active stroke:#2563eb,fill:#eff6ff,stroke-width:2px
classDef verify stroke:#7c3aed,fill:#f5f3ff,stroke-width:2px
classDef ready stroke:#0891b2,fill:#ecfeff,stroke-width:2px
classDef blocked stroke:#b45309,fill:#fffbeb,stroke-width:2px
classDef cancelled stroke:#6b7280,fill:#f9fafb,stroke-width:2pxCanonical record: .ai/repo/project/milestones/two-folder-integration.yaml. Read it back with majordomus plan show two-folder-integration.