Skip to content

majordomus migrate

Move pre-.ai project data from .majordomus/ into .ai/, explicitly and backed up.

Move pre-.ai project data from .majordomus/ into .ai/, explicitly and backed up.

  • generated-output-mutating
  • system
  • runs before init

Runs on the legacy layout only. It copies the local state aside before anything moves, never deletes a file it does not know, and refuses a directory that holds both project data and a tool installation.

Syntax

majordomus migrate [--dry-run]

Lifecycle

  1. Set up
  2. Begin
  3. Work
  4. Verify
  5. Conclude
  6. Inspect

Other commands at this stage:init, update.

Reads

  • .majordomus/
  • share/skeleton/ai

Writes

  • .ai/
  • tmp/majordomus-migrate-backup/
  • .gitignore
  • .ai/local/state/ledger.jsonl

Exit codes

Only the codes this command can produce. The full contract is on the commands index.

  • 0 ok
  • 2 usage
  • 10 contract unmet
  • 12 missing artifact
  • 13 internal error
  • 15 refused

Leaving the legacy layout, on purpose

migrate moves project data from the pre-.ai layout under .majordomus/ into the portable AI layer. It is explicit, it prints its plan, it copies the local state aside first, and it never deletes a file it does not know.

The plan, and nothing written

--dry-run
yes
legacy layout
yes

Given this repository

# A repository still on the pre-.ai layout: project data under .majordomus/, no .ai/ at all.
# The tool no longer writes that layout, so it is assembled from a fresh installation.
. "$FIXTURE_SETUP/installed.sh"
mkdir -p .majordomus
for s in policy.yaml profiles prompts project; do [ -e ".ai/repo/$s" ] && mv ".ai/repo/$s" ".majordomus/$s"; done
mv .ai/local/state .majordomus/state
rm -rf .ai
sed -i.bak '/^\.ai\/local\/$/d' .gitignore && rm -f .gitignore.bak
git add -A && git commit -qm legacy

Run

$ majordomus migrate --dry-run

Output contains

  • ^migrate: .majordomus/ \(pre-.ai layout\) -> .ai/
  • move .majordomus/policy.yaml -> .ai/repo/policy.yaml

exit 0

Every file and where it goes, before anything moves. A plan that surprises is a plan to read again, not to run.

The move itself

--dry-run
no
legacy layout
yes

Given this repository

# A repository still on the pre-.ai layout: project data under .majordomus/, no .ai/ at all.
# The tool no longer writes that layout, so it is assembled from a fresh installation.
. "$FIXTURE_SETUP/installed.sh"
mkdir -p .majordomus
for s in policy.yaml profiles prompts project; do [ -e ".ai/repo/$s" ] && mv ".ai/repo/$s" ".majordomus/$s"; done
mv .ai/local/state .majordomus/state
rm -rf .ai
sed -i.bak '/^\.ai\/local\/$/d' .gitignore && rm -f .gitignore.bak
git add -A && git commit -qm legacy

Run

$ majordomus migrate

Output contains

  • ^migrated: .ai/ is the layout

exit 0

The tracked half lands under .ai/repo/ and the local half under .ai/local/, which is ignored from then on. doctor runs at the end so the first thing seen on the new layout is its real state.

Nothing to migrate and no layer

--dry-run
no
legacy layout
no

Given this repository

# An empty git repository: no AI layer at all.
git commit -q --allow-empty -m base 2>/dev/null || true

Run

$ majordomus migrate

Output contains

  • nothing to migrate

exit 12

A repository with neither layout is not migrated into existence; it is told to run init.

Reference

Move a repository's project data from the pre-.ai layout under .majordomus/ into the portable AI layer under .ai/, once and explicitly. No ordinary command migrates: check, doctor, start and the rest refuse (12) a legacy layout and name this command.

Markers: legacy is .ai/repo/policy.yaml; new is .ai/manifest.yaml; a .majordomus/bin/majordomus is a tool installation and never project data.

Reads: every file under .majordomus/, the skeleton manifest (the destinations come from it), the tool's templates (to tell an unchanged template from a customised one). Writes: .ai/README.md, .ai/manifest.yaml; the canonical files moved into .ai/repo/ (git mv where tracked, so history follows); .ai/local/state/ moved to .ai/local/state/ and taken out of the index; the rest of the layer seeded from the skeleton without overwriting anything that moved; one .ai/local/ line in .gitignore; a byte-for-byte copy of the state under tmp/majordomus-migrate-backup/<utc>/state/, and of the legacy providers/ under .../providers/, verified and printed, before either is touched; a layout.migrated ledger line; then update --force renders the projections from the tool's thin bootstraps and doctor judges the result, each exit reported on its own line.

Behaviour:

  • --dry-run prints the whole plan, one line per file with its action and destination, and writes nothing. The same table drives the real run.
  • templates/*.md identical to the tool's own are dropped; a customised one moves to .ai/repo/templates/. generated/ is dropped: every projection now carries its own stamp.
  • providers/ — the provider body and the monolithic templates that asked for it — is not carried into .ai/. The body no longer exists anywhere and update renders none, so an old template would leave a literal token in a generated file. The directory is copied to the backup, removed from the index, and reported in one line: the bootstraps are now the tool's thin adapters, a repository override goes under .ai/repo/providers/<provider>.tmpl in the new format, and the body's rules belong under .ai/repo/rules/project/ as rule objects (DOCTRINE.md describes the format).
  • A file under .majordomus/ this version does not know is never deleted. It is reported, and .majordomus/ is removed only when it is empty.
  • Refuses (15) a .majordomus/ that holds both policy.yaml and bin/majordomus, and names the safe manual step; refuses (15) when a destination under .ai/ already exists, listing each one. Nothing is written in either case.
  • A legacy policy that does not parse, or is not version 1, is refused (10) before anything moves: fix it in place first.
  • Idempotent: on a repository already on the .ai layout it says so and exits 0. With neither layout present it exits 12 and names init.
  • The state stops being tracked: its durability is the checkout plus the backup, not the branch. Commit the tracked half after reviewing git status.
$ majordomus migrate --dry-run
migrate: .majordomus/ (pre-.ai layout) -> .ai/ (ai-repository/v1)
  move  .ai/repo/policy.yaml -> .ai/repo/policy.yaml
  move  .ai/repo/profiles/debugging.yaml -> .ai/repo/profiles/debugging.yaml
  state .ai/local/state/current.yaml -> .ai/local/state/current.yaml
  drop  .majordomus/templates/handover.md    (identical to the tool's template)
  ...
dry run: nothing written

Evidence

Every case below runs in CI on Linux and macOS. The site refuses to build if a public command has no behavioural case, no negative case, or no demonstration.

Guarantees this command carries