@nikovirtala/adr 0.2.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.
Files changed (45) hide show
  1. package/README.md +77 -13
  2. package/dist/cli.js +348 -46
  3. package/dist/cli.js.map +1 -1
  4. package/dist/index.js +5 -3
  5. package/dist/init-YgW7g9Fx.js +588 -0
  6. package/dist/init-YgW7g9Fx.js.map +1 -0
  7. package/dist/{kind-BcHjCHvi.js → kind-DjvA3dyA.js} +2 -6
  8. package/dist/{kind-BcHjCHvi.js.map → kind-DjvA3dyA.js.map} +1 -1
  9. package/dist/kind.js +1 -1
  10. package/dist/list-Dt4KGxvj.js +58 -0
  11. package/dist/list-Dt4KGxvj.js.map +1 -0
  12. package/dist/{write-9IaqHRrY.js → search-CwIPlNGd.js} +1339 -560
  13. package/dist/search-CwIPlNGd.js.map +1 -0
  14. package/dist/server-CXCO5zfp.js +938 -0
  15. package/dist/server-CXCO5zfp.js.map +1 -0
  16. package/dist/types/cli/hooks.d.ts +6 -0
  17. package/dist/types/cli/index-command.d.ts +3 -0
  18. package/dist/types/cli/init.d.ts +5 -2
  19. package/dist/types/cli/list.d.ts +1 -1
  20. package/dist/types/cli/mcp.d.ts +6 -0
  21. package/dist/types/cli/open.d.ts +2 -0
  22. package/dist/types/cli/search.d.ts +3 -0
  23. package/dist/types/cli/text.d.ts +12 -0
  24. package/dist/types/commit-actions.d.ts +74 -0
  25. package/dist/types/config.d.ts +7 -1
  26. package/dist/types/hooks.d.ts +59 -0
  27. package/dist/types/index.d.ts +7 -2
  28. package/dist/types/init.d.ts +23 -2
  29. package/dist/types/log-index.d.ts +117 -0
  30. package/dist/types/log.d.ts +44 -1
  31. package/dist/types/mcp/policy.d.ts +33 -0
  32. package/dist/types/mcp/prompt.d.ts +7 -0
  33. package/dist/types/mcp/resources.d.ts +16 -0
  34. package/dist/types/mcp/server.d.ts +16 -0
  35. package/dist/types/mcp/text.d.ts +42 -0
  36. package/dist/types/mcp/tools.d.ts +27 -0
  37. package/dist/types/scope.d.ts +3 -1
  38. package/dist/types/search.d.ts +61 -0
  39. package/dist/types/skill.d.ts +5 -0
  40. package/dist/types/templates/list.d.ts +31 -0
  41. package/dist/types/verify.d.ts +6 -4
  42. package/dist/types/views.d.ts +9 -3
  43. package/dist/types/write.d.ts +65 -9
  44. package/package.json +5 -2
  45. package/dist/write-9IaqHRrY.js.map +0 -1
package/dist/cli.js CHANGED
@@ -1,8 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { n as DECISION_STATUSES, t as adrKind } from "./kind-BcHjCHvi.js";
3
- import { C as initDecisionLog, S as unavailableReason, a as noteDecision, c as scopeDecision, d as withdrawDecision, f as adrChecks, g as showDecision, h as listDecisions, i as linkDecision, m as decisionsFor, n as deprecateDecision, o as proposeDecision, r as followScopeRenames, s as rejectDecision, t as acceptDecision, u as supersedeDecision, v as openDecisionLog, x as resolveDecision, y as readDecisions } from "./write-9IaqHRrY.js";
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
+ 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
- import { UsageError, renderStateHeader, resolveAttribution, stagedRenames, templateHistoryRef } from "@nikovirtala/ledger";
6
+ import { UsageError, exitCode, findingsReport, formatText, renderStateHeader, resolveAttribution, stagedRenames, templateHistoryRef } from "@nikovirtala/ledger";
7
+ import { realpath } from "node:fs/promises";
8
+ import { isAbsolute, relative, resolve, sep } from "node:path";
6
9
  import { COMMIT_OPTIONS, commitChoice, confirm, processIO, runCli, verifyCommand } from "@nikovirtala/ledger/cli";
