Skip to content

majordomus adr

The repository's architecture decisions as objects: list, show, ask for the next free identity, propose one with provenance, and validate the whole set against this tree and every other ref.

The repository's architecture decisions as objects: list, show, ask for the next free identity, propose one with provenance, and validate the whole set against this tree and every other ref.

  • state-mutating
  • memory
  • --json

propose writes status 'proposed' and refuses --status: accepting a decision is a person editing the field. An identity is allocated above the high-water mark of four sources — this working tree, every sibling worktree as it stands on disk, every ref, and the peer board — and never into a hole, because a hole is an identity taken and withdrawn or taken and not yet written; next asks that survey without writing anything and says what each source claimed. check refuses duplicate identities, a superseded record with no replacement, one-sided supersession, and an identity this tree adds that another ref carries a different decision at.

Syntax

majordomus adr <list|show <id>|next|propose "<title>"|check|affected> [--json]

Lifecycle

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

Other commands at this stage:handover, finish.

Reads

  • .ai/repo/adrs/
  • .ai/repo/knowledge/sources.yaml
  • share/allow/adr.txt

Writes

  • .ai/repo/adrs/
  • .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

Decisions the tool proposes and only a person accepts

An architecture decision is a file under the repository's adrs section: front matter that satisfies the decision schema over a body carrying the context, the decision and its consequences. Nothing registers it — the source class `adr` in the knowledge sources discovers it over the tracked tree, and list, show, check, the doctrine and MCP all read that one catalogue. What the tool writes is always `proposed`, because acceptance is the person's act.

A decision exists because its file does

decision tracked
yes

Given this repository

# Installed, with one decision proposed and committed. Discovery is over the tracked tree,
# so a decision the working copy holds and git does not is not yet part of the layer: the
# commit is what makes the record discoverable, exactly as it is for every other kind.
. "$FIXTURE_SETUP/installed.sh"
"$MJ" adr propose "The registry is the one canonical declaration" --from file:docs/d --tag architecture >/dev/null
git add . && git commit -qm "adr: the registry is the one canonical declaration"

Run

$ majordomus adr list

Output contains

  • adr-0001
  • proposed

exit 0

One line per decision, in discovery order: the identity, the status, the date it reached it, and the title.

What check examined

decision tracked
yes

Given this repository

# Installed, with one decision proposed and committed. Discovery is over the tracked tree,
# so a decision the working copy holds and git does not is not yet part of the layer: the
# commit is what makes the record discoverable, exactly as it is for every other kind.
. "$FIXTURE_SETUP/installed.sh"
"$MJ" adr propose "The registry is the one canonical declaration" --from file:docs/d --tag architecture >/dev/null
git add . && git commit -qm "adr: the registry is the one canonical declaration"

Run

$ majordomus adr check

Output contains

  • examined 1 decision\(s\)
  • every identity unique

exit 0

A clean result states what it examined, so that a pass over an empty directory cannot look like a pass over the set.

The status is not the tool's to choose

decision tracked
yes

Given this repository

# Installed, with one decision proposed and committed. Discovery is over the tracked tree,
# so a decision the working copy holds and git does not is not yet part of the layer: the
# commit is what makes the record discoverable, exactly as it is for every other kind.
. "$FIXTURE_SETUP/installed.sh"
"$MJ" adr propose "The registry is the one canonical declaration" --from file:docs/d --tag architecture >/dev/null
git add . && git commit -qm "adr: the registry is the one canonical declaration"

Run

$ majordomus adr propose A decision that accepts itself --status accepted

Output contains

  • not yours to choose
  • a person editing the record

exit 15

Exit 15 is REFUSED. A tool that can write `accepted` turns its own inference into repository truth, and a reader months later cannot tell which decisions a person actually made.

Asking for one that does not exist

decision tracked
no

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 adr show adr-9999

Output contains

  • adr list

exit 12

Exit 12 is MISSING_ARTIFACT, and the message names the command that lists what does exist.

Which decisions a change set reaches

decision naming a file
yes

Given this repository

# One decision, committed, that names what it put in force. `propose` writes provenance —
# where the record came from — and never `related`, which is the other direction and a
# person's statement: this rule, this file, are what the decision did.
. "$FIXTURE_SETUP/adr-recorded.sh"
adr="$(ls .ai/repo/adrs/0001-*.md)"
sed -i.bak 's|^status: proposed$|status: proposed\
related:\
  - rule:majordomus.adr-integrity\
  - file:docs/d|' "$adr" && rm -f "$adr.bak"
git add . && git commit -qm "adr: name the rule and the file the decision put in force"

Run

$ majordomus adr affected

Output contains

  • no decision names anything this change set touches

exit 0

Read from what the decisions themselves name: a clean tree reaches nothing, and a change to a named path names the decision behind it. Review notes only — whether a decision still holds after the code it governs moved is not the tool's judgement, so the exit code stays 0.

Reference

The repository's architecture decisions as objects: what was decided, why, what it cost, and where the record came from. Under the layer's adrs section.

A decision is a file .ai/repo/adrs/<NNNN>-<slug>.md — YAML front matter satisfying share/schemas/majordomus/adr/adr.v1.schema.json over a body carrying # Context, # Decision and # Consequences. Nothing registers it: the source class adr in .ai/repo/knowledge/sources.yaml discovers it over the tracked tree, which is the same declaration the Rust executable indexes. See SCHEMAS.md for the contract.

