@nikovirtala/adr 0.0.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +156 -0
- package/dist/cli.js +1057 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.js +3 -0
- package/dist/kind-BcHjCHvi.js +415 -0
- package/dist/kind-BcHjCHvi.js.map +1 -0
- package/dist/kind.js +1 -0
- package/dist/types/cli/accept.d.ts +3 -0
- package/dist/types/cli/commands.d.ts +3 -0
- package/dist/types/cli/deprecate.d.ts +3 -0
- package/dist/types/cli/editor.d.ts +12 -0
- package/dist/types/cli/for.d.ts +3 -0
- package/dist/types/cli/init.d.ts +3 -0
- package/dist/types/cli/link.d.ts +3 -0
- package/dist/types/cli/list.d.ts +3 -0
- package/dist/types/cli/new.d.ts +6 -0
- package/dist/types/cli/note.d.ts +3 -0
- package/dist/types/cli/open.d.ts +17 -0
- package/dist/types/cli/reject.d.ts +3 -0
- package/dist/types/cli/scope.d.ts +7 -0
- package/dist/types/cli/show.d.ts +3 -0
- package/dist/types/cli/supersede.d.ts +3 -0
- package/dist/types/cli/text.d.ts +46 -0
- package/dist/types/cli/withdraw.d.ts +3 -0
- package/dist/types/cli.d.ts +2 -0
- package/dist/types/config.d.ts +33 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/init.d.ts +27 -0
- package/dist/types/kind/project.d.ts +50 -0
- package/dist/types/kind/reduce.d.ts +35 -0
- package/dist/types/kind/rules.d.ts +20 -0
- package/dist/types/kind/schemas.d.ts +37 -0
- package/dist/types/kind.d.ts +18 -0
- package/dist/types/log.d.ts +53 -0
- package/dist/types/scope.d.ts +51 -0
- package/dist/types/templates/builtin.d.ts +8 -0
- package/dist/types/verify.d.ts +10 -0
- package/dist/types/views.d.ts +65 -0
- package/dist/types/write.d.ts +333 -0
- package/dist/write-9IaqHRrY.js +1986 -0
- package/dist/write-9IaqHRrY.js.map +1 -0
- package/package.json +55 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Niko Virtala
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# @nikovirtala/adr
|
|
2
|
+
|
|
3
|
+
Architecture decision records kept as event logs in git, built on [`@nikovirtala/ledger`](https://www.npmjs.com/package/@nikovirtala/ledger). A decision is a Markdown body plus append-only event files. Its status (proposed, accepted, rejected, withdrawn, deprecated, superseded) is computed from the events, the body is frozen by its hash once accepted, and supersession chains always lead to the decision in force. See the [design doc](https://github.com/nikovirtala/ark/blob/main/docs/01-adr-v1.md).
|
|
4
|
+
|
|
5
|
+
Released at 0.x. Phase 1 ("Core") brought the decision kind, the library and the `adr` CLI with `new`, `accept`, `supersede`, `list`, `show` and `verify`. Phase 2a ("Commands and Scope") adds `init`, `reject`, `withdraw`, `deprecate`, `note`, `link`, `scope` and `for`, `accept --copy-scope`, Conventional Commits messages for `--commit`, and `ARK_ON_BEHALF_OF` for agents.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install --save-dev @nikovirtala/adr # Node 24 or newer, git 2.32.0 or newer
|
|
11
|
+
npx @nikovirtala/adr --help # or run it without installing
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
adr needs Node 24 or newer (`engines: >=24`). On older Node, npm prints an `EBADENGINE` warning and adr isn't supported.
|
|
15
|
+
|
|
16
|
+
## Layout
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
docs/adr/
|
|
20
|
+
adr.toml # optional config (or adr.toml at the repository root)
|
|
21
|
+
decisions/k3f9q8a1-use-aurora-for-orders.md
|
|
22
|
+
events/k3f9q8a1/20261016T081030Z-p4d2-proposed.yaml
|
|
23
|
+
events/k3f9q8a1/20261016T091512Z-7aq5-accepted.yaml
|
|
24
|
+
templates/nygard.md # copied from the built-in on first use
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Each decision has a canonical id (`k3f9q8a1`). Accepted decisions also get a number (`ADR-0001`), assigned in the order their acceptances land on the first-parent history of main and never stored or renumbered. Commands take an id or a number in any form: `k3f9q8a1`, `7`, `0007`, `ADR-7`, `adr-0007`.
|
|
28
|
+
|
|
29
|
+
## Commands
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
# Set up: write adr.toml and starter templates; never overwrites a file.
|
|
33
|
+
adr init --template nygard --template madr --commit
|
|
34
|
+
|
|
35
|
+
# Propose: render a draft from a template, write the body and the proposed event, and stage them.
|
|
36
|
+
adr new "Use Aurora for orders" --section context="We need SQL." --section scope=services/orders
|
|
37
|
+
adr new "Use Aurora Serverless" --supersedes ADR-0002 --template madr --edit
|
|
38
|
+
|
|
39
|
+
# Accept: lint the draft, hash the body, and write accepted (plus superseded on each declared target).
|
|
40
|
+
adr accept k3f9q8a1 --commit
|
|
41
|
+
|
|
42
|
+
# Supersede an accepted decision by another accepted one.
|
|
43
|
+
adr supersede ADR-0001 --by ADR-0002 --reason "Aurora fits the query load" --commit
|
|
44
|
+
|
|
45
|
+
# End a decision's life: reject or withdraw a proposal, deprecate an accepted decision.
|
|
46
|
+
adr reject k3f9q8a1 --reason "Too early" --commit
|
|
47
|
+
adr withdraw k3f9q8a1 --commit # --reason is optional
|
|
48
|
+
adr deprecate ADR-0003 --reason "Moving to a managed service" --commit
|
|
49
|
+
|
|
50
|
+
# Annotate: a note in any state, and typed links (relates-to, refines, depends-on).
|
|
51
|
+
adr note ADR-0003 "Benchmarks are in the wiki" --commit
|
|
52
|
+
adr link ADR-0004 ADR-0002 --rel refines --commit
|
|
53
|
+
adr link ADR-0004 platform:k3f9q8a1 # a decision in a configured [[sources]] log
|
|
54
|
+
|
|
55
|
+
# Scope: the code a decision governs, as globs, and which decisions govern a file.
|
|
56
|
+
adr scope ADR-0004 --set 'services/orders/**' --set 'libs/db/**'
|
|
57
|
+
adr scope ADR-0004 --add 'services/billing/**' --remove 'libs/db/**' --commit
|
|
58
|
+
adr scope ADR-0004 --clear
|
|
59
|
+
adr scope --follow-renames # after git mv: update the globs, stage, never commit
|
|
60
|
+
adr for services/orders/api.ts
|
|
61
|
+
adr for services/orders/api.ts --ref v1.2.0
|
|
62
|
+
|
|
63
|
+
# Read: list with computed status, show one decision with its state header.
|
|
64
|
+
adr list --status accepted
|
|
65
|
+
adr show ADR-0001 --history
|
|
66
|
+
adr list --ref v1.2.0 # time travel: the log as of any ref
|
|
67
|
+
|
|
68
|
+
# Verify the log: the whole tree, a range, or the staged changes (for a pre-commit hook).
|
|
69
|
+
adr verify
|
|
70
|
+
adr verify --base origin/main --format github
|
|
71
|
+
adr verify --staged
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
- `new` takes `--template <name>` (default `nygard`; built-ins `nygard`, `madr`, `y-statement`, or your own `<root>/templates/<name>.md`), `--section <id>=<markdown>` (repeatable), `--supersedes <decision>` (repeatable), and `--edit`/`--no-edit`. It opens `$VISUAL` or `$EDITOR` with `--edit`, or by default in an interactive terminal; an editor failure never loses your edits and never commits content you didn't confirm.
|
|
75
|
+
- `new --no-edit --commit` (or `--commit` with no editor) refuses a draft with unfilled required sections: exit 1, nothing written. Without `--commit` the draft is staged with a warning on stderr; after an editor, a draft that still has unfilled sections stays staged and isn't committed.
|
|
76
|
+
- `accept` of a decision that supersedes others offers their scope globs: `--copy-scope` copies them in the same write, `--no-copy-scope` doesn't, and without either it asks `[y/N]` in a terminal and otherwise prints the `adr scope … --add …` command that would copy them.
|
|
77
|
+
- `scope` takes `--set <glob>` (repeatable; replaces the set), `--add`/`--remove <glob>` (repeatable) or `--clear`, and stores the set sorted and deduplicated. It notes a glob that matches no tracked file and a narrowing of an accepted decision. `scope --follow-renames [<decision>]` updates the globs of every decision (or one) for the staged renames, so a moved file stays governed, and only stages.
|
|
78
|
+
- `for <path>…` lists the decisions governing each file, from scope globs and code anchors, with a warning for decisions that aren't in force. A code anchor is a comment such as `// adr:<id>` or `# adr:<source>:<id>`: the token counts only after a comment leader on the same line (a heuristic), and anchors in binary files, submodules, the log directory and files matched by `[scope] anchor_exclude` are not found.
|
|
79
|
+
- Writes stage their files and don't commit, unless you pass `--commit` or set `auto_commit`. A commit holds every file of the decisions it touches (body, events, template), so `adr verify --ref <commit>` is clean, and never your unrelated staged work. It refuses with exit 2 when a file it needs has unstaged changes or isn't tracked. Its `ADR: <id>` and `ADR-Event: <type>` trailers name one event each, in a fixed order, whatever your `trailer.*` config.
|
|
80
|
+
- `--commit` messages follow Conventional Commits: the subject comes from `[commit] subject` (default `docs(adr): {action} {title}`, for example `docs(adr): propose to use Aurora for orders`, `docs(adr): decide to use Aurora for orders`, `docs(adr): set the scope of ADR-0003`). The description starts with a lowercase letter and the rest keeps its natural casing (`Use` becomes `use`; `CLI`, `ARK_AGENT`, `GraphQL` and `Node 24` stay). `new`, `accept`, `reject` and `withdraw` name the decision by its title (`propose to`, `decide to`, `decide not to`, `withdraw the proposal to`), while `deprecate`, `supersede`, `note`, `link` and `scope` name it by its number or id. A title that would make the description longer than 72 characters is shortened with `…`, at a word boundary when one is near the limit, the body then starts with `Full title: <title>`, and a note says so; numbers and ids are never shortened. `-m/--message <text>` replaces the subject and body as given; a message that ends in its own trailer block as git reads it, such as `Co-authored-by:` or `Refs:` lines, gets adr's trailers appended to that paragraph. The trailers are always the last paragraph, so `git log --grep "ADR: <id>"` finds a decision's commits.
|
|
81
|
+
- `show` leads with a state header. A decision that isn't in force says so and names what to read instead, for example `SUPERSEDED by ADR-0002 (accepted 2026-10-18). Do not apply this decision; read ADR-0002 instead.`
|
|
82
|
+
|
|
83
|
+
Every command takes `--json`, `--root <dir>` (the log directory) and `--help`; reads also take `--ref <ref>`, and writes refuse it.
|
|
84
|
+
|
|
85
|
+
## Exit codes and JSON
|
|
86
|
+
|
|
87
|
+
- **0**: success.
|
|
88
|
+
- **1**: failing findings from `verify`, or a write the log's rules forbid (an illegal transition, a draft that fails its template lint, an agent's acceptance, an unaccepted successor). Nothing is written, and the findings are in the output.
|
|
89
|
+
- **2**: the tool can't run: bad usage (unknown command, flag or decision, a missing argument, `--ref` on a write), config, environment or internal errors. Nothing is written.
|
|
90
|
+
|
|
91
|
+
With `--json`, stdout carries exactly one JSON document: `adr-list/v1`, `adr-show/v1`, `adr-write/v1` (for every write command), `adr-for/v1`, `adr-init/v1`, ledger's `ledger-findings/v1` for `verify`, or `ark-error/v1` (`{ "format": "ark-error/v1", "error": { "code", "message" } }`) when the command exits 2. Every key is always present, with `null` where a value is unknown. Warnings and notes go to stderr.
|
|
92
|
+
|
|
93
|
+
## Config (`adr.toml`)
|
|
94
|
+
|
|
95
|
+
Every key is optional.
|
|
96
|
+
|
|
97
|
+
```toml
|
|
98
|
+
[log]
|
|
99
|
+
root = "docs/adr" # read from <repo>/adr.toml; the default
|
|
100
|
+
default_template = "nygard"
|
|
101
|
+
main_branch = "origin/main" # the ref numbers come from; default origin/HEAD, origin/main, main, …
|
|
102
|
+
index = "none" # validated now, used in phase 2
|
|
103
|
+
|
|
104
|
+
[policy]
|
|
105
|
+
accept_requires_human = true # refuse acceptances by an agent
|
|
106
|
+
auto_commit = false # commit writes without --commit
|
|
107
|
+
mcp_can_decide = false # validated now, used in phase 3
|
|
108
|
+
|
|
109
|
+
[verify]
|
|
110
|
+
fail_on = "error" # or "warning"
|
|
111
|
+
|
|
112
|
+
[commit]
|
|
113
|
+
subject = "docs(adr): {action} {title}" # the default; --message overrides it
|
|
114
|
+
|
|
115
|
+
[scope]
|
|
116
|
+
anchor_keyword = "adr" # the default: anchors read adr:<id>
|
|
117
|
+
anchor_exclude = ["**/*.md"] # files never searched for anchors (docs that show examples); default []
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`[commit] subject` is one line with `{title}` (what names the decision: its title with an ordinary capitalized first word lowercased (`Use` → `use`, so a single-capital proper noun such as `Aurora` loses that capital; `CLI`, `GraphQL` and `ARK_AGENT` stay), or its number or id), `{raw_title}` (the same with its case kept), `{action}`, `{id}`, `{display}` and `{command}`. It is checked when the config is read: at most one title placeholder, after the type and scope, and room within 72 characters for the title (20 characters) and, uncut, for the longest number-based name (`ADR-0001 to a decision in another log`); an invalid template exits 2. Whatever the template, the description's first letter is lowercased.
|
|
121
|
+
|
|
122
|
+
The config is `<repo>/adr.toml`, whose `[log] root` chooses the log directory, or `<root>/adr.toml`. `--root` uses a repository-level file that names the same root, and overrides one that names another root with a warning.
|
|
123
|
+
|
|
124
|
+
## Agents
|
|
125
|
+
|
|
126
|
+
An agent using the CLI sets `ARK_AGENT=<agent-id>`, and `ARK_ON_BEHALF_OF=<email>` for the person it acts for. Its events are recorded as the agent, acting on behalf of that address. Without `ARK_ON_BEHALF_OF` they are recorded on behalf of the git user, and every write says so in a note on stderr and in the JSON `notes`: `on_behalf_of taken from the git identity <email>; set ARK_ON_BEHALF_OF to name who you act for`. An `ARK_ON_BEHALF_OF` that isn't an email address exits 2 before anything is written; without `ARK_AGENT` it is ignored, with a note. With `accept_requires_human` on (the default), `adr accept` refuses an agent with exit 1 (`decision/agent-accepted`), and `adr new` tells the agent to ask a human to review and accept.
|
|
127
|
+
|
|
128
|
+
The git identity is git's author identity (`git var GIT_AUTHOR_IDENT` with `user.useConfigOnly`): `GIT_AUTHOR_NAME` and `GIT_AUTHOR_EMAIL` when set, else `user.name` and `user.email`; it is never guessed. Setting `GIT_AUTHOR_*` also changes the author of `--commit` commits, which is why agents should use `ARK_ON_BEHALF_OF` instead. `--commit` also needs a committer identity, and exits 2 before writing without one.
|
|
129
|
+
|
|
130
|
+
## Library
|
|
131
|
+
|
|
132
|
+
The CLI is a thin layer over the library, and phase 3's MCP server uses the same functions and returns the same JSON documents.
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
import { listDecisions, openDecisionLog, readDecisions, showDecision, verifyDecisions } from "@nikovirtala/adr";
|
|
136
|
+
|
|
137
|
+
const log = await openDecisionLog({ cwd: process.cwd() });
|
|
138
|
+
const read = await readDecisions(log);
|
|
139
|
+
const { decisions } = listDecisions(read, { status: ["accepted"] });
|
|
140
|
+
const findings = await verifyDecisions(log, { type: "full" });
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`initDecisionLog`, `proposeDecision`, `acceptDecision`, `rejectDecision`, `withdrawDecision`, `deprecateDecision`, `supersedeDecision`, `noteDecision`, `linkDecision`, `scopeDecision` and `followScopeRenames` write, `decisionsFor` answers `adr for`, `resolveDecision` turns an id or number into an id, and `stateHeader` gives the header `show` prints.
|
|
144
|
+
|
|
145
|
+
`@nikovirtala/adr/kind` exports only the decision kind (`adrKind`, made with ledger's `defineKind`) and its types, without any CLI code, for tools that fold decision logs themselves, such as issues linking to decisions:
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import { adrKind } from "@nikovirtala/adr/kind";
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Not yet included
|
|
152
|
+
|
|
153
|
+
- **Reference checks are local.** `verify` checks that `supersedes`, `superseded.by` and `linked.to` name decisions in the same log. Qualified references to other logs (`<source>:<id>`, written by `adr link`) are skipped.
|
|
154
|
+
- **Phase 2b:** git hooks (pre-commit, commit-msg, post-merge, pre-push), a hint for recovering a failed commit, a generated index (and the number fallback for shallow clones), `list --since`, and CI checks for index freshness, scope globs, code anchors, decisions governed by inactive ones and scope narrowing.
|
|
155
|
+
- **Phase 3:** an MCP server, a SKILL.md for agents, and search.
|
|
156
|
+
- **ark's step 4, with issues:** cross-log reference checks at the pinned ref and listing across sources; until then `link` stores a qualified target unchecked.
|