Skip to content

Latest commit

 

History

History
265 lines (210 loc) · 10.7 KB

README.md

File metadata and controls

265 lines (210 loc) · 10.7 KB

@adrkit/cli

Git-native architecture decision record tooling from adrkit.

Install and run

Use the package name for zero-install runs. The published binary is adr, but a bare npx adr resolves an unrelated npm package:

npx @adrkit/cli lint

For project use, add it as a dev dependency and invoke it through a script:

npm install --save-dev @adrkit/cli    # or: bun add --dev @adrkit/cli
npm pkg set scripts.adr=adr
npm run adr -- lint

That keeps you on the installed local binary instead of whatever npx adr might download from the registry.

You can also install it globally:

npm install -g @adrkit/cli
adr lint

Every install also provides adrkit, an identical alias for the same binary. Prefer adrkit in CI, Makefiles, and agent instructions because its name is unambiguous:

adrkit lint

Do not use npx adrkit or bunx adrkit. There is no unscoped adrkit package, so those forms would resolve against the registry rather than the published @adrkit/cli package.

Human-readable CLI output is TTY-aware: it stays ANSI-free when redirected or piped, honors NO_COLOR, and can be forced with --color auto|always|never.

Commands

The binary includes:

  • new
  • lint
  • graph
  • explain
  • check
  • queue
  • approve
  • object
  • resolve
  • accept
  • migrate --from madr
  • completion
  • evaluate

Run adr --help for the command list, adr help <command> for one command's flags, and adr --version to print the installed version.

Review queue and acceptance

adr queue lists every proposed record with its SLA state, deadline, approvals, and objections. In a terminal it prints a list sized to the window, and each item ends with the next step: the command to accept it, or the reason it cannot be accepted yet. Piped, redirected, or captured output is the deterministic Markdown report, unchanged; --format json emits QueueReport v1, and --format terminal|markdown choose explicitly.

adr queue
adr accept 0044 --by @octocat

adr accept sets status: accepted, provenance.ratifiedBy, and review.decidedAt (now, UTC, with seconds), and changes no other line. It commits nothing. It refuses, and leaves the file untouched, when the record is not proposed, has an unresolved objection, has fewer approvals than its review.quorum, or would not be a valid accepted record. --by is required and is never inferred. Exit codes: 0 accepted, 1 refused, 2 usage error.

Recording review: approve, object, resolve

adr approve 0044 --by @octocat
adr object 0044 --by @hubot --summary "Needs a load test first"
adr resolve 0044 --objection 1 --by @hubot

adr approve adds --by to review.approvals. adr object appends { by, summary, resolved: false } to review.objections; the summary is one line of at most 500 characters. adr resolve sets resolved: true on objection n (counting from 1, in file order), and only the objector may resolve it. Each acts only on a proposed record, changes no other line, commits nothing, and leaves the file untouched on a repeat (approving twice, the same open objection twice, resolving a resolved objection). --by is required and is never inferred, and identities compare case-insensitively, so @Bob after @bob is the same approval and quorum counts distinct people. Exit codes match adr accept: 0 written or unchanged, 1 refused, 2 usage error; --json is available on all three. adr queue's approval and objection counts follow these writes (ADR-0051, proposed).

Decision graph

adr graph chooses its default at the stdout boundary: an interactive terminal gets a compact status and relationship view, while a pipe, redirect, or captured subprocess keeps receiving deterministic Graphviz DOT. Pin a channel whenever another tool consumes the output:

adr graph                                  # terminal view on a TTY; DOT in a pipe
adr graph --focus 0014                     # one ADR and its direct neighborhood
adr graph --kind supersedes                # one relationship kind
adr graph --format mermaid > decisions.mmd
adr graph --format dot | dot -Tsvg > decisions.svg
adr graph --format json > decisions.json

--kind is repeatable and accepts supersedes, relatesTo, and conflictsWith. Explicit --format terminal|dot|json|mermaid always overrides TTY selection. DOT, Mermaid, and JSON remain ANSI-free even with forced color. Large sparse and focused terminal views are bounded; use another --kind or a machine format for the complete edge set.

If one or more corpus records are invalid, graph still emits the complete projection of every valid record, writes the error findings to stderr, and exits 1. Exit 0 means rendered without corpus errors; exit 2 remains a usage or unreachable-directory error.

Shell completions

adr completion <bash|zsh|fish> prints a deterministic completion script to stdout. The generated script registers both adr and adrkit where the shell format permits.

