Skip to content

Move a repository from the pre-.ai layout to the layer

See what a migration would move, move it with a backup, and prove the result is a layer the tool reads.

guaranteed Getting started and adoptionbasicmaintainerwrites state

Situation

A repository installed Majordomus before the .ai/ layer existed and keeps its policy under .majordomus/. Every command refuses to read it, and the maintainer wants to know what will move before anything does.

Scenario

setup: legacy-layout
given:
  - 'project data under .majordomus/, the pre-.ai layout, and no manifest'
steps:
  - id: plan-it
    run: ['migrate', '--dry-run']
    note: 'every move named, nothing written'
    expect:
      exit: 0
      stdout_contains: ['^migrate: \.majordomus/ \(pre-\.ai layout\) -> \.ai/', 'move  \.majordomus/policy\.yaml -> \.ai/repo/policy\.yaml']
  - id: do-it
    run: ['migrate']
    note: 'the files move, the old directory is backed up, the manifest is written'
    expect:
      exit: 0
      stdout_contains: ['^migrated: \.ai/ is the layout']
  - id: nothing-left
    run: ['migrate']
    note: 'a second migration says the layer is already there and moves nothing'
    expect:
      exit: 0
      stdout_contains: ['^already migrated: \.ai/manifest\.yaml is present']
then:
  - 'the migration is explicit, dry-runnable and backed up'
  - 'afterwards every command reads the layer and refuses the old path by name'

Outcome

migrate --dry-run lists every move; migrate performs it with a backup and writes the manifest; a second run says there is nothing to migrate, and doctor proves the layer is real.

The scenario

Executed against the real tool, in a repository prepared by legacy-layout, every time this site is generated. What follows is not a description of what should happen; it is what happened.

given

  • project data under .majordomus/, the pre-.ai layout, and no manifest

when

  1. 1majordomus migrate --dry-run
  2. 2majordomus migrate
  3. 3majordomus migrate

then

  • the migration is explicit, dry-runnable and backed up
  • afterwards every command reads the layer and refuses the old path by name

What the tool did

Evidence of the run this page was generated from: pass, 3 step(s). Output is normalised: the repository's path, timestamps, ids and durations are placeholders; nothing else is edited.