7
10
  import { spawn } from "node:child_process";
8
11
  // src/cli/open.ts
@@ -27,6 +30,8 @@ async function displayAfterWrite(log) {
27
30
  const read = await readDecisions(log);
28
31
  return { display: (id) => read.projection.views.get(id)?.display ?? id };
29
32
  }
33
+ /** `notes` with `note` appended when there is one. */
34
+ const notesWith = (notes, note) => note === null ? notes : [...notes, note];
30
35
  // src/cli/text.ts
31
36
  /** A table: each column padded to its widest value, trailing spaces removed. */
32
37
  function columns(rows) {
@@ -39,6 +44,21 @@ function columns(rows) {
39
44
  /** `adr list`: one row per decision, `→ <effective>` when it differs from the decision. */
40
45
  function listText(doc, display) {
41
46
  if (doc.decisions.length === 0) return "No decisions.";
47
+ return listRows(doc, display).join("\n");
48
+ }
49
+ /**
50
+ * `adr list --since <rev>` (design 2b D7): a heading, then each row followed by the event types
51
+ * added since `rev`; nothing at all when no decision changed, so the post-merge hook stays quiet.
52
+ */
53
+ function sinceText(doc, since, display) {
54
+ if (doc.decisions.length === 0) return "";
55
+ const rows = listRows(doc, display);
56
+ return [`Changed since ${since.rev} (${since.commit.slice(0, 7)}):`, ...doc.decisions.flatMap((v, i) => {
57
+ const types = [...new Set(since.events.filter((e) => e.subject === v.id).map((e) => e.type))];
58
+ return [rows[i] ?? "", ` [new: ${types.join(", ")}]`];
59
+ })].join("\n");
60
+ }
61
+ function listRows(doc, display) {
42
62
  return columns(doc.decisions.map((v) => {
43
63
  const arrow = v.effective === null ? "→ (cycle)" : v.effective === v.id ? "" : `→ ${display(v.effective)}`;
44
64
  return [
@@ -48,7 +68,7 @@ function listText(doc, display) {
48
68
  v.title,
49
69
  arrow
50
70
  ];
51
- }));
71
+ })).split("\n");
52
72
  }
53
73
  /** `ADR-0003 (m2x7p4d2)`, or the id alone when it has no number. */
