Skip to content

majordomus adr

The repository's architecture decisions as objects: list, show, propose one with provenance, and validate the whole set.

The repository's architecture decisions as objects: list, show, propose one with provenance, and validate the whole set.

  • state-mutating
  • memory
  • --json

propose writes status 'proposed' and refuses --status: accepting a decision is a person editing the field. The identity is allocated under a lock over the section directory, so two worktrees proposing at once get two identities; check refuses duplicate identities, a superseded record with no replacement, and one-sided supersession.

Syntax

majordomus adr <list|show <id>|propose "<title>"|check> [--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 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.

  • 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.

  • 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.

Guarantees this command carries