@nikovirtala/adr 0.3.0 → 0.4.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/README.md +49 -6
- package/dist/cli.js +168 -39
- package/dist/cli.js.map +1 -1
- package/dist/index.js +4 -2
- package/dist/init-YgW7g9Fx.js +588 -0
- package/dist/init-YgW7g9Fx.js.map +1 -0
- package/dist/list-Dt4KGxvj.js +58 -0
- package/dist/list-Dt4KGxvj.js.map +1 -0
- package/dist/{scope-CLqH-q1y.js → search-CwIPlNGd.js} +379 -487
- package/dist/search-CwIPlNGd.js.map +1 -0
- package/dist/server-CXCO5zfp.js +938 -0
- package/dist/server-CXCO5zfp.js.map +1 -0
- package/dist/types/cli/init.d.ts +5 -2
- package/dist/types/cli/mcp.d.ts +6 -0
- package/dist/types/cli/search.d.ts +3 -0
- package/dist/types/cli/text.d.ts +6 -0
- package/dist/types/commit-actions.d.ts +2 -0
- package/dist/types/index.d.ts +5 -2
- package/dist/types/init.d.ts +16 -1
- package/dist/types/log.d.ts +10 -0
- package/dist/types/mcp/policy.d.ts +33 -0
- package/dist/types/mcp/prompt.d.ts +7 -0
- package/dist/types/mcp/resources.d.ts +16 -0
- package/dist/types/mcp/server.d.ts +16 -0
- package/dist/types/mcp/text.d.ts +42 -0
- package/dist/types/mcp/tools.d.ts +27 -0
- package/dist/types/scope.d.ts +3 -1
- package/dist/types/search.d.ts +61 -0
- package/dist/types/skill.d.ts +5 -0
- package/dist/types/templates/list.d.ts +31 -0
- package/dist/types/views.d.ts +3 -1
- package/dist/types/write.d.ts +39 -1
- package/package.json +5 -2
- package/dist/scope-CLqH-q1y.js.map +0 -1
package/README.md
CHANGED
|
@@ -32,6 +32,8 @@ Each decision has a canonical id (`k3f9q8a1`). Accepted decisions also get a num
|
|
|
32
32
|
```sh
|
|
33
33
|
# Set up: write adr.toml and starter templates; never overwrites a file.
|
|
34
34
|
adr init --template nygard --template madr --commit
|
|
35
|
+
adr init --skill # also the agent skill and an AGENTS.md pointer
|
|
36
|
+
adr init --skill --skill-dir .kiro/skills/adr
|
|
35
37
|
|
|
36
38
|
# Propose: render a draft from a template, write the body and the proposed event, and stage them.
|
|
37
39
|
adr new "Use Aurora for orders" --section context="We need SQL." --section scope=services/orders
|
|
@@ -67,6 +69,10 @@ adr list --since ORIG_HEAD # only decisions with events added si
|
|
|
67
69
|
adr show ADR-0001 --history
|
|
68
70
|
adr list --ref v1.2.0 # time travel: the log as of any ref
|
|
69
71
|
|
|
72
|
+
# Search ids, titles, bodies and notes: every word must match; active decisions unless --all.
|
|
73
|
+
adr search orders aurora
|
|
74
|
+
adr search dynamodb --all
|
|
75
|
+
|
|
70
76
|
# Verify the log: the whole tree, a range, or the staged changes (for a pre-commit hook).
|
|
71
77
|
adr verify
|
|
72
78
|
adr verify --base origin/main --format github
|
|
@@ -79,6 +85,9 @@ adr index --check
|
|
|
79
85
|
# Git hooks: install adr in ledger's shared hook shims, or print the config for a hook manager.
|
|
80
86
|
adr hooks install
|
|
81
87
|
adr hooks print --manager lefthook
|
|
88
|
+
|
|
89
|
+
# The MCP server for agents, over stdio; start it from an MCP client (see "MCP server").
|
|
90
|
+
adr mcp
|
|
82
91
|
```
|
|
83
92
|
|
|
84
93
|
- `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.
|
|
@@ -93,6 +102,8 @@ adr hooks print --manager lefthook
|
|
|
93
102
|
- `list --since <rev>` lists only the decisions with event files added between `<rev>` and HEAD (or `--ref`), each with the new event types, such as `[new: accepted]`; it prints nothing when the log didn't change.
|
|
94
103
|
- In a shallow clone, numbers need history the clone lacks. With a committed index, adr reads them from `index.md` in the commit being read (`--ref`, else HEAD) and notes `numbers come from docs/adr/index.md (shallow clone)`; without one, decisions display by id until you run `git fetch --unshallow`. Nothing is fetched automatically.
|
|
95
104
|
- `hooks install` registers adr in ledger's shims, one per hook and shared with other ark tools: `pre-commit` runs `adr hooks run pre-commit`, `commit-msg` runs `adr hooks trailers` (adds `ADR:` and `ADR-Event:` trailers when event files are staged, unless the message has them or a merge is in progress), `pre-push` runs `adr verify --base @{upstream} --ref HEAD`, which checks the current branch's commits against its upstream whichever refs the push names (without an upstream, against `origin`'s default branch; with no remote, the whole log), and `post-merge` runs `adr list --since ORIG_HEAD`. It never overwrites a foreign hook (it writes the other hooks, exits 2 and names the line to add to that hook) and writes nothing when `core.hooksPath` is set (exit 2, pointing to `adr hooks print --manager lefthook|husky|pre-commit` for the hook manager in use). `hooks uninstall` removes adr from the shims. The pre-commit steps are rename following (`[hooks] scope_follow_renames`), the index (checked, or regenerated and staged with `[hooks] index = "stage"`), then `verify --staged`. The hook stages only into the commit's own index: for `git commit <paths>`, a rename that changes a decision's scope stops the commit and names the command to run. When the staged `index.md` lists decision files that aren't staged, the `index/stale` finding names them; stage them, or move them out of the work tree and run `adr index`.
|
|
105
|
+
- `search <query>…` joins its words and lists the decisions whose id, number, title, body or notes contain every one, case- and accent-insensitively (`Café` finds `cafe`), as substrings (`order` finds `orders`). Only proposed and accepted decisions are listed unless `--all`; a note counts the others that match. Each result has the best matching line of the body or a note. It reads the local log only; with `[[sources]]` configured, a note says so (as `list` and `for` do) until ark's step 4.
|
|
106
|
+
- `init --skill` writes the agent skill to `.claude/skills/adr/SKILL.md`, or to `<dir>/SKILL.md` with `--skill-dir <dir>` (a directory inside the repository), and adds one line pointing at it to `AGENTS.md`, creating the file if needed. It never overwrites a SKILL.md (one that differs is kept, with a note), and a second run changes nothing. An `AGENTS.md` with unstaged changes, or not tracked, gets the line but isn't staged; with `--commit` it is refused (exit 2).
|
|
96
107
|
- `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.`
|
|
97
108
|
|
|
98
109
|
Every command takes `--json`, `--root <dir>` (the log directory) and `--help`; reads also take `--ref <ref>`, and writes refuse it.
|
|
@@ -103,7 +114,7 @@ Every command takes `--json`, `--root <dir>` (the log directory) and `--help`; r
|
|
|
103
114
|
- **1**: failing findings from `verify` (or `index --check`, or a hook), 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.
|
|
104
115
|
- **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, except when git refuses a `--commit` (`commit-failed`): the write is then staged and the error says how to finish the commit.
|
|
105
116
|
|
|
106
|
-
With `--json`, stdout carries exactly one JSON document: `adr-list/v1` (with `since`, `null` without `--since`), `adr-show/v1`, `adr-write/v1` (for every write command), `adr-for/v1`, `adr-init/v1`, `adr-index/v1`, `adr-hooks/v1`, ledger's `ledger-findings/v1` for `verify`, or `ark-error/v1` (`{ "format": "ark-error/v1", "error": { "code", "message" } }`) when the command exits 2; a `commit-failed` error also has the write's `notes`. Every key is always present, with `null` where a value is unknown. A `numbering` object's `reason` is `"index"` when a shallow clone's numbers come from `index.md`. In a shallow clone with a committed index, `adr-write/v1`'s `decision.number` comes from the index in HEAD, which on a branch is the number the acceptance will get once the branch merges. Warnings and notes go to stderr.
|
|
117
|
+
With `--json`, stdout carries exactly one JSON document: `adr-list/v1` (with `since`, `null` without `--since`), `adr-show/v1`, `adr-search/v1`, `adr-write/v1` (for every write command), `adr-for/v1`, `adr-init/v1`, `adr-index/v1`, `adr-hooks/v1`, ledger's `ledger-findings/v1` for `verify`, or `ark-error/v1` (`{ "format": "ark-error/v1", "error": { "code", "message" } }`) when the command exits 2; a `commit-failed` error also has the write's `notes`. Every key is always present, with `null` where a value is unknown. A `numbering` object's `reason` is `"index"` when a shallow clone's numbers come from `index.md`. In a shallow clone with a committed index, `adr-write/v1`'s `decision.number` comes from the index in HEAD, which on a branch is the number the acceptance will get once the branch merges. Warnings and notes go to stderr.
|
|
107
118
|
|
|
108
119
|
## Config (`adr.toml`)
|
|
109
120
|
|
|
@@ -119,7 +130,7 @@ index = "none" # or "committed": adr index, fresh on every writ
|
|
|
119
130
|
[policy]
|
|
120
131
|
accept_requires_human = true # refuse acceptances by an agent
|
|
121
132
|
auto_commit = false # commit writes without --commit
|
|
122
|
-
mcp_can_decide = false #
|
|
133
|
+
mcp_can_decide = false # agents propose, humans decide; true adds adr_accept, adr_reject, adr_deprecate and adr_supersede to adr mcp
|
|
123
134
|
|
|
124
135
|
[verify]
|
|
125
136
|
fail_on = "error" # or "warning"
|
|
@@ -149,9 +160,41 @@ An agent using the CLI sets `ARK_AGENT=<agent-id>`, and `ARK_ON_BEHALF_OF=<email
|
|
|
149
160
|
|
|
150
161
|
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.
|
|
151
162
|
|
|
163
|
+
Agents with an MCP client use `adr mcp` instead (below), and `adr init --skill` gives them the skill: when to check the decisions, how to read them, and that they propose and a human accepts.
|
|
164
|
+
|
|
165
|
+
## MCP server
|
|
166
|
+
|
|
167
|
+
`adr mcp` serves the decision log to agents over stdio, in the repository it starts in (`--root` works as for every command). Add it to your MCP client's server config:
|
|
168
|
+
|
|
169
|
+
```json
|
|
170
|
+
{
|
|
171
|
+
"mcpServers": {
|
|
172
|
+
"adr": {
|
|
173
|
+
"command": "npx",
|
|
174
|
+
"args": ["-y", "@nikovirtala/adr", "mcp"],
|
|
175
|
+
"env": { "ARK_ON_BEHALF_OF": "you@example.com" }
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Set `ARK_ON_BEHALF_OF` in that `env`: stdio clients pass only a few environment variables (`HOME`, `PATH` and the like) to the server, so a value exported in your shell doesn't reach it. Writes are recorded as an agent named by the MCP client (`actor.agent`), on behalf of `ARK_ON_BEHALF_OF`, or of the git user, with a note. An invalid `ARK_ON_BEHALF_OF` makes `adr mcp` exit 2 before serving.
|
|
182
|
+
|
|
183
|
+
| Tools | Listed |
|
|
184
|
+
| --- | --- |
|
|
185
|
+
| `adr_search`, `adr_get`, `adr_list`, `adr_templates`, `adr_for_path` (reads) | always |
|
|
186
|
+
| `adr_propose`, `adr_update_draft`, `adr_note`, `adr_link`, `adr_scope`, `adr_withdraw` (writes) | always |
|
|
187
|
+
| `adr_accept`, `adr_reject`, `adr_deprecate`, `adr_supersede` | with `[policy] mcp_can_decide = true` |
|
|
188
|
+
|
|
189
|
+
- Every tool returns the JSON document its CLI command prints under `--json` (`adr-search/v1`, `adr-show/v1`, `adr-list/v1`, `adr-for/v1`, `adr-write/v1`, and `adr-templates/v1` for `adr_templates`) as `structuredContent`, with a readable text whose state headers come first and point at `adr://decision/<number>`. Errors are `isError` results with `ark-error/v1`; a write the log or the MCP policy refuses is a document with `ok: false` and the findings.
|
|
190
|
+
- Resources: `adr://index` (the index table) and `adr://decision/{id}`, one decision's state header and body, where `{id}` is the id, the number or `ADR-NNNN`. The prompt `adr_draft_from_discussion` (optional `title`, `template`, `supersedes`) walks an agent from a conversation to a proposal.
|
|
191
|
+
- `adr_propose` takes a `sections` map (section id to markdown); template lint findings come back as data, and `adr_update_draft` replaces sections of a draft in place, writing only if nobody changed the file meanwhile. With `auto_commit`, an incomplete draft is refused instead of committed.
|
|
192
|
+
- Without `mcp_can_decide`, an agent may update, scope and withdraw only proposals made on behalf of the same person, and never rescope an accepted or deprecated decision (`decision/mcp-cannot-decide`). `accept_requires_human` still refuses an agent's `adr_accept`.
|
|
193
|
+
- MCP writes stage their files and commit only with `[policy] auto_commit`; there is no commit input. Each call reads the log again, but the tool list is fixed at startup: restart `adr mcp` after changing `mcp_can_decide`.
|
|
194
|
+
|
|
152
195
|
## Library
|
|
153
196
|
|
|
154
|
-
The CLI is a thin layer over the library, and
|
|
197
|
+
The CLI is a thin layer over the library, and the MCP server uses the same functions and returns the same JSON documents.
|
|
155
198
|
|
|
156
199
|
```ts
|
|
157
200
|
import { listDecisions, openDecisionLog, readDecisions, showDecision, verifyDecisions } from "@nikovirtala/adr";
|
|
@@ -162,7 +205,7 @@ const { decisions } = listDecisions(read, { status: ["accepted"] });
|
|
|
162
205
|
const findings = await verifyDecisions(log, { type: "full" });
|
|
163
206
|
```
|
|
164
207
|
|
|
165
|
-
`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. `indexDecisionLog` and `renderDecisionIndex` regenerate or render the index, and `installDecisionHooks`, `uninstallDecisionHooks`, `printDecisionHooks`, `runPreCommitHook` and `addHookTrailers` are the hook commands.
|
|
208
|
+
`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. `searchDecisions` answers `adr search`, `listTemplates` lists the usable templates (`adr-templates/v1`), `updateDraft` replaces sections of a proposed draft, `sourcesNote` gives the local-only note, and `SKILL_MD` is the agent skill. `indexDecisionLog` and `renderDecisionIndex` regenerate or render the index, and `installDecisionHooks`, `uninstallDecisionHooks`, `printDecisionHooks`, `runPreCommitHook` and `addHookTrailers` are the hook commands.
|
|
166
209
|
|
|
167
210
|
`@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:
|
|
168
211
|
|
|
@@ -173,5 +216,5 @@ import { adrKind } from "@nikovirtala/adr/kind";
|
|
|
173
216
|
## Not yet included
|
|
174
217
|
|
|
175
218
|
- **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`) and qualified anchors are skipped until ark's step 4.
|
|
176
|
-
- **
|
|
177
|
-
- **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.
|
|
219
|
+
- **Search limits.** `adr search` matches substrings without stemming (`orders` doesn't find `order`) and without phrases, and folds accents but not `ß` (`strasse` doesn't find `Straße`).
|
|
220
|
+
- **ark's step 4, with issues:** cross-log reference checks at the pinned ref, and listing and searching across sources; until then `link` stores a qualified target unchecked.
|
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { A as
|
|
2
|
+
import { A as changedSince, I as resolveDecision, M as numberingNote, N as openDecisionLog, O as runDecisionIndex, P as readDecisions, _ as withdrawDecision, c as followScopeRenames, d as proposeDecision, f as rejectDecision, h as supersedeDecision, i as showDecision, l as linkDecision, n as decisionsFor, o as acceptDecision, p as scopeDecision, r as listDecisions, s as deprecateDecision, t as searchDecisions, u as noteDecision } from "./search-CwIPlNGd.js";
|
|
3
3
|
import { n as DECISION_STATUSES, t as adrKind } from "./kind-DjvA3dyA.js";
|
|
4
|
+
import { a as installDecisionHooks, c as uninstallDecisionHooks, l as adrChecks, o as printDecisionHooks, r as addHookTrailers, s as runPreCommitHook, t as initDecisionLog } from "./init-YgW7g9Fx.js";
|
|
4
5
|
import { createRequire } from "node:module";
|
|
5
6
|
import { UsageError, exitCode, findingsReport, formatText, renderStateHeader, resolveAttribution, stagedRenames, templateHistoryRef } from "@nikovirtala/ledger";
|
|
6
|
-
import {
|
|
7
|
+
import { realpath } from "node:fs/promises";
|
|
8
|
+
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
7
9
|
import { COMMIT_OPTIONS, commitChoice, confirm, processIO, runCli, verifyCommand } from "@nikovirtala/ledger/cli";
|
|
8
10
|
import { spawn } from "node:child_process";
|
|
9
11
|
// src/cli/open.ts
|
|
@@ -176,7 +178,8 @@ const VERBS = {
|
|
|
176
178
|
deprecate: "deprecated",
|
|
177
179
|
note: "noted",
|
|
178
180
|
link: "linked",
|
|
179
|
-
scope: "scoped"
|
|
181
|
+
scope: "scoped",
|
|
182
|
+
update: "updated"
|
|
180
183
|
};
|
|
181
184
|
/**
|
|
182
185
|
* The text of a write command (design §5.6, §6.1). `subject` names what a refused command was
|
|
@@ -243,6 +246,9 @@ function writeText(doc, display, o) {
|
|
|
243
246
|
recorded
|
|
244
247
|
].join("\n");
|
|
245
248
|
}
|
|
249
|
+
case "update":
|
|
250
|
+
if (doc.written.length === 0) return `Draft of ${named(v.id, display)} unchanged`;
|
|
251
|
+
return [`Updated the draft of ${named(v.id, display)}: ${v.title}`, recorded].join("\n");
|
|
246
252
|
}
|
|
247
253
|
}
|
|
248
254
|
/**
|
|
@@ -298,15 +304,39 @@ function forText(doc, display, keyword) {
|
|
|
298
304
|
}
|
|
299
305
|
return lines.join("\n");
|
|
300
306
|
}
|
|
307
|
+
/**
|
|
308
|
+
* `adr search`: one padded row per result with its excerpt (or which field matched) under it, and
|
|
309
|
+
* an inactive result's warning; then the count.
|
|
310
|
+
*/
|
|
311
|
+
function searchText(doc) {
|
|
312
|
+
const quoted = JSON.stringify(doc.query);
|
|
313
|
+
if (doc.results.length === 0) return `No ${doc.include_inactive ? "" : "active "}decisions match ${quoted}.`;
|
|
314
|
+
const rows = columns(doc.results.map((r) => [
|
|
315
|
+
r.decision.number === null ? r.decision.id : `${r.decision.display} (${r.decision.id})`,
|
|
316
|
+
r.decision.status,
|
|
317
|
+
r.decision.title
|
|
318
|
+
])).split("\n");
|
|
319
|
+
const lines = [];
|
|
320
|
+
doc.results.forEach((r, i) => {
|
|
321
|
+
lines.push(rows[i] ?? "");
|
|
322
|
+
if (r.header.warning !== null) lines.push(` ${r.header.warning}`);
|
|
323
|
+
const where = r.matched.filter((f) => f === "id" || f === "title").join(" and ");
|
|
324
|
+
lines.push(r.excerpt === null ? ` (${where} match)` : ` ${r.excerpt}`);
|
|
325
|
+
});
|
|
326
|
+
const n = doc.total;
|
|
327
|
+
lines.push(`${n === 1 ? "1 decision matches" : `${n} decisions match`} ${quoted}.`);
|
|
328
|
+
return lines.join("\n");
|
|
329
|
+
}
|
|
301
330
|
/** `adr init`: what was written and kept, then the next step; or that nothing was needed. */
|
|
302
331
|
function initText(doc) {
|
|
303
332
|
if (doc.written.length === 0) return `Already initialized: ${doc.root} (kept ${doc.kept.join(", ")})`;
|
|
333
|
+
const skill = doc.skill !== null && doc.written.includes(doc.skill);
|
|
304
334
|
return [
|
|
305
|
-
`Initialized ${doc.root}`,
|
|
335
|
+
doc.written.every((p) => p === doc.skill || p === "AGENTS.md") ? "Set up the adr agent skill" : `Initialized ${doc.root}`,
|
|
306
336
|
...doc.written.map((p) => ` ${p}`),
|
|
307
337
|
...doc.kept.map((p) => `Kept ${p}`),
|
|
308
338
|
...doc.commit === null ? [] : [`Committed ${short$1(doc.commit)}.`],
|
|
309
|
-
"Next: adr new \"<title>\""
|
|
339
|
+
skill ? "Next: adr mcp, from your MCP client's config" : "Next: adr new \"<title>\""
|
|
310
340
|
].join("\n");
|
|
311
341
|
}
|
|
312
342
|
// src/cli/accept.ts
|
|
@@ -594,39 +624,68 @@ const indexCommand = {
|
|
|
594
624
|
}
|
|
595
625
|
};
|
|
596
626
|
// src/cli/init.ts
|
|
597
|
-
|
|
598
|
-
const
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
627
|
+
const DEFAULT_SKILL_DIR = ".claude/skills/adr";
|
|
628
|
+
const MAX_DIR = 1024;
|
|
629
|
+
/** `--skill-dir` as a repo-relative directory inside the work tree and outside `.git`. */
|
|
630
|
+
async function skillDir(root, cwd, given) {
|
|
631
|
+
const dir = given.trim();
|
|
632
|
+
const bad = () => new UsageError("--skill-dir must be a directory inside the repository");
|
|
633
|
+
if (dir === "" || dir.length > MAX_DIR || dir.includes("\0")) throw bad();
|
|
634
|
+
const base = await realpath(cwd).catch(() => cwd);
|
|
635
|
+
const rel = relative(root, resolve(base, dir));
|
|
636
|
+
if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) throw bad();
|
|
637
|
+
const segments = rel.split(sep).filter((s) => s !== "");
|
|
638
|
+
if (segments[0] === ".git") throw bad();
|
|
639
|
+
return segments.join("/");
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* `adr init [--template <name>]… [--skill [--skill-dir <path>]] [--commit | --no-commit]
|
|
643
|
+
* [-m <message>]` (design §6.2, phase 3 §9).
|
|
644
|
+
*/
|
|
645
|
+
function initCommand(version) {
|
|
646
|
+
return {
|
|
647
|
+
name: "init",
|
|
648
|
+
summary: "set up a decision log: a config file and starter templates",
|
|
649
|
+
usage: "init [--template <name>]… [--skill [--skill-dir <path>]] [--commit | --no-commit] [-m <message>]",
|
|
650
|
+
options: {
|
|
651
|
+
template: {
|
|
652
|
+
type: "string",
|
|
653
|
+
multiple: true
|
|
654
|
+
},
|
|
655
|
+
skill: { type: "boolean" },
|
|
656
|
+
"skill-dir": { type: "string" },
|
|
657
|
+
...COMMIT_OPTIONS
|
|
606
658
|
},
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
659
|
+
positionals: {
|
|
660
|
+
min: 0,
|
|
661
|
+
max: 0,
|
|
662
|
+
names: []
|
|
663
|
+
},
|
|
664
|
+
ref: "reject",
|
|
665
|
+
async run({ io, flags, values }) {
|
|
666
|
+
const templates = Array.isArray(values.template) ? values.template : void 0;
|
|
667
|
+
const given = typeof values["skill-dir"] === "string" ? values["skill-dir"] : null;
|
|
668
|
+
if (given !== null && values.skill !== true) throw new UsageError("--skill-dir needs --skill");
|
|
669
|
+
const log = await openForCli(io, flags);
|
|
670
|
+
const root = log.git.root ?? io.cwd;
|
|
671
|
+
const dir = values.skill !== true ? null : given === null ? DEFAULT_SKILL_DIR : await skillDir(root, io.cwd, given);
|
|
672
|
+
const doc = await initDecisionLog(log, {
|
|
673
|
+
...templates === void 0 ? {} : { templates },
|
|
674
|
+
...dir === null ? {} : { skill: {
|
|
675
|
+
dir,
|
|
676
|
+
version
|
|
677
|
+
} },
|
|
678
|
+
...commitChoice(values, { autoCommit: log.adr.autoCommit })
|
|
679
|
+
});
|
|
680
|
+
return {
|
|
681
|
+
exit: 0,
|
|
682
|
+
json: doc,
|
|
683
|
+
text: initText(doc),
|
|
684
|
+
notes: doc.notes
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
}
|
|
630
689
|
// src/cli/link.ts
|
|
631
690
|
/** `adr link <decision> <target> [--rel <rel>]` (design §6.7): a typed link, in any state. */
|
|
632
691
|
const linkCommand = {
|
|
@@ -728,6 +787,42 @@ const listCommand = {
|
|
|
728
787
|
};
|
|
729
788
|
}
|
|
730
789
|
};
|
|
790
|
+
// src/cli/mcp.ts
|
|
791
|
+
/**
|
|
792
|
+
* `adr mcp` (design §7.1): the MCP server over stdio. Loaded with a dynamic import, so no other
|
|
793
|
+
* command loads the MCP SDK.
|
|
794
|
+
*/
|
|
795
|
+
function mcpCommand(version) {
|
|
796
|
+
return {
|
|
797
|
+
name: "mcp",
|
|
798
|
+
summary: "start the MCP server over stdio",
|
|
799
|
+
usage: "mcp",
|
|
800
|
+
options: {},
|
|
801
|
+
positionals: {
|
|
802
|
+
min: 0,
|
|
803
|
+
max: 0,
|
|
804
|
+
names: []
|
|
805
|
+
},
|
|
806
|
+
ref: "reject",
|
|
807
|
+
async run({ io, flags }) {
|
|
808
|
+
if (flags.json) throw new UsageError("adr mcp speaks JSON-RPC on stdout; it takes no --json");
|
|
809
|
+
if (io.interactive) io.stderr.write("adr: adr mcp speaks MCP on stdin and stdout; start it from an MCP client\n");
|
|
810
|
+
const { serveAdrMcp } = await import("./server-CXCO5zfp.js");
|
|
811
|
+
await serveAdrMcp({
|
|
812
|
+
cwd: io.cwd,
|
|
813
|
+
root: flags.root,
|
|
814
|
+
env: io.env,
|
|
815
|
+
version,
|
|
816
|
+
stderr: io.stderr
|
|
817
|
+
});
|
|
818
|
+
return {
|
|
819
|
+
exit: 0,
|
|
820
|
+
json: null,
|
|
821
|
+
text: ""
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
};
|
|
825
|
+
}
|
|
731
826
|
// src/cli/editor.ts
|
|
732
827
|
/**
|
|
733
828
|
* The user's editor as an `edit` callback (design §5.7), or `null` when neither `VISUAL` nor
|
|
@@ -1066,6 +1161,38 @@ const scopeCommand = {
|
|
|
1066
1161
|
};
|
|
1067
1162
|
}
|
|
1068
1163
|
};
|
|
1164
|
+
// src/cli/search.ts
|
|
1165
|
+
/** `adr search <query>… [--all]` (design §8.2): words are joined, and every one must match. */
|
|
1166
|
+
const searchCommand = {
|
|
1167
|
+
name: "search",
|
|
1168
|
+
summary: "search decisions' ids, titles, bodies and notes",
|
|
1169
|
+
usage: "search <query>… [--all]",
|
|
1170
|
+
options: { all: { type: "boolean" } },
|
|
1171
|
+
positionals: {
|
|
1172
|
+
min: 1,
|
|
1173
|
+
max: 20,
|
|
1174
|
+
names: ["query"]
|
|
1175
|
+
},
|
|
1176
|
+
ref: "read",
|
|
1177
|
+
async run({ io, flags, values, positionals }) {
|
|
1178
|
+
const read = await readDecisions(await openForCli(io, flags));
|
|
1179
|
+
const doc = searchDecisions(read, {
|
|
1180
|
+
query: positionals.join(" "),
|
|
1181
|
+
includeInactive: values.all === true
|
|
1182
|
+
});
|
|
1183
|
+
const notes = [...notesWith(doc.notes, numberingNote(read, { unavailable: false }))];
|
|
1184
|
+
if (doc.inactive_omitted > 0) {
|
|
1185
|
+
const k = doc.inactive_omitted;
|
|
1186
|
+
notes.push(`${k === 1 ? "1 inactive decision also matches" : `${k} inactive decisions also match`}; add --all to see them`);
|
|
1187
|
+
}
|
|
1188
|
+
return {
|
|
1189
|
+
exit: 0,
|
|
1190
|
+
json: doc,
|
|
1191
|
+
text: searchText(doc),
|
|
1192
|
+
notes
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
};
|
|
1069
1196
|
// src/cli/show.ts
|
|
1070
1197
|
/** `adr show <decision> [--history]` (design §5.6). */
|
|
1071
1198
|
const showCommand = {
|
|
@@ -1203,7 +1330,7 @@ function adrCli(version) {
|
|
|
1203
1330
|
version,
|
|
1204
1331
|
summary: "Architecture decision records kept as event logs in git.",
|
|
1205
1332
|
commands: [
|
|
1206
|
-
initCommand,
|
|
1333
|
+
initCommand(version),
|
|
1207
1334
|
newCommand,
|
|
1208
1335
|
acceptCommand,
|
|
1209
1336
|
rejectCommand,
|
|
@@ -1218,7 +1345,9 @@ function adrCli(version) {
|
|
|
1218
1345
|
forCommand,
|
|
1219
1346
|
listCommand,
|
|
1220
1347
|
showCommand,
|
|
1221
|
-
|
|
1348
|
+
searchCommand,
|
|
1349
|
+
verify,
|
|
1350
|
+
mcpCommand(version)
|
|
1222
1351
|
]
|
|
1223
1352
|
};
|
|
1224
1353
|
}
|