54
74
  function named(id, display) {
@@ -132,7 +152,7 @@ function unfilledWarning(doc) {
132
152
  function findingLines(findings) {
133
153
  return findings.map((f) => ` ${f.file === null ? "" : `${f.file}: `}${f.message} [${f.rule}]`);
134
154
  }
135
- const short = (sha) => sha.slice(0, 7);
155
+ const short$1 = (sha) => sha.slice(0, 7);
136
156
  /**
137
157
  * `adr scope --follow-renames`: one `<named>: <old globs> → <new globs>` line per changed
138
158
  * decision (`affected`), whether or not a decision was given.
@@ -158,7 +178,8 @@ const VERBS = {
158
178
  deprecate: "deprecated",
159
179
  note: "noted",
160
180
  link: "linked",
161
- scope: "scoped"
181
+ scope: "scoped",
182
+ update: "updated"
162
183
  };
163
184
  /**
164
185
  * The text of a write command (design §5.6, §6.1). `subject` names what a refused command was
@@ -178,7 +199,7 @@ function writeText(doc, display, o) {
178
199
  if (o.followRenames !== void 0) return followRenamesText(doc, display, o.followRenames);
179
200
  const v = doc.decision;
180
201
  if (v === null) return "";
181
- const committed = doc.commit === null ? null : `Committed ${short(doc.commit)}`;
202
+ const committed = doc.commit === null ? null : `Committed ${short$1(doc.commit)}`;
182
203
  const recorded = committed === null ? "Staged; commit to record it." : `${committed}.`;
183
204
  switch (doc.command) {
184
205
  case "new": return [
@@ -225,6 +246,9 @@ function writeText(doc, display, o) {
225
246
  recorded
226
247
  ].join("\n");
227
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");
228
252
  }
229
253
  }
230
254
  /**
@@ -280,15 +304,39 @@ function forText(doc, display, keyword) {
280
304
  }
281
305
  return lines.join("\n");
282
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
+ }
283
330
  /** `adr init`: what was written and kept, then the next step; or that nothing was needed. */
284
331
  function initText(doc) {
285
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);
286
334
  return [
287
- `Initialized ${doc.root}`,
335
+ doc.written.every((p) => p === doc.skill || p === "AGENTS.md") ? "Set up the adr agent skill" : `Initialized ${doc.root}`,
288
336
  ...doc.written.map((p) => ` ${p}`),
289
337
  ...doc.kept.map((p) => `Kept ${p}`),
290
- ...doc.commit === null ? [] : [`Committed ${short(doc.commit)}.`],
291
- "Next: adr new \"<title>\""
338
+ ...doc.commit === null ? [] : [`Committed ${short$1(doc.commit)}.`],
339
+ skill ? "Next: adr mcp, from your MCP client's config" : "Next: adr new \"<title>\""
292
340
  ].join("\n");
293
341
  }
294
342
  // src/cli/accept.ts
@@ -366,12 +414,12 @@ const acceptCommand = {
366
414
  }
367
415
  };
368
416
  // src/cli/deprecate.ts
369
- const USAGE$4 = "deprecate <decision> --reason <text> [--commit | --no-commit] [-m <message>]";
417
+ const USAGE$5 = "deprecate <decision> --reason <text> [--commit | --no-commit] [-m <message>]";
370
418
  /** `adr deprecate` (design §6.5): an accepted decision no longer in force. */
