@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
@@ -32,9 +32,18 @@ export interface DecisionsRead {
32
32
  readonly findings: readonly Finding[];
33
33
  /** How the log was located (`ToolLog.notes`); `[]` for a MemoryTree read. */
34
34
  readonly notes: readonly string[];
35
+ /** The `[[sources]]` names in the config; their logs aren't read yet (ark's step 4). */
36
+ readonly sources: readonly string[];
35
37
  }
36
- /** `numberLog` on the repository, then `readDecisionsFrom(log.tree, …)`. */
38
+ /**
39
+ * `numberLog` on the repository, then `readDecisionsFrom(log.tree, …)`. In a shallow clone of a
40
+ * log with `[log] index = "committed"`, the numbers come from `<root>/index.md` in the commit
41
+ * being read (`--ref`, else HEAD, resolved when the numbers are needed), never from the work tree's
42
+ * copy, which may number acceptances that exist only there (design 2b D1, §4.6).
43
+ */
37
44
  export declare function readDecisions(log: DecisionLog): Promise<DecisionsRead>;
45
+ /** The display numbers `readDecisions` uses, with its shallow-clone fallback to `index.md`. */
46
+ export declare function numberDecisions(log: DecisionLog): Promise<Numbering>;
38
47
  /** The git-free core of `readDecisions`; tests pass a MemoryTree and a hand-made `Numbering`. */