majordomus adr list [--status <status>] [--json]      every decision: id, status, date, title
majordomus adr show <id> [--json]                     the path, then the file as written
majordomus adr next [--json]                          the next free identity, and every source surveyed
majordomus adr propose "<title>" [--from <ref>]...    write a new decision, status proposed
                       [--tag <tag>]... [--supersedes <id>]
majordomus adr check [--json]                         validate every decision and every reference
majordomus adr affected [--base <ref>|--staged|--worktree] [--json]   the decisions a change set touches
  • list prints one line per discovered decision, invalid ones included; --status filters to one of proposed, accepted, superseded, rejected.

  • show prints the repository-relative path on the first line and the file below it. An id that is not a decision exits 12 and names adr list.

  • next answers what the next free identity is, and says what it looked at to decide. Four sources, each reporting how many identities it claimed: this working tree, every sibling worktree as it stands on disk (authored and not yet committed is a real claim), every ref — branches, tags, remote-tracking refs and every linked worktree's HEAD, in one git log --all --no-renames --diff-filter=A — and the peer board of the shared server, which is the only source that knows about an identity a session has decided to take and has not written anywhere. A source that could not be reached is reported as unreachable and never dropped silently from the denominator. Allocation is monotonic: one above the highest identity anything has ever claimed, never into a hole. A hole means an identity was taken and withdrawn, or taken and not yet written, and nothing can tell either from a number nobody ever used — 0034 of this repository is on no branch, in no tag and in no working tree, and ADR 0035 cites it. Holes are therefore listed as spent, with whatever cites them named as evidence for the reader. The shell tool has no peer identity of its own, so a session that announced an allocation and then asks is stepped over its own claim; the output says when the high-water mark came from a board claim. propose allocates through the same survey.

  • propose writes status: proposed and nothing else. There is no way to write accepted: --status exits 15 naming the reason, because a tool that can write accepted turns its own inference into repository truth and a later reader cannot tell which decisions a person actually made. The identity is allocated under a lock over the section directory, so two worktrees proposing at the same moment get two identities. A --from reference is <type>:<value> with the type one of decision, session, commit, issue, file, test; a file: or test: path that does not exist exits 2. Referenced evidence makes the record provenance.origin: extracted, and an extracted record with no evidence is refused as an assertion. --supersedes writes both halves of the relation, so the chain is walkable from either end.

  • related is the other half of a record's references, and it is authored rather than written by propose: rule:<id>, claim:<id>, file:<path>, test:<path> — what the decision put in force, as against provenance.derived_from, which is where it came from. Each is validated where its type says the target lives, and the extractor turns it into a graph edge (declares, supports, references, tested_by), so the reverse direction — which decision put this rule in force — is knowledge edges, not a second list somebody keeps in step.

  • check validates every record against the allow-list generated from the schema (no unknown key), schema: adr/v1, the closed status set, an id whose number equals the file-name prefix, the required body sections; and across the set: duplicate identities, a superseded record with no superseded_by, one-sided supersession, and a reference that resolves to nothing. It exits 10 on any failure.

  • check also asks the question no single tree can answer about itself: for every identity this tree adds relative to the base (origin/master, origin/main, master, main, in that order; MJ_ADR_BASE overrides and is then the only candidate), does another ref carry a different document at that identity? A different file name is a competing claim and is refused, naming both documents, the refs that carry the other one, and majordomus adr next; the same file name on two refs is an edit and is resolved by merging. The verdict always states its subject — how many identities were added and what they were measured against — and when the base does not resolve it says the other refs were not surveyed rather than exiting clean with nothing said. doctor runs the same examination: the pre-commit hook prints it on the branch, and CI's core-check refuses it there, rather than at the merge where generate --strict would exclude every claimant and name only the identity. ADR 0053 is the reasoning.

  • affected reads a change set — the working tree against HEAD by default, --staged for the index, --base <ref> for <ref>..HEAD plus the working tree — and names every decision whose own file changed, or whose related names a path the change touches (a reference to a directory covers everything below it). Every item is a WARN review note and the exit code stays 0: whether a decision still holds after the code it governs moved is the judgement a tool may not make. It is the reverse question the graph answers — what decided this file? — asked from the change set instead of from a record.

doctor and watch run the same examination through the doctrine majordomus.adr-integrity. The threshold for recording a decision at all is the rule majordomus.decision-threshold, which a reviewer decides: the tool validates records and reports what a change reaches, and never claims to know that a diff embodied a decision.

$ majordomus adr propose "The registry is the one canonical declaration" --from file:docs/CAPABILITIES.md
proposed: adr-0011  The registry is the one canonical declaration
.ai/repo/adrs/0011-the-registry-is-the-one-canonical-declaration.md
status is "proposed"; accepting it is a person editing that field.

$ majordomus adr check
examined 11 decision(s) in .ai/repo/adrs/
every identity unique, every status known, every reference resolves

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.

Failure modes

Demonstration

Tested by test/cases/34_command_fixtures.sh, which replays the fixture and compares it with what the command prints.

Guarantees this command carries

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