Tested by test/cases/94_use_cases.sh, which runs every scenario and refuses a step whose recorded output is not what it produced.

  1. every move named, nothing written

    migrate: .majordomus/ (pre-.ai layout) -> .ai/ (ai-repository/v1)
      move  .majordomus/policy.yaml -> .ai/repo/policy.yaml
      move  .majordomus/profiles/README.md -> .ai/repo/profiles/README.md
      move  .majordomus/profiles/debugging.yaml -> .ai/repo/profiles/debugging.yaml
      move  .majordomus/profiles/deep-work.yaml -> .ai/repo/profiles/deep-work.yaml
      move  .majordomus/profiles/implementation.yaml -> .ai/repo/profiles/implementation.yaml
      move  .majordomus/profiles/routine.yaml -> .ai/repo/profiles/routine.yaml
      move  .majordomus/project/README.md -> .ai/repo/project/README.md
      move  .majordomus/prompts/README.md -> .ai/repo/prompts/README.md
      move  .majordomus/prompts/continue.md -> .ai/repo/prompts/continue.md
      move  .majordomus/prompts/debug.md -> .ai/repo/prompts/debug.md
      move  .majordomus/prompts/handover.md -> .ai/repo/prompts/handover.md
      move  .majordomus/prompts/review.md -> .ai/repo/prompts/review.md
      state .majordomus/state/decisions.md -> .ai/local/state/decisions.md
      state .majordomus/state/ledger.jsonl -> .ai/local/state/ledger.jsonl
      state .majordomus/state/open-questions.md -> .ai/local/state/open-questions.md
      seed  the rest of .ai/ from the skeleton: protocol, manifest, vendored rules, knowledge, workflows
      state backup first: tmp/majordomus-migrate-backup/<utc>/state/, byte for byte; then moved, and taken out of the index
      then  .ai/local/ appended to .gitignore once; majordomus update --force; majordomus doctor
    dry run: nothing written
    • exit 0
    • stdout_contains /^migrate: \.majordomus/ \(pre-\.ai layout\) -> \.ai//
    • stdout_contains /move \.majordomus/policy\.yaml -> \.ai/repo/policy\.yaml/
  2. the files move, the old directory is backed up, the manifest is written

    migrate: .majordomus/ (pre-.ai layout) -> .ai/ (ai-repository/v1)
      move  .majordomus/policy.yaml -> .ai/repo/policy.yaml
      move  .majordomus/profiles/README.md -> .ai/repo/profiles/README.md
      move  .majordomus/profiles/debugging.yaml -> .ai/repo/profiles/debugging.yaml
      move  .majordomus/profiles/deep-work.yaml -> .ai/repo/profiles/deep-work.yaml
      move  .majordomus/profiles/implementation.yaml -> .ai/repo/profiles/implementation.yaml
      move  .majordomus/profiles/routine.yaml -> .ai/repo/profiles/routine.yaml
      move  .majordomus/project/README.md -> .ai/repo/project/README.md
      move  .majordomus/prompts/README.md -> .ai/repo/prompts/README.md
      move  .majordomus/prompts/continue.md -> .ai/repo/prompts/continue.md
      move  .majordomus/prompts/debug.md -> .ai/repo/prompts/debug.md
      move  .majordomus/prompts/handover.md -> .ai/repo/prompts/handover.md
      move  .majordomus/prompts/review.md -> .ai/repo/prompts/review.md
      state .majordomus/state/decisions.md -> .ai/local/state/decisions.md
      state .majordomus/state/ledger.jsonl -> .ai/local/state/ledger.jsonl
      state .majordomus/state/open-questions.md -> .ai/local/state/open-questions.md
      seed  the rest of .ai/ from the skeleton: protocol, manifest, vendored rules, knowledge, workflows
      state backup first: tmp/majordomus-migrate-backup/<utc>/state/, byte for byte; then moved, and taken out of the index
      then  .ai/local/ appended to .gitignore once; majordomus update --force; majordomus doctor
    backup .majordomus/state/ -> tmp/majordomus-migrate-backup/<time>/state/ (byte for byte; verified)
    WARN  backup      tmp/majordomus-migrate-backup/<time> — is not ignored by git; do not commit it  [reproduce: git check-ignore -v tmp/majordomus-migrate-backup/<time>]
    seeded
      .ai/repo/README.md
      .ai/repo/scope.yaml
      .ai/repo/rules/README.md
      .ai/repo/rules/project/README.md
      .ai/repo/rules/vendor/majordomus/
      .ai/repo/knowledge/
      .ai/repo/knowledge/curated/README.md
      .ai/repo/workflows/
      .ai/repo/skills/
      .ai/repo/adrs/
      .ai/repo/sessions/README.md
    moved 12 canonical file(s) into .ai/repo/, 3 state file(s) into .ai/local/state/, dropped 0 derived file(s), backed up and removed 0 provider file(s)
    removed .majordomus/ (empty)
    --- majordomus update --force
    unchanged AGENTS.md
    unchanged CLAUDE.md
    unchanged GEMINI.md
    generated 3 target(s) from policy 1791d4f72c77; each carries its own stamp
    update: exit 0
    --- majordomus doctor
    OK   knowledge   graph — buildable here: 15 node(s), 0 edge(s), compiled into a temporary file and removed; no stored index to go stale
    INFO knowledge   links — 3 link(s) written in prose resolve to nothing in the graph; an author's link may point outside it, so none fails (majordomus knowledge validate lists them)
    OK   knowledge   records — 0 record(s) valid against knowledge/v1; every reference resolves  [reproduce: majordomus knowledge check]
    OK   knowledge   candidates — none await review, cap 40
    OK   layout      .ai/manifest.yaml — schema ai-repository/v1
    OK   layout      .ai/repo/scope.yaml — version 1; 21 in pathspec(s); every key is one the schema declares
    OK   layout      .ai/local/ — ignored, and nothing under it is tracked
    OK   layout      .ai/ — every section the manifest names exists; the layer is readable without the tool
    OK   layout      .ai/repo/prompts — present
    OK   layout      .ai/local/state/handovers — present, 0 record(s)
    OK   layout      .ai/local/state/checkpoints — present, 0 record(s)
    OK   records     ledger.jsonl — every line is a well-formed event
    OK   policy      39 key(s) — every policy value the code reads is declared, with no reader-side default
    OK   bootstrap   3 projection(s) — each points at .ai/README.md, carries no rule of its own and names only rules in force
    INFO catalogue   - — the manifest names no use-cases section; nothing to resolve
    OK   context     .ai/ — 13 context document(s) carry the contract; ids unique, references resolve, no final document superseded, no cycle  [reproduce: majordomus context validate]
    OK   policy      .ai/repo/policy.yaml — parsed, version 1
    OK   profiles    4 files — parsed; default 'implementation' exists
    INFO adr         .ai/repo/adrs/ — no decisions; nothing to validate
    OK   records     decisions.md — every entry is attributable
    INFO project     .ai/repo/project — no canonical project model here; nothing to validate
    OK   command     surface — 30 public command(s), reconciled against the dispatch table  [reproduce: majordomus doctor]
    OK   doctrine    50 of 61 rule(s) carry a validator — validator, dispatch, propagation, test and CI resolve for every one of the 50; the remaining 11 declare no validator and this check does not examine them
    FAIL wiring      doctor-on-commit — hook .git/hooks/pre-commit does not exist  [reproduce: ls -l .git/hooks/pre-commit]
    FAIL wiring      finish-on-push — hook .git/hooks/pre-push does not exist  [reproduce: ls -l .git/hooks/pre-push]
    OK   command     coverage — every public command is exercised and refuted  [reproduce: bash test/run.sh 31_command_coverage]
    OK   resolver    handovers — no record for this worktree and branch (absence, not a stale match)  [reproduce: majordomus handover --resolve]
    OK   lifecycle   activity — 0 episode(s) opened here; too few to judge whether the writers stopped
    OK   knowledge   wiring — the close path and the compaction adapter call the deriver
    OK   knowledge   deriver — no episode has closed here; nothing to judge
    INFO project     .ai/repo/project — no canonical project model here; nothing to validate
    OK   projection  AGENTS.md — content matches its stamp
    OK   projection  CLAUDE.md — content matches its stamp
    OK   projection  GEMINI.md — content matches its stamp
    OK   budget      AGENTS.md — 42 lines, budget 150
    OK   links       AGENTS.md — all references resolve
    OK   counts      AGENTS.md — no hardcoded counts
    OK   context     builder — 21 lines, budget 300  [reproduce: majordomus context]
    OK   capture     share/schemas/majordomus/capture/capture.v1 — 2 file(s) describe majordomus.capture/v1 at the path the identifier derives: the record as JSON Schema, the document as protobuf
    OK   capture     .ai/local/prompts — ignored and untracked
    OK   capture     .ai/local/prompts — no records yet; the archive is empty
    OK   capture     .ai/local/prompts — every prompt is present as all three: the record, the Markdown and the YAML
    OK   prompts     .ai/local/prompts — the archive and every record in it are readable by their owner only
    OK   prompts     .ai/local/prompts — no records yet; the archive is empty
    OK   prompt      4 asset(s) — front matter valid, every token known
    OK   records     open-questions.md — every entry parses
    OK   retention   ledger — 3 lines, cap 5000
    OK   retention   handovers — 0 files, cap 200
    OK   retention   checkpoints — 0 files, cap 500
    OK   retention   sessions — 0 records, cap 500
    INFO project     .ai/repo/project — no canonical project model here; nothing to validate
    OK   rules       .ai/repo/rules/vendor/majordomus — every file matches the manifest (1 (1.0.0-alpha.8))
    OK   rules       61 rule(s) — resolve in one deterministic order; vendored baseline plus project rules, no override — 51 carry a validator, 10 are normative text this tool does not check
    OK   rules       blocking — all 44 blocking rule(s) carry a validator
    OK   schema      rules — 61 rule(s) resolve in one order: the vendored baseline in manifest order, then the project's, as a dependency graph
    OK   schema      share/kinds.yaml — 37 kind(s); every one that carries metadata declares a schema
    OK   schema      share/schemas — 54 schema(s), each valid JSON and each applied — by a kind, through an allow-list the tool reads, by a validator that names it, or as a generated document's contract (15)
    WARN session     .ai/repo/sessions/ — 1 record(s) exist here and are committed nowhere, so they reach no surface: README.md  [reproduce: git add .ai/repo/sessions && majordomus derive]
    OK   records     0 record(s) — every changed_files names work, not generated or local state
    OK   session     .ai/local/session-contexts — ignored and untracked
    OK   session     .ai/local/session-contexts — no working contexts yet; the store is empty
    INFO skill       .ai/repo/skills/ — no skills; nothing to validate
    OK   skill       reachable — no personal command or skill shares a name with a projected skill  [reproduce: majordomus run editors.status]
    INFO use-case    - — no use-cases section in the manifest; nothing to cover
    INFO env         - — bash <version>, git <version>, jq <version>, shellcheck present
    WARN clone       unpushed — commits on no remote: main(3) — work on one disk is invisible to every other session and cannot be integrated  [reproduce: git push -u origin <branch>]
    OK   clone       uncommitted — no linked worktree, so no uncommitted work can sit in one
    INFO clone       trunk — no remote-tracking branch, so this checkout's distance from the remote trunk cannot be measured
    INFO clone       release — no remote-tracking branch, so whether the trunk's landed work is released cannot be measured
    ·    reasoning   <decided by the advisors and the executable of the recording machine>
    ·    layout      - — <judged when the recording machine has built the executable>
    ·    budget      doctor — <timed against the policy budget>
    doctor: 2 failure(s)
    doctor: exit 10
    migrated: .ai/ is the layout; review with git status, then commit the tracked half
    • exit 0
    • stdout_contains /^migrated: \.ai/ is the layout/
  3. a second migration says the layer is already there and moves nothing

    already migrated: .ai/manifest.yaml is present and .majordomus/policy.yaml is not
    • exit 0
    • stdout_contains /^already migrated: \.ai/manifest\.yaml is present/

Why the tool knows this

The chain from this page to the code: the use case names commands, rules and claims; each rule is dispatched from the registry and each claim names the test that proves it; the scenario above executed the commands. Nothing in this chain is prose about intent.

The rules that make it hold

Guarantees exercised

Related use cases

Computed from what they share: claims, rules, commands, category, applications. Nobody maintains this list.

Searches the titles and descriptions of every page this site publishes.