adr completion bash > ~/.local/share/bash-completion/completions/adr
cp ~/.local/share/bash-completion/completions/adr ~/.local/share/bash-completion/completions/adrkit
adr completion zsh > ~/.zsh/completions/_adr
cp ~/.zsh/completions/_adr ~/.zsh/completions/_adrkit
adr completion fish > ~/.config/fish/completions/adr.fish
cp ~/.config/fish/completions/adr.fish ~/.config/fish/completions/adrkit.fish

If you prefer symlinks, point both Fish entry points at the same generated file:

ln -sf ~/.config/fish/completions/adr.fish ~/.config/fish/completions/adrkit.fish

Inbound @adr markers

adr explain <path> and adr check <files...> resolve decisions in both directions. A record declares the paths it governs with affects; a source file can declare the decision it lives under with a comment marker:

// @adr 0012
export function syncOnce() { /* ... */ }
$ adr explain src/services/sync/retry.ts
Decisions governing src/services/sync/retry.ts:
  0009  [accepted] Resolve affects deterministically
    via path: src/services/sync/**
  0012  [accepted] Bind catalog entities to owned paths
    declared by src/services/sync/retry.ts:1 (@adr 0012)

Marker scanning is intentionally narrow:

  • only the first 8192 bytes of a file are considered
  • the scan stops at the last complete line inside that window
  • at most the first 64 declarations per file are retained
  • the marker must be the first content on a dedicated comment line
  • lines inside fenced code blocks are ignored
  • markdown files (.md, .mdx, .markdown) accept only <!-- and {/*

Nothing is written back to the record.

A marker that names a superseded, rejected, or deprecated record still resolves to that historical record and emits an advisory stale-marker warning. For a superseded chain, the warning names the terminal live successor when one can be resolved; adrkit never silently substitutes it. The warning does not affect the exit code.

Under --as-of, a marker naming a record that was in force on that date is not stale — it was an accurate declaration then. The present-tense warning is unchanged.

Time travel: adr explain --as-of

adr explain <path> --as-of <date|ref> answers which decisions governed a path on a past date. Every record carries a date, and a superseded record names its successor, so a valid-time window is derivable with no schema change: it opens on the record's own date and closes on its immediate successor's.

$ adr explain src/auth/session.ts --as-of 2026-03-01
As of 2026-03-01
Note: matchers and @adr markers are read from today’s corpus and working tree; only standing is re-dated.
Decisions governing src/auth/session.ts as of 2026-03-01:
  0007  [superseded] Use JWT sessions (superseded by 0019)
    in force 2026-01-15 → 2026-06-01 (closed by 0019)
    via path: src/auth/**
Not yet recorded as of 2026-03-01:
  0019  [accepted] Use opaque server sessions
    recorded 2026-06-01

The status in brackets is the record's status now; the window underneath is what placed it. Both are true, and a reader needs both. The note under the header is there because the evidence lines (via path:, declared by …) are read from today's corpus and today's file — they are the one thing --as-of does not re-date.

  • The value is a date first, a git ref second. YYYY-MM-DD or an ISO datetime with an explicit timezone is read as a date; anything else is resolved with git rev-parse --verify <ref>^{commit} in the current directory and dated by that commit's committer date. A tag named 2026-03-01 therefore reads as a date, not as that tag.
  • Windows are half-open. The successor owns its own start day, so exactly one record along a supersession chain is in force on any given date.
  • deprecated is reported as undetermined, not guessed at. The schema allows supersededBy only on superseded, so a deprecated record records no date it stopped governing. Saying so is the honest answer; a rejected record is history on every date, because it was in force on none.
  • --as-of re-dates the corpus, never the working tree. Which decisions reach the path is still read from today's records and today's file contents. Reading file contents at a past ref is a larger contract and is not attempted.
  • It is additive. Without the flag, output is unchanged. With it, --json gains an asOf block and the present-tense governing, activeProposals, and history keys keep their meaning (ADR-0039).

In --json, pattern matches appear in firedMatchers and file declarations in declaredBy. explain --json includes a single-file markers block plus scannedBytes, fileBytes, and exact retained/omitted declaration counts. check --json includes a markerScan report with state counts; exact unavailable, truncated, and skipped paths; and aggregate declaration counts.

Multi-file scans are capped at 3,000 normalized paths with 16 concurrent reads, and retain at most the first 10,000 declarations in code-unit path then source order. Skipped paths and declaration overflow each collapse to one bounded warning; neither they nor marker claims can change the exit code.

See the commands reference.

The published ESM CLI runs on Node.js 22 or newer; development in the adrkit repository uses Bun.

See also @adrkit/mcp, a local read-only Model Context Protocol server that exposes the same corpus to coding agents.

Documentation: https://adrkit.dev

License: Apache-2.0