39
48
  export declare function readDecisionsFrom(tree: TreeReader, o: {
40
49
  readonly layout: LogLayout;
@@ -43,7 +52,22 @@ export declare function readDecisionsFrom(tree: TreeReader, o: {
43
52
  readonly ref?: string | null;
44
53
  readonly commit?: string | null;
45
54
  readonly notes?: readonly string[];
55
+ readonly sources?: readonly string[];
46
56
  }): Promise<DecisionsRead>;
57
+ /**
58
+ * The note listing reads add while only the local log is read: the logs of `[[sources]]` aren't
59
+ * listed or searched until ark's step 4. `null` without sources.
60
+ */
61
+ export declare function sourcesNote(read: DecisionsRead): string | null;
62
+ /** `read.notes`, then the sources note when there is one. */
63
+ export declare function listingNotes(read: DecisionsRead): readonly string[];
64
+ /**
65
+ * The note a read command adds about where numbers come from: the shallow-clone index, or why
66
+ * they are unavailable (`unavailable: false` leaves that one out); `null` for none.
67
+ */
68
+ export declare function numberingNote(read: DecisionsRead, o: {
69
+ readonly unavailable: boolean;
70
+ }): string | null;
47
71
  /** Why numbers are unavailable, in words: `shallow clone` or `no main branch`. */
48
72
  export declare function unavailableReason(n: Numbering): string;
49
73
  /**
@@ -51,3 +75,22 @@ export declare function unavailableReason(n: Numbering): string;
51
75
  * Throws `UsageError` (exit 2) naming the reason when it names nothing.
52
76
  */
53
77
  export declare function resolveDecision(read: DecisionsRead, input: string): string;
78
+ /** The `since` key of `adr list --since <rev>` (design 2b D7). */
79
+ export interface ChangedSince {
80
+ /** The revision as given, such as `ORIG_HEAD`. */
81
+ readonly rev: string;
82
+ readonly commit: string;
83
+ /** Event files added since `rev`, sorted by path. */
84
+ readonly events: readonly {
85
+ readonly subject: string;
86
+ readonly type: string;
87
+ readonly path: string;
88
+ }[];
89
+ }
90
+ /**
91
+ * The event files in `--ref`'s tree (else HEAD's) that `rev`'s tree lacks (design 2b D7). Diffing
92
+ * trees answers "what did this pull bring" for fast-forwards and merges (`ORIG_HEAD` is the tip
93
+ * before them), and lists nothing for a rebase of your own work, whose events are in both trees.
94
+ * Throws `RefNotFoundError` when either revision doesn't resolve.
95
+ */
96
+ export declare function changedSince(log: DecisionLog, rev: string): Promise<ChangedSince>;
@@ -0,0 +1,33 @@
1
+ import type { Actor, Finding } from "@nikovirtala/ledger";
2
+ import type { AdrConfig } from "../config.js";
3
+ import { type DecisionView } from "../kind.js";
4
+ import type { DecisionsRead } from "../log.js";
5
+ /** The tools that decide; listed only with `[policy] mcp_can_decide = true`. */
6
+ export declare const DECISION_TOOLS: readonly ["adr_accept", "adr_reject", "adr_deprecate", "adr_supersede"];
7
+ /** Whether `adr mcp` offers the decision tools (design §7.5, Table A). */
8
+ export declare function decisionToolsAvailable(adr: AdrConfig): boolean;
9
+ /**
10
+ * Who a proposal is for (design D5): its `proposed` event's actor, `on_behalf_of` for an agent
11
+ * and `email` for a human, lower-cased; `null` when the event or the address is missing.
12
+ */
13
+ export declare function proposalOwner(read: DecisionsRead, id: string): string | null;
14
+ /** The writes `mcpWriteRefusal` judges: the ones whose ownership matters. */
15
+ export type PolicyTool = "adr_scope" | "adr_update_draft" | "adr_withdraw";
16
+ /**
17
+ * The MCP policy (design §7.5, Table B): `null` when the write may go ahead, else the
18
+ * `decision/mcp-cannot-decide` finding. Without `mcp_can_decide` an agent may scope, edit and
19
+ * withdraw only the proposals made for the person it acts for, and rescope no accepted or
20
+ * deprecated decision.
21
+ * Writes the state machine forbids anyway get `null`, so the library refuses them with the real
22
+ * reason.
23
+ */
24
+ export declare function mcpWriteRefusal(o: {
25
+ readonly tool: PolicyTool;
26
+ readonly view: DecisionView;
27
+ readonly owner: string | null;
28
+ readonly actor: Actor;
29
+ readonly mcpCanDecide: boolean;
30
+ readonly display: (id: string) => string;
31
+ /** The finding's file when the decision has no body: its events directory. */
32
+ readonly eventsDir: string;
33
+ }): Finding | null;
@@ -0,0 +1,7 @@
1
+ import type { McpPrompt } from "@nikovirtala/ledger/mcp";
2
+ import { type AdrMcpEnv } from "./tools.js";
3
+ /**
4
+ * `adr_draft_from_discussion` (design §7.8): the steps from a discussion to a proposal, with the
5
+ * template's sections spelled out, ending with a human's `adr accept`.
6
+ */
7
+ export declare function draftPrompt(e: AdrMcpEnv): McpPrompt;
@@ -0,0 +1,16 @@
1
+ import type { McpResource, McpResourceTemplate } from "@nikovirtala/ledger/mcp";
2
+ import { type DecisionsRead } from "../log.js";
3
+ import { type AdrMcpEnv } from "./tools.js";
4
+ /**
5
+ * Where an MCP client reads a decision: `adr://decision/ADR-0012` when numbered, else
6
+ * `adr://decision/<id>`. The `refer` of every header the MCP texts show.
7
+ */
8
+ export declare function decisionUri(read: DecisionsRead): (id: string) => string;
9
+ /** `adr://index`: the index table, numbered like every other read (design §7.7). */
10
+ export declare function indexResource(e: AdrMcpEnv): McpResource;
11
+ /**
12
+ * `adr://decision/{id}`: the decision's state header, then its body. `{id}` is the id, the number
13
+ * (`12`, `0012`) or `ADR-0012`; an unknown one is not found, a malformed or qualified one is the
14
+ * caller's error.
15
+ */
16
+ export declare function decisionResource(e: AdrMcpEnv): McpResourceTemplate;
@@ -0,0 +1,16 @@
1
+ import { type McpServeOptions } from "@nikovirtala/ledger/mcp";
2
+ export declare const INSTRUCTIONS = "Architecture decisions in this repository are managed by adr. Decision files don't show their status: read decisions with adr_get or adr://decision/{id}, check adr_for_path and adr_search before changing architecture, and propose changes with adr_propose; a human accepts them.";
3
+ /**
4
+ * `adr mcp` (design §7.2): checks `ARK_ON_BEHALF_OF` and opens the log first, so a bad
5
+ * environment exits 2 before serving, then serves adr's tools over stdio until the client goes.
6
+ */
7
+ export declare function serveAdrMcp(o: {
8
+ readonly cwd: string;
9
+ readonly root: string | null;
10
+ readonly env: NodeJS.ProcessEnv;
11
+ readonly version: string;
12
+ readonly stderr: {
13
+ write(s: string): void;
14
+ };
15
+ readonly transport?: McpServeOptions["transport"];
16
+ }): Promise<void>;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * The readable text of each MCP tool result (design §7.4, last column). The documents are the
3
+ * CLI's; these texts are MCP's own, and their state headers point at `adr://decision/…`.
4
+ */
5
+ import { type StateHeader } from "@nikovirtala/ledger";
6
+ import type { McpToolResult } from "@nikovirtala/ledger/mcp";
7
+ import type { DecisionView } from "../kind.js";
8
+ import type { DecisionsRead } from "../log.js";
9
+ import type { DecisionsForJson } from "../scope.js";
10
+ import type { DecisionSearchJson } from "../search.js";
11
+ import type { TemplatesJson } from "../templates/list.js";
12
+ import { type DecisionListJson, type DecisionShowJson } from "../views.js";
13
+ import type { WriteResultJson } from "../write.js";
14
+ type Text = Omit<McpToolResult, "document">;
15
+ /** The decision's state header with MCP's `adr://` references. */
16
+ export declare function mcpHeader(view: DecisionView, read: DecisionsRead): StateHeader;
17
+ /** `adr_search`: each result's header, title and excerpt, then the counts. */
18
+ export declare function searchText(doc: DecisionSearchJson, read: DecisionsRead): Text;
19
+ /** `adr_get`: the header, the decision's fields as `adr show` lists them, then the body. */
20
+ export declare function getText(doc: DecisionShowJson, read: DecisionsRead): Text;
21
+ /** `adr_list`: one line per decision. */
22
+ export declare function listText(doc: DecisionListJson, read: DecisionsRead): Text;
23
+ /** `adr_templates`: each template, then its sections. */
24
+ export declare function templatesText(doc: TemplatesJson): Text;
25
+ /** `adr_for_path`: per path, each decision's header and how it matched. */
26
+ export declare function forText(doc: DecisionsForJson, read: DecisionsRead): Text;
27
+ /** What a write's text needs besides the document. */
28
+ export interface WriteTextOptions {
29
+ /** The log read after the write, for the headers and how decisions display. */
30
+ readonly read: DecisionsRead;
31
+ /** For `adr_propose`: whether a human must accept. */
32
+ readonly acceptRequiresHuman?: boolean;
33
+ /** For `adr_scope`: the globs before the write. */
34
+ readonly scopeBefore?: readonly string[];
35
+ /** For `adr_update_draft`: the section ids sent. */
36
+ readonly sections?: readonly string[];
37
+ /** The tool, for the refusal's last line. */
38
+ readonly tool: string;
39
+ }
40
+ /** A write's text: `Refused:` and the findings, or what was written; then the notes. */
41
+ export declare function writeText(doc: WriteResultJson, o: WriteTextOptions): Text;
42
+ export {};
@@ -0,0 +1,27 @@
1
+ import { type JsonValue } from "@nikovirtala/ledger";
2
+ import { type McpTool } from "@nikovirtala/ledger/mcp";
3
+ import { type DecisionLog } from "../log.js";
4
+ /** Where `adr mcp` runs: every call opens the log afresh from here (design D2). */
5
+ export interface AdrMcpEnv {
6
+ readonly cwd: string;
7
+ readonly root: string | null;
8
+ readonly env: NodeJS.ProcessEnv;
9
+ }
10
+ type Schema = Readonly<Record<string, JsonValue>>;
11
+ export declare const ID: Schema;
12
+ export declare const READ: {
13
+ readonly readOnlyHint: true;
14
+ readonly openWorldHint: false;
15
+ };
16
+ export declare const WRITE: {
17
+ readonly readOnlyHint: false;
18
+ readonly destructiveHint: false;
19
+ readonly openWorldHint: false;
20
+ };
21
+ /** The log for one call, opened again so outside edits and `[policy]` changes are seen. */
22
+ export declare const openLog: (e: AdrMcpEnv, ref?: string | null) => Promise<DecisionLog>;
23
+ /** The eleven tools `adr mcp` always lists (design §7.4). */
24
+ export declare function alwaysOnTools(e: AdrMcpEnv): McpTool[];
25
+ /** The four tools `adr mcp` lists only with `[policy] mcp_can_decide = true` (design §7.4). */
26
+ export declare function decisionTools(e: AdrMcpEnv): McpTool[];
27
+ export {};
@@ -1,6 +1,6 @@
1
1
  import { type Finding, type ScopeVia } from "@nikovirtala/ledger";
2
2
  import type { DecisionStatus } from "./kind.js";
3
- import type { DecisionLog, DecisionsRead } from "./log.js";
3
+ import { type DecisionLog, type DecisionsRead } from "./log.js";
4
4
  import { type NumberingJson } from "./views.js";
5
5
  /** One decision that governs a path in `adr-for/v1` (design §6.9). */
6
6
  export interface DecisionForJson {
@@ -39,6 +39,8 @@ export interface DecisionsForJson {
39
39
  readonly findings: readonly Finding[];
40
40
  readonly notes: readonly string[];
41
41
  }
42
+ /** The order `adr for` and `adr search` list statuses in: in force first. */
43
+ export declare const STATUS_ORDER: readonly DecisionStatus[];
42
44
  /**
43
45
  * Which decisions govern each path (design §6.9): globs of decisions that were ever in force, and
44
46
  * comment anchors of any decision, read from the work tree or, with a `--ref` log, that commit.
@@ -0,0 +1,61 @@
1
+ import { type Finding, type StateHeader } from "@nikovirtala/ledger";
2
+ import type { DecisionView } from "./kind.js";
3
+ import { type DecisionsRead } from "./log.js";
4
+ import { type NumberingJson } from "./views.js";
5
+ export type SearchField = "id" | "title" | "body" | "notes";
6
+ /** One decision `adr search` found. */
7
+ export interface DecisionSearchResultJson {
8
+ readonly decision: DecisionView;
9
+ /** `stateHeader(view, read)`: the CLI's wording. */
10
+ readonly header: StateHeader;
11
+ readonly score: number;
12
+ /** The fields with a hit, in the order id, title, body, notes (the display number is `id`). */
13
+ readonly matched: readonly SearchField[];
14
+ /** The best matching line of the body or a note, cut to about 160 characters; `null` if none. */
15
+ readonly excerpt: string | null;
16
+ }
17
+ /** `adr search <query>… --json` and `adr_search` (design §6.4). */
18
+ export interface DecisionSearchJson {
19
+ readonly format: "adr-search/v1";
20
+ readonly root: string;
21
+ readonly ref: string | null;
22
+ readonly commit: string | null;
23
+ readonly numbering: NumberingJson;
24
+ /** As given, trimmed. */
25
+ readonly query: string;
26
+ /** Folded, deduplicated, in query order. */
27
+ readonly terms: readonly string[];
28
+ readonly include_inactive: boolean;
29
+ /** Matching decisions before `limit`. */
30
+ readonly total: number;
31
+ /** Decisions that match but aren't active; `0` with `include_inactive`. */
32
+ readonly inactive_omitted: number;
33
+ readonly results: readonly DecisionSearchResultJson[];
34
+ /** The read's findings of the listed decisions and of the log as a whole. */
35
+ readonly findings: readonly Finding[];
36
+ /** `read.notes`, then the sources note. */
37
+ readonly notes: readonly string[];
38
+ }
39
+ /** A folded string and, for every folded UTF-16 offset (and its end), the original offset. */
40
+ interface Folded {
41
+ readonly text: string;
42
+ readonly at: readonly number[];
43
+ }
44
+ /**
45
+ * Case- and accent-insensitive form: per code point NFKD, combining marks dropped, lower-cased.
46
+ * `Café` → `cafe` and `fi` → `fi`; `ß` and `ø` have no decomposition and stay as they are.
47
+ */
48
+ export declare function fold(s: string): Folded;
49
+ /**
50
+ * Full-text search of the log (design §8.1): a decision matches when every term of the folded
51
+ * query is a substring of its folded id, display number, title, body or note texts. Ranked by
52
+ * score (10 for a term equal to the id or number, 5 in the title, up to 3 for body hits and 2
53
+ * for note hits), then status as `adr for` orders them, then list order. Only proposed and
54
+ * accepted decisions unless `includeInactive`. Throws `UsageError` for an invalid query.
55
+ */
56
+ export declare function searchDecisions(read: DecisionsRead, o: {
57
+ readonly query: string;
58
+ readonly includeInactive?: boolean;
59
+ readonly limit?: number;
60
+ }): DecisionSearchJson;
61
+ export {};
@@ -0,0 +1,5 @@
1
+ /**
2
+ * The agent skill `adr init --skill` writes (01, "Agent skill (SKILL.md)"): the judgment of when
3
+ * to use adr's MCP tools. `skill.test.ts` keeps it equal to the block in docs/01-adr-v1.md.
4
+ */
5
+ export declare const SKILL_MD = "---\nname: adr\ndescription: Use before changing architecture, data stores, integration patterns, dependencies or cross-cutting conventions, and whenever asked to record, propose or look up an architectural decision.\n---\n\n# Architectural decisions (adr)\n\nDecisions in this repo are managed by adr. Decision files do not\nshow their own status, so never judge from the raw files whether a\ndecision applies.\n\n## Before changing architecture\n\n1. Call adr_for_path for the files you will touch, and adr_search\n for the area you are about to change. adr_search returns active\n decisions only; adr_for_path also lists inactive ones, each with\n a warning.\n2. If your change conflicts with an accepted decision, stop and tell\n the user which ADR it conflicts with. Do not work around it silently.\n3. If the user still wants the change, propose a superseding ADR.\n\n## Reading decisions\n\n- Use adr_get or adr://decision/{id}, never docs/adr/decisions/*.md.\n- If a response says SUPERSEDED or DEPRECATED, follow it to the\n effective decision before acting.\n- If it says REJECTED or WITHDRAWN, the decision was never in force;\n do not apply it.\n\n## Proposing decisions\n\n1. Call adr_templates and fill every required section.\n2. Use only facts from the conversation and the code. Mark unknowns\n as open questions instead of inventing them.\n3. List at least two options considered when the template has that section.\n4. Set supersedes when the proposal replaces an existing decision.\n5. Do not accept your own proposal. Tell the user the ADR id and\n that they can review it and run: adr accept <id>\n6. If the user decides against your proposal, withdraw it with\n adr_withdraw and give their reason. Don't withdraw other\n people's proposals; tell the user instead.\n\n## Changing scope\n\n- When you move or rename code covered by a decision, let the\n pre-commit hook update its globs (adr hooks install). For your\n own proposals, you can use adr_scope. Rescoping an accepted\n decision needs mcp_can_decide; otherwise tell the user and\n suggest adr scope <id> \u2026.\n- Before any scope change, read the decision's Scope section. If it\n would no longer read true after the change, do not rescope: tell\n the user the decision itself is changing and propose a\n superseding ADR.\n\n## Never\n\n- Edit an accepted decision body, any file under docs/adr/events/,\n or index.md by hand.\n- Use adr_note to change the meaning of a decision. Notes clarify;\n changed decisions need a superseding ADR.\n\n## Without the MCP server\n\nUse the CLI with --json: adr search, adr for, adr show, adr new,\nadr note. Set ARK_AGENT=<your name> and ARK_ON_BEHALF_OF=<the user's\nemail> so your writes are attributed.\n";
@@ -0,0 +1,31 @@
1
+ import { type Finding } from "@nikovirtala/ledger";
2
+ import type { DecisionsRead } from "../log.js";
3
+ /** `adr_templates` (and phase 4's `adr template list --json`): the templates a proposal can use. */
4
+ export interface TemplatesJson {
5
+ readonly format: "adr-templates/v1";
6
+ readonly root: string;
7
+ /** `[log] default_template`. */
8
+ readonly default: string;
9
+ readonly templates: readonly {
10
+ readonly name: string;
11
+ readonly version: number;
12
+ readonly source: "repo" | "builtin";
13
+ /** `<root>/templates/<name>.md` for a repo template; `null` for a built-in one. */
14
+ readonly path: string | null;
15
+ readonly sections: readonly {
16
+ readonly id: string;
17
+ readonly heading: string;
18
+ readonly required: boolean;
19
+ readonly guidance: string | null;
20
+ }[];
21
+ }[];
22
+ /** Why a repo template is left out: it doesn't parse or fails the template lint. */
23
+ readonly findings: readonly Finding[];
24
+ readonly notes: readonly string[];
25
+ }
26
+ /**
27
+ * The templates of `<root>/templates` that parse and pass the template lint, and the built-in
28
+ * ones no repo template or file of the same name shadows (`adr new` copies those on first use),
29
+ * sorted by name.
30
+ */
31
+ export declare function listTemplates(read: DecisionsRead): Promise<TemplatesJson>;
@@ -1,9 +1,11 @@
1
1
  import { type Check, type Finding, type VerifyMode } from "@nikovirtala/ledger";
2
- import type { DecisionLog } from "./log.js";
2
+ import { type DecisionLog } from "./log.js";
3
3
  /**
4
- * Every phase-1 check for the log: ledger's mechanical and fold checks with adr's actor policy,
5
- * and template lint against the templates in the head tree and, for older versions, the history
6
- * of `historyRef`.
4
+ * Every check for the log: ledger's mechanical and fold checks with adr's actor policy, template
5
+ * lint against the templates in the head tree and, for older versions, the history of
6
+ * `historyRef`, the scope-glob and anchor checks, and the decision checks over code
7
+ * (`decision/governed-by-inactive`, `decision/scope-narrowed`). With `[log] index = "committed"`,
8
+ * also the freshness of `<root>/index.md`.
7
9
  */
8
10
  export declare function adrChecks(log: DecisionLog, historyRef: string | null): readonly Check[];
9
11
  /** Verifies the decision log in `mode` (staged, range or full) with `adrChecks`. */
@@ -1,10 +1,10 @@
1
1
  import { type EventEnvelope, type Finding, type StateHeader } from "@nikovirtala/ledger";
2
2
  import type { DecisionStatus, DecisionView } from "./kind.js";
3
- import type { DecisionsRead } from "./log.js";
3
+ import { type ChangedSince, type DecisionsRead } from "./log.js";
4
4
  /** The `numbering` key of the read documents. */
5
5
  export interface NumberingJson {
6
6
  readonly available: boolean;
7
- readonly reason: "shallow" | "no-main-ref" | null;
7
+ readonly reason: "shallow" | "no-main-ref" | "index" | null;
8
8
  readonly ref: string | null;
9
9
  }
10
10
  /** `adr list --json` (design §5.5). */
@@ -15,6 +15,8 @@ export interface DecisionListJson {
15
15
  readonly commit: string | null;
16
16
  readonly numbering: NumberingJson;
17
17
  readonly decisions: readonly DecisionView[];
18
+ /** With `--since <rev>`: the event files added since `rev`; `null` without it. */
19
+ readonly since: ChangedSince | null;
18
20
  /** Newer envelope or schema versions; upgrade adr. */
19
21
  readonly unreadable: readonly string[];
20
22
  /** First event not `proposed`, or a body with no events; run adr verify. */
@@ -39,12 +41,16 @@ export interface DecisionShowJson {
39
41
  }
40
42
  /** The `numbering` key of `adr-list/v1`, `adr-show/v1` and `adr-for/v1`. */
41
43
  export declare const numberingJson: (read: DecisionsRead) => NumberingJson;
44
+ /** Numbered decisions by number, then unnumbered ones by `proposed_at`, then id. */
45
+ export declare function compareViews(a: DecisionView, b: DecisionView): number;
42
46
  /**
43
- * The `adr-list/v1` document: decisions in list order, filtered by `status` and truncated to
47
+ * The `adr-list/v1` document: decisions in list order, filtered by `status` and by `since` (only
48
+ * the decisions with event files added since its revision, from `changedSince`), and truncated to
44
49
  * `limit` after ordering. `findings` are those of the listed decisions and of the log as a whole.
45
50
  */
46
51
  export declare function listDecisions(read: DecisionsRead, o?: {
47
52
  readonly status?: readonly DecisionStatus[];
53
+ readonly since?: ChangedSince | null;
48
54
  readonly limit?: number;
49
55
  }): DecisionListJson;
50
56
  /**
@@ -1,15 +1,17 @@
1
- import { type Actor, type EventEnvelope, type Finding, type SetOp, type Template, type TemplateSource, type Trailer } from "@nikovirtala/ledger";
1
+ import { type Actor, type EventEnvelope, type Finding, type SetOp, type Template, type TemplateSource, type Trailer, type TreeReader } from "@nikovirtala/ledger";
2
2
  import { type DecisionView } from "./kind.js";
3
3
  import { type DecisionLog, type DecisionsRead } from "./log.js";
4
4
  /** `adr <write command> --json` (design §5.5, §6.1). */
5
5
  export interface WriteResultJson {
6
6
  readonly format: "adr-write/v1";
7
- readonly command: "new" | "accept" | "supersede" | "reject" | "withdraw" | "deprecate" | "note" | "link" | "scope";
7
+ readonly command: "new" | "accept" | "supersede" | "reject" | "withdraw" | "deprecate" | "note" | "link" | "scope" | "update";
8
8
  readonly ok: boolean;
9
9
  /**
10
10
  * The decision after the write; before it when `ok` is false (`null` for a refused `new`).
11
11
  * After a commit it is read again from the log, so an acceptance committed on the main ref
12
- * carries its number; otherwise `number` is `null` until the acceptance lands on main.
12
+ * carries its number; otherwise `number` is `null` until the acceptance lands on main. In a
13
+ * shallow clone with a committed index, the number comes from HEAD's `index.md`, which on a
14
+ * branch is the number the decision will get once the branch merges.
13
15
  */
14
16
  readonly decision: DecisionView | null;
15
17
  /** Each decision superseded by this write, after it (read again after a commit, like `decision`). */
@@ -33,6 +35,11 @@ export interface WriteOptions {
33
35
  readonly message?: string | null;
34
36
  /** Notes to report before the write's own, such as the attribution notes. */
35
37
  readonly notes?: readonly string[];
38
+ /**
39
+ * With `[log] index = "committed"`: `"refresh"` (the default) keeps `<root>/index.md` fresh;
40
+ * `"skip"` leaves it alone (the pre-commit hook's rename following, design 2b D8).
41
+ */
42
+ readonly index?: "refresh" | "skip";
36
43
  }
37
44
  export interface ProposeInput {
38
45
  readonly title: string;
@@ -61,6 +68,14 @@ export interface WritePlan {
61
68
  readonly subject: string;
62
69
  /** New files, repo-relative path → bytes, in write order: body, materialized template, events. */
63
70
  readonly files: Readonly<Record<string, Uint8Array>>;
71
+ /**
72
+ * Existing files to replace, path → the new bytes and the bytes the plan read; written only if
73
+ * the file still holds `expected` (a draft update).
74
+ */
75
+ readonly replace?: Readonly<Record<string, {
76
+ readonly bytes: Uint8Array;
77
+ readonly expected: Uint8Array;
78
+ }>>;
64
79
  readonly events: readonly EventEnvelope[];
65
80
  /** Paths to stage: every new file, plus the existing body for accept. */
66
81
  readonly stage: readonly string[];
@@ -119,6 +134,21 @@ export type PlanResult = {
119
134
  export declare function planPropose(read: DecisionsRead, input: ProposeInput, c: PlanContext, o?: {
120
135
  readonly refuseIncomplete?: boolean;
121
136
  }): Promise<PlanResult>;
137
+ /** Input of a draft update: the decision and the sections to replace (section id → markdown). */
138
+ export interface UpdateDraftInput {
139
+ readonly id: string;
140
+ readonly sections: Readonly<Record<string, string>>;
141
+ }
142
+ /**
143
+ * Plans a draft update (design §6.6): the given sections of a proposed decision's body replaced in
144
+ * place, every other line kept, written only if the body still holds the bytes read. Refused
145
+ * (`ok: false`) when the decision isn't proposed or its body or template version is missing or
146
+ * unreadable, and with `refuseIncomplete` when the new draft fails its template lint. An unknown
147
+ * section, a missing heading or a heading in the content is a `UsageError`.
148
+ */
149
+ export declare function planUpdateDraft(read: DecisionsRead, input: UpdateDraftInput, c: PlanContext, o?: {
150
+ readonly refuseIncomplete?: boolean;
151
+ }): Promise<PlanResult>;
122
152
  /** Plans `adr accept`: `accepted` with the body hash, and `superseded` on each declared target. */
123
153
  export declare function planAccept(read: DecisionsRead, input: {
124
154
  readonly id: string;
@@ -229,10 +259,26 @@ export interface AfterStage {
229
259
  /** `false` skips the commit: the files stay written and staged, and `commit` is `null`. */
230
260
  readonly commit: boolean;
231
261
  }
262
+ /** What `applyPlan` did. */
263
+ export interface AppliedPlan {
264
+ /** Repo-relative paths written: the plan's files, then `<root>/index.md` when it was written. */
265
+ readonly written: string[];
266
+ readonly commit: string | null;
267
+ readonly findings: readonly Finding[];
268
+ /** Notes about the index ("not updated", "may be stale"); `[]` when none. */
269
+ readonly notes: readonly string[];
270
+ }
271
+ /**
272
+ * What the recovery hint says about rerunning a write command after `git commit` failed (design
273
+ * 2b D5): the batch is already in the log, so a rerun is refused, writes nothing or duplicates it.
274
+ */
275
+ export declare const WRITE_RERUN = "Don't run the command again: the log already holds the change, so a rerun would be refused, do nothing, or write it a second time.";
232
276
  /**
233
277
  * Applies a plan (§5.4): refuses a commit whose scope has unstaged or needed untracked paths,
234
278
  * writes the files (removing them again if a write fails), stages them, runs `afterStage`, and
235
- * commits `plan.stage` plus the involved subjects' already-staged files with exact trailers.
279
+ * commits `plan.stage` plus the involved subjects' already-staged files with exact trailers. A
280
+ * failed `git commit` throws `CommitFailedError` with the manual command (design 2b D5) and the
281
+ * notes made so far.
236
282
  */
237
283
  export declare function applyPlan(log: DecisionLog, plan: WritePlan, o: {
238
284
  readonly commit: boolean;
@@ -241,11 +287,13 @@ export declare function applyPlan(log: DecisionLog, plan: WritePlan, o: {
241
287
  /** `message` is the user's own: append the trailers to its own trailer block, if it ends in one. */
242
288
  readonly mergeTrailers?: boolean;
243
289
  readonly afterStage?: () => Promise<AfterStage>;
244
- }): Promise<{
245
- written: string[];
246
- commit: string | null;
247
- findings: readonly Finding[];
248
- }>;
290
+ /**
291
+ * Renders the log's index for `tree`, which builds on `history` (HEAD, the new commit, or
292
+ * null on an unborn HEAD); `null` when the numbers can't be projected. Given only when the log
293
+ * keeps a committed index (design 2b D2, §4.5).
294
+ */
295
+ readonly index?: (tree: TreeReader, history: string | null) => Promise<string | null>;
296
+ }): Promise<AppliedPlan>;
249
297
  /**
250
298
  * The clock, the randomness and `applyPlan` the write functions use. Only tests pass their own
251
299
  * (for example an `apply` that fails once); the default is the real ones.
@@ -318,6 +366,14 @@ export declare function linkDecision(log: DecisionLog, input: LinkInput, o?: Wri
318
366
  * and commits with `commit`. An unchanged set writes nothing.
319
367
  */
320
368
  export declare function scopeDecision(log: DecisionLog, input: ScopeInput, o?: WriteOptions, deps?: WriteDeps): Promise<WriteResultJson>;
369
+ /**
370
+ * Replaces sections of a proposed decision's draft (design §6.6), stages the body, and commits
371
+ * with `commit` (default `[policy] auto_commit`). A body with unstaged changes is a `UsageError`,
372
+ * so nobody's unsaved edit is folded into this write; a body changed between plan and write is
373
+ * `FileChangedError`. A commit is never made of a draft that fails its template lint: it is
374
+ * refused (`ok: false`, nothing written), as for `proposeDecision` without an editor.
375
+ */
376
+ export declare function updateDraft(log: DecisionLog, input: UpdateDraftInput, o?: WriteOptions, deps?: WriteDeps): Promise<WriteResultJson>;
321
377
  /**
322
378
  * `adr scope [<decision>] --follow-renames` (design §6.8): rewrites the scope globs of the given
323
379
  * decision, or of every decision, after the renames staged against `HEAD`, and stages one
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nikovirtala/adr",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Architecture decision records kept as event logs in git: frozen bodies, computed status and supersession chains. CLI and library.",
5
5
  "keywords": [
6
6
  "adr",
@@ -45,7 +45,10 @@
45
45
  "provenance": true
46
46
  },
47
47
  "dependencies": {
48
- "@nikovirtala/ledger": "^0.4.0"
48
+ "@nikovirtala/ledger": "^0.6.0"
49
+ },
50
+ "devDependencies": {
51
+ "@modelcontextprotocol/sdk": "1.32.2"
49
52
  },
50
53
  "scripts": {
51
54
  "build": "tsdown",