371
419
  const deprecateCommand = {
372
420
  name: "deprecate",
373
421
  summary: "deprecate an accepted decision",
374
- usage: USAGE$4,
422
+ usage: USAGE$5,
375
423
  options: {
376
424
  reason: { type: "string" },
377
425
  ...COMMIT_OPTIONS
@@ -384,7 +432,7 @@ const deprecateCommand = {
384
432
  ref: "reject",
385
433
  async run({ io, flags, values, positionals }) {
386
434
  const input = positionals[0] ?? "";
387
- if (typeof values.reason !== "string" || values.reason.trim() === "") throw new UsageError(`missing --reason <text>; usage: adr ${USAGE$4}`);
435
+ if (typeof values.reason !== "string" || values.reason.trim() === "") throw new UsageError(`missing --reason <text>; usage: adr ${USAGE$5}`);
388
436
  const reason = typeof values.reason === "string" ? values.reason.trim() : null;
389
437
  const log = await openForCli(io, flags);
390
438
  const { actor, notes } = await resolveAttribution(log.git, { env: io.env });
@@ -435,21 +483,121 @@ const forCommand = {
435
483
  exit: 0,
436
484
  json: doc,
437
485
  text: forText(doc, display, read.adr.anchorKeyword),
438
- notes: doc.notes
486
+ notes: notesWith(doc.notes, numberingNote(read, { unavailable: false }))
439
487
  };
440
488
  }
441
489
  };
442
- // src/cli/init.ts
443
- /** `adr init [--template <name>]… [--commit | --no-commit] [-m <message>]` (design §6.2). */
444
- const initCommand = {
445
- name: "init",
446
- summary: "set up a decision log: a config file and starter templates",
447
- usage: "init [--template <name>]… [--commit | --no-commit] [-m <message>]",
490
+ // src/cli/hooks.ts
491
+ const USAGE$4 = "hooks install | uninstall | print --manager lefthook|husky|pre-commit | run pre-commit | trailers <message-file>";
492
+ const MANAGERS = [
493
+ "lefthook",
494
+ "husky",
495
+ "pre-commit"
496
+ ];
497
+ const outcome = (doc, text, notes) => ({
498
+ exit: 0,
499
+ json: doc,
500
+ text,
501
+ notes
502
+ });
503
+ /**
504
+ * `adr hooks install | uninstall | print --manager … | run pre-commit | trailers <message-file>`
505
+ * (design 2b D4, D8, §4.3): adr's git hooks through ledger's shims, shared with other ark tools.
506
+ */
507
+ const hooksCommand = {
508
+ name: "hooks",
509
+ summary: "install, remove or print adr's git hooks, and run their steps",
510
+ usage: USAGE$4,
511
+ options: { manager: { type: "string" } },
512
+ positionals: {
513
+ min: 1,
514
+ max: 2,
515
+ names: ["action", "argument"]
516
+ },
517
+ ref: "reject",
518
+ async run({ io, flags, values, positionals }) {
519
+ const [action = "", argument] = positionals;
520
+ const manager = values.manager;
521
+ if (manager !== void 0 && action !== "print") throw new UsageError(`--manager goes with hooks print only; usage: adr ${USAGE$4}`);
522
+ const noArgument = () => {
523
+ if (argument !== void 0) throw new UsageError(`unexpected argument ${JSON.stringify(argument)}; usage: adr ${USAGE$4}`);
524
+ };
525
+ switch (action) {
526
+ case "install": {
527
+ noArgument();
528
+ const log = await openForCli(io, flags);
529
+ const doc = await installDecisionHooks(log);
530
+ const lines = [...[...doc.written, ...doc.unchanged].map((p) => `${doc.written.includes(p) ? "wrote" : "unchanged"} ${p}`).sort((a, b) => order(a) - order(b)), `adr hooks: ${doc.registrations.map((r) => r.hook).join(", ")}`];
531
+ return outcome(doc, lines.join("\n"), log.notes);
532
+ }
533
+ case "uninstall": {
534
+ noArgument();
535
+ const log = await openForCli(io, flags);
536
+ const doc = await uninstallDecisionHooks(log);
537
+ const text = doc.written.length === 0 ? "adr hooks were not installed" : doc.written.map((p) => `removed adr from ${p}`).join("\n");
538
+ return outcome(doc, text, log.notes);
539
+ }
540
+ case "print": {
541
+ noArgument();
542
+ if (manager === void 0) throw new UsageError("hooks print needs --manager lefthook, husky or pre-commit");
543
+ if (!MANAGERS.includes(manager)) throw new UsageError(`unknown hook manager ${JSON.stringify(manager)}; use lefthook, husky or pre-commit`);
544
+ const log = await openForCli(io, flags);
545
+ const doc = printDecisionHooks(log, manager);
546
+ return outcome(doc, doc.snippet ?? "", log.notes);
547
+ }
548
+ case "run": {
549
+ if (argument !== "pre-commit") throw new UsageError("adr hooks run supports pre-commit");
550
+ const worktree = await openForCli(io, flags);
551
+ const index = await openForCli(io, flags, { reader: "index" });
552
+ const r = await runPreCommitHook({
553
+ worktree,
554
+ index,
555
+ env: io.env
556
+ });
557
+ return {
558
+ exit: r.exit,
559
+ json: findingsReport(r.findings, { failOn: r.failOn }),
560
+ text: r.findings.length === 0 ? "" : formatText(r.findings),
561
+ notes: r.notes
562
+ };
563
+ }
564
+ case "trailers": {
565
+ if (argument === void 0) throw new UsageError(`missing argument <message-file>; usage: adr ${USAGE$4}`);
566
+ const log = await openForCli(io, flags);
567
+ const doc = await addHookTrailers(log, resolve(io.cwd, argument));
568
+ return outcome(doc, "", log.notes);
569
+ }
570
+ default: throw new UsageError(`unknown hooks action ${JSON.stringify(action)}; use install, uninstall, print, run or trailers`);
571
+ }
572
+ }
573
+ };
574
+ /** A shim line's rank in hook order, for the install text. */
575
+ function order(line) {
576
+ return [
577
+ "pre-commit",
578
+ "commit-msg",
579
+ "pre-push",
580
+ "post-merge"
581
+ ].findIndex((h) => line.endsWith(`/${h}`));
582
+ }
583
+ // src/cli/index-command.ts
584
+ const short = (sha) => sha.slice(0, 7);
585
+ /** The text of `adr index` (design 2b §4.5). */
586
+ function indexText({ doc, refreshed }) {
587
+ const { path } = doc;
588
+ if (doc.check) return doc.findings.length === 0 ? `${path} is up to date` : formatText(doc.findings);
589
+ if (doc.commit !== null) return [`Committed ${path} as ${short(doc.commit)}`, ...refreshed ? [`Wrote ${path} with uncommitted log changes`] : []].join("\n");
590
+ if (refreshed) return `${path} is up to date in HEAD; wrote the work tree's copy, which includes uncommitted log changes`;
591
+ if (doc.fresh) return `${path} is up to date`;
592
+ return doc.written.length > 0 ? `Wrote ${path}` : `Staged ${path}`;
593
+ }
594
+ /** `adr index [--check] [--commit | --no-commit] [-m <message>]` (design 2b §4.5). */
595
+ const indexCommand = {
596
+ name: "index",
597
+ summary: "regenerate the decision index (index.md), or check that it is current",
598
+ usage: "index [--check] [--commit | --no-commit] [-m <message>]",
448
599
  options: {
449
- template: {
450
- type: "string",
451
- multiple: true
452
- },
600
+ check: { type: "boolean" },
453
601
  ...COMMIT_OPTIONS
454
602
  },
455
603
  positionals: {
@@ -457,22 +605,87 @@ const initCommand = {
457
605
  max: 0,
458
606
  names: []
459
607
  },
460
- ref: "reject",
608
+ ref: "read",
461
609
  async run({ io, flags, values }) {
462
- const templates = Array.isArray(values.template) ? values.template : void 0;
610
+ const check = values.check === true;
611
+ if (check && (values.commit === true || typeof values.message === "string")) throw new UsageError("--check writes nothing; drop --commit and --message");
612
+ if (!check && flags.ref !== null) throw new UsageError("adr index writes the work tree; use --ref only with --check");
463
613
  const log = await openForCli(io, flags);
464
- const doc = await initDecisionLog(log, {
465
- ...templates === void 0 ? {} : { templates },
466
- ...commitChoice(values, { autoCommit: log.adr.autoCommit })
614
+ const run = await runDecisionIndex(log, {
615
+ check,
616
+ ...check ? {} : commitChoice(values, { autoCommit: log.adr.autoCommit })
467
617
  });
468
618
  return {
469
- exit: 0,
470
- json: doc,
471
- text: initText(doc),
472
- notes: doc.notes
619
+ exit: check ? exitCode(run.doc.findings, { failOn: log.adr.failOn }) : 0,
620
+ json: run.doc,
621
+ text: indexText(run),
622
+ notes: run.doc.notes
473
623
  };
474
624
  }
475
625
  };
626
+ // src/cli/init.ts
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
658
+ },
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
+ }
476
689
  // src/cli/link.ts
477
690
  /** `adr link <decision> <target> [--rel <rel>]` (design §6.7): a typed link, in any state. */
478
691
  const linkCommand = {
@@ -524,15 +737,25 @@ function statuses(values) {
524
737
  return s;
525
738
  });
526
739
  }
527
- /** `adr list [--status <status>]…` (design §5.6). */
740
+ /** `--since`'s revision: non-empty and not an option. */
741
+ function sinceRev(values) {
742
+ const v = values.since;
743
+ if (typeof v !== "string") return null;
744
+ if (v.trim() === "" || v.startsWith("-")) throw new UsageError("--since must be a revision");
745
+ return v;
746
+ }
747
+ /** `adr list [--status <status>]… [--since <rev>]` (design §5.6, 2b D7). */
528
748
  const listCommand = {
529
749
  name: "list",
530
750
  summary: "list decisions with their computed status",
531
- usage: "list [--status <status>]…",
532
- options: { status: {
533
- type: "string",
534
- multiple: true
535
- } },
751
+ usage: "list [--status <status>]… [--since <rev>]",
752
+ options: {
753
+ status: {
754
+ type: "string",
755
+ multiple: true
756
+ },
757
+ since: { type: "string" }
758
+ },
536
759
  positionals: {
537
760
  min: 0,
538
761
  max: 0,
@@ -541,10 +764,17 @@ const listCommand = {
541
764
  ref: "read",
542
765
  async run({ io, flags, values }) {
543
766
  const status = statuses(values);
544
- const read = await readDecisions(await openForCli(io, flags));
545
- const doc = listDecisions(read, { status });
767
+ const rev = sinceRev(values);
768
+ const log = await openForCli(io, flags);
769
+ const since = rev === null ? null : await changedSince(log, rev);
770
+ const read = await readDecisions(log);
771
+ const doc = listDecisions(read, {
772
+ status,
773
+ since
774
+ });
546
775
  const notes = [...doc.notes];
547
- if (!read.numbering.available && doc.decisions.length > 0) notes.push(`numbers are unavailable (${unavailableReason(read.numbering)}); decisions display by id`);
776
+ const numbering = numberingNote(read, { unavailable: true });
777
+ if (numbering !== null && doc.decisions.length > 0) notes.push(numbering);
548
778
  if (doc.findings.length > 0) notes.push(`the decision log has ${plural(doc.findings.length, "problem", "problems")}; run adr verify`);
549
779
  if (doc.unreadable.length > 0) notes.push(`${plural(doc.unreadable.length, "decision uses", "decisions use")} a newer format; upgrade adr`);
550
780
  if (doc.unfoldable.length > 0) notes.push(`${plural(doc.unfoldable.length, "decision", "decisions")} can't be read; run adr verify`);
@@ -552,11 +782,47 @@ const listCommand = {
552
782
  return {
553
783
  exit: 0,
554
784
  json: doc,
555
- text: listText(doc, display),
785
+ text: doc.since === null ? listText(doc, display) : sinceText(doc, doc.since, display),
556
786
  notes
557
787
  };
558
788
  }
559
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
+ }
560
826
  // src/cli/editor.ts
561
827
  /**
562
828
  * The user's editor as an `edit` callback (design §5.7), or `null` when neither `VISUAL` nor
@@ -895,6 +1161,38 @@ const scopeCommand = {
895
1161
  };
896
1162
  }
897
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
+ };
898
1196
  // src/cli/show.ts
899
1197
  /** `adr show <decision> [--history]` (design §5.6). */
900
1198
  const showCommand = {
@@ -917,7 +1215,7 @@ const showCommand = {
917
1215
  exit: 0,
918
1216
  json: doc,
919
1217
  text: showText(doc, display),
920
- notes: doc.notes
1218
+ notes: notesWith(doc.notes, numberingNote(read, { unavailable: false }))
921
1219
  };
922
1220
  }
923
1221
  };
@@ -1032,7 +1330,7 @@ function adrCli(version) {
1032
1330
  version,
1033
1331
  summary: "Architecture decision records kept as event logs in git.",
1034
1332
  commands: [
1035
- initCommand,
1333
+ initCommand(version),
1036
1334
  newCommand,
1037
1335
  acceptCommand,
1038
1336
  rejectCommand,
@@ -1042,10 +1340,14 @@ function adrCli(version) {
1042
1340
  noteCommand,
1043
1341
  linkCommand,
1044
1342
  scopeCommand,
1343
+ indexCommand,
1344
+ hooksCommand,
1045
1345
  forCommand,
1046
1346
  listCommand,
1047
1347
  showCommand,
1048
- verify
1348
+ searchCommand,
1349
+ verify,
1350
+ mcpCommand(version)
1049
1351
  ]
1050
1352
  };
1051
1353
  }