Skip to content

majordomus archive

Take a snapshot of the tracked tree that can be read, or checked, somewhere else.

Take a snapshot of the tracked tree that can be read, or checked, somewhere else.

  • generated-output-mutating
  • system
  • --json

The file set is the git index, so nothing untracked — no build output, no cache, no .ai/local/ — can travel. What a profile drops beyond that is declared in share/archive.yaml; a profile that drops the derived paths reads .gitattributes rather than keeping a second list of generated paths. Every entry's mode is taken from the index and written into the archive's manifest, and the restore script inside the archive puts the modes back and builds a local git index, because a copy whose scripts are not executable fails checks for reasons of its own.

Syntax

majordomus archive [<profile>] [--out <path>] [--format zip|tar.gz] [--dry-run] [--force] [--json] | majordomus archive --list [--json]

Lifecycle

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

Other commands at this stage:doctor, watch, history, search, doctrine, version, knowledge, rules, bench.

Reads

  • share/archive.yaml
  • .ai/repo/archive.yaml
  • .gitattributes

Writes

  • tmp/archives/

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

A snapshot that can be read, or checked, somewhere else

A repository leaves the machine more often than it looks: to a model that will read all of it, to a reviewer who cannot clone, to an auditor who must run the gates on a copy. What travels is the git index and only the git index, so nothing untracked can escape; what is dropped beyond that is a profile in share/archive.yaml, and a profile that drops the generated projections reads .gitattributes rather than keeping a second list of them.

What snapshots there are, and which is the default

layer installed
yes

Given this repository

# A repository with Majordomus installed and projections generated, and one commit of work.
"$MJ" init >/dev/null
"$MJ" update >/dev/null
mkdir -p lib docs
echo a > lib/a
echo d > docs/d
git add . && git commit -qm base

Run

$ majordomus archive --list

Output contains

  • context
  • \(default\)
  • audit
  • governance

exit 0

The profiles are declarations, not flags: each one names a different reader, and the summary says what that reader is owed. A repository may add its own in .ai/repo/archive.yaml without changing the tool.

What would travel, and why the rest would not

layer installed
yes

Given this repository

# A repository with Majordomus installed and projections generated, and one commit of work.
"$MJ" init >/dev/null
"$MJ" update >/dev/null
mkdir -p lib docs
echo a > lib/a
echo d > docs/d
git add . && git commit -qm base

Run

$ majordomus archive --dry-run

Output contains

  • profile context
  • tracked file
  • nothing written

exit 0

The count of tracked files is stated beside the count archived, so a selection that quietly matched nothing cannot look like a selection that matched everything. Every file left out is attributed to the rule that left it out.

Written, and then read back

layer installed
yes

Given this repository

# A repository with Majordomus installed and projections generated, and one commit of work.
"$MJ" init >/dev/null
"$MJ" update >/dev/null
mkdir -p lib docs
echo a > lib/a
echo d > docs/d
git add . && git commit -qm base

Run

$ majordomus archive --format tar.gz

Output contains

  • OK archive
  • read back
  • wrote tmp/archives/

exit 0

The archive is opened again before the command returns. The entry count must match what was staged, and executables that went in must come back out — the failure this command exists to answer is a container that silently discarded the file modes.

Asking for a snapshot nobody declared

layer installed
yes

Given this repository

# A repository with Majordomus installed and projections generated, and one commit of work.
"$MJ" init >/dev/null
"$MJ" update >/dev/null
mkdir -p lib docs
echo a > lib/a
echo d > docs/d
git add . && git commit -qm base

Run

$ majordomus archive nosuch

Output contains

  • no profile
  • archive --list

exit 12

Exit 12 is MISSING_ARTIFACT, and the message names the command that lists what does exist rather than listing it here.

Reference

Take a snapshot of the tracked tree that can be read, or checked, somewhere else.

A repository leaves the machine more often than it looks: to a language model that will read all of it at once, to a reviewer who cannot clone, to an auditor who has to run the gates on a copy. Those were being done by hand, differently each time, and the hand-made version got two things wrong that only surface at the far end.

What travels is the git index and only the git index. Nothing untracked is ever archived — not the build output, not the caches, and not .ai/local/, which is this checkout's own state and is never shared. That is one rule instead of a list of exclusions, and it is what makes the command safe to point at a repository nobody has read.

What is left out beyond that is declared, not coded. share/archive.yaml holds the profiles; a repository may add or replace one in an archive.yaml of its own under .ai/repo/. A profile that drops the derived paths reads .gitattributes — every projection in this repository is already marked merge=derived there, for a different reason — rather than keeping a second list of generated paths, which is the duplication .ai/repo/rules/project/commands-are-projections.v1.md refuses.

Modes are carried explicitly. zip -X strips the extra fields that hold the Unix mode, and the unpacked tree then has no executable in it: every script fails to run, and at the far end that is indistinguishable from the repository being broken. So the mode of each entry is read from the index — the authority, not the working tree — written into _ARCHIVE/MANIFEST.txt, and _ARCHIVE/restore.sh inside the archive puts the modes back and creates a local git index, because the checks here enumerate the repository with git ls-files and without an index they examine nothing and report that nothing is wrong.

Every archive is read back before the command returns: the entry count must match what was staged, and if executables went in and none came out, that is a failure rather than a surprise for the recipient.

Reads: share/archive.yaml, an archive.yaml under .ai/repo/ when the repository has one, .gitattributes, and the git index. Writes: tmp/archives/ — or wherever --out says.

The profiles shipped:

ProfileFor
contextA model that will read the repository. Every tracked source, without the projections generated from it.
auditRunning the repository's own checks on a copy. Every tracked file, nothing dropped, modes and a git index restored.
governanceThe operating contract alone: .ai/, docs/, AGENTS.md, CLAUDE.md, and no code.
$ majordomus archive --dry-run
archive: profile context — 2299 of 2757 tracked file(s), 12919 KB of content
         left out: 10 (binary)
         left out: 443 (derived)
         left out: 5 (excluded)
         nothing written (--dry-run)

$ majordomus archive
OK   archive     prismatic-majordomus-context-20260911.zip — 2303 entr(ies) read back, 76 of them executable
archive: profile context — 2299 of 2757 tracked file(s), 12919 KB of content
         wrote tmp/archives/prismatic-majordomus-context-20260911.zip (5044 KB)

Behaviour:

  • --list prints the profiles with what each one is for, and marks the default.
  • --dry-run reports the selection and the reason every file was left out, and writes nothing.
  • --out overrides the destination; --format zip|tar.gz overrides the container.
  • An existing output file is refused with 15 unless --force is given, so an archive someone is uploading cannot be replaced underneath them.
  • A path the index names and the working tree no longer has is counted and reported, not silently absent.
  • --json emits one object with the counts, the reasons and the path.

Exit 2 on a usage error, 12 when there is no such profile or zip is not on PATH, 10 when a profile selects none of the tracked files, 15 when the output exists, 13 when the container could not be written or does not read back as what went in.

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