@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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +156 -0
  3. package/dist/cli.js +1057 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/index.js +3 -0
  6. package/dist/kind-BcHjCHvi.js +415 -0
  7. package/dist/kind-BcHjCHvi.js.map +1 -0
  8. package/dist/kind.js +1 -0
  9. package/dist/types/cli/accept.d.ts +3 -0
  10. package/dist/types/cli/commands.d.ts +3 -0
  11. package/dist/types/cli/deprecate.d.ts +3 -0
  12. package/dist/types/cli/editor.d.ts +12 -0
  13. package/dist/types/cli/for.d.ts +3 -0
  14. package/dist/types/cli/init.d.ts +3 -0
  15. package/dist/types/cli/link.d.ts +3 -0
  16. package/dist/types/cli/list.d.ts +3 -0
  17. package/dist/types/cli/new.d.ts +6 -0
  18. package/dist/types/cli/note.d.ts +3 -0
  19. package/dist/types/cli/open.d.ts +17 -0
  20. package/dist/types/cli/reject.d.ts +3 -0
  21. package/dist/types/cli/scope.d.ts +7 -0
  22. package/dist/types/cli/show.d.ts +3 -0
  23. package/dist/types/cli/supersede.d.ts +3 -0
  24. package/dist/types/cli/text.d.ts +46 -0
  25. package/dist/types/cli/withdraw.d.ts +3 -0
  26. package/dist/types/cli.d.ts +2 -0
  27. package/dist/types/config.d.ts +33 -0
  28. package/dist/types/index.d.ts +8 -0
  29. package/dist/types/init.d.ts +27 -0
  30. package/dist/types/kind/project.d.ts +50 -0
  31. package/dist/types/kind/reduce.d.ts +35 -0
  32. package/dist/types/kind/rules.d.ts +20 -0
  33. package/dist/types/kind/schemas.d.ts +37 -0
  34. package/dist/types/kind.d.ts +18 -0
  35. package/dist/types/log.d.ts +53 -0
  36. package/dist/types/scope.d.ts +51 -0
  37. package/dist/types/templates/builtin.d.ts +8 -0
  38. package/dist/types/verify.d.ts +10 -0
  39. package/dist/types/views.d.ts +65 -0
  40. package/dist/types/write.d.ts +333 -0
  41. package/dist/write-9IaqHRrY.js +1986 -0
  42. package/dist/write-9IaqHRrY.js.map +1 -0
  43. package/package.json +55 -1
@@ -0,0 +1,65 @@
1
+ import { type EventEnvelope, type Finding, type StateHeader } from "@nikovirtala/ledger";
2
+ import type { DecisionStatus, DecisionView } from "./kind.js";
3
+ import type { DecisionsRead } from "./log.js";
4
+ /** The `numbering` key of the read documents. */
5
+ export interface NumberingJson {
6
+ readonly available: boolean;
7
+ readonly reason: "shallow" | "no-main-ref" | null;
8
+ readonly ref: string | null;
9
+ }
10
+ /** `adr list --json` (design §5.5). */
11
+ export interface DecisionListJson {
12
+ readonly format: "adr-list/v1";
13
+ readonly root: string;
14
+ readonly ref: string | null;
15
+ readonly commit: string | null;
16
+ readonly numbering: NumberingJson;
17
+ readonly decisions: readonly DecisionView[];
18
+ /** Newer envelope or schema versions; upgrade adr. */
19
+ readonly unreadable: readonly string[];
20
+ /** First event not `proposed`, or a body with no events; run adr verify. */
21
+ readonly unfoldable: readonly string[];
22
+ readonly findings: readonly Finding[];
23
+ readonly notes: readonly string[];
24
+ }
25
+ /** `adr show <decision> --json` (design §5.5). */
26
+ export interface DecisionShowJson {
27
+ readonly format: "adr-show/v1";
28
+ readonly root: string;
29
+ readonly ref: string | null;
30
+ readonly commit: string | null;
31
+ readonly numbering: NumberingJson;
32
+ readonly header: StateHeader;
33
+ readonly decision: DecisionView;
34
+ readonly body: string | null;
35
+ /** Every valid event in fold order with `history`, else `null`. */
36
+ readonly events: readonly EventEnvelope[] | null;
37
+ readonly findings: readonly Finding[];
38
+ readonly notes: readonly string[];
39
+ }
40
+ /** The `numbering` key of `adr-list/v1`, `adr-show/v1` and `adr-for/v1`. */
41
+ export declare const numberingJson: (read: DecisionsRead) => NumberingJson;
42
+ /**
43
+ * The `adr-list/v1` document: decisions in list order, filtered by `status` and truncated to
44
+ * `limit` after ordering. `findings` are those of the listed decisions and of the log as a whole.
45
+ */
46
+ export declare function listDecisions(read: DecisionsRead, o?: {
47
+ readonly status?: readonly DecisionStatus[];
48
+ readonly limit?: number;
49
+ }): DecisionListJson;
50
+ /**
51
+ * The `adr-show/v1` document of the decision `id` (resolve it with `resolveDecision` first).
52
+ * Throws `UsageError` for an unknown, unreadable or unfoldable decision.
53
+ */
54
+ export declare function showDecision(read: DecisionsRead, id: string, o?: {
55
+ readonly history?: boolean;
56
+ }): Promise<DecisionShowJson>;
57
+ /**
58
+ * The state header (01:258–259): `<display> (<id>) · <status>`, and for a decision that isn't in
59
+ * force a warning saying what to read instead. `refer` names where to read another decision. CLI
60
+ * default: `display(id)`. Phase 3 MCP passes `id => "adr://decision/" + display(id)`, e.g.
61
+ * `adr://decision/ADR-0012`, or the id when unnumbered.
62
+ */
63
+ export declare function stateHeader(view: DecisionView, read: DecisionsRead, o?: {
64
+ readonly refer?: (id: string) => string;
65
+ }): StateHeader;
@@ -0,0 +1,333 @@
1
+ import { type Actor, type EventEnvelope, type Finding, type SetOp, type Template, type TemplateSource, type Trailer } from "@nikovirtala/ledger";
2
+ import { type DecisionView } from "./kind.js";
3
+ import { type DecisionLog, type DecisionsRead } from "./log.js";
4
+ /** `adr <write command> --json` (design §5.5, §6.1). */
5
+ export interface WriteResultJson {
6
+ readonly format: "adr-write/v1";
7
+ readonly command: "new" | "accept" | "supersede" | "reject" | "withdraw" | "deprecate" | "note" | "link" | "scope";
8
+ readonly ok: boolean;
9
+ /**
10
+ * The decision after the write; before it when `ok` is false (`null` for a refused `new`).
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.
13
+ */
14
+ readonly decision: DecisionView | null;
15
+ /** Each decision superseded by this write, after it (read again after a commit, like `decision`). */
16
+ readonly affected: readonly DecisionView[];
17
+ /** Repo-relative paths this command wrote, in write order. */
18
+ readonly written: readonly string[];
19
+ readonly commit: string | null;
20
+ /** Refusal findings when `ok` is false; the draft's template lint for `new` (informational). */
21
+ readonly findings: readonly Finding[];
22
+ /**
23
+ * How the log was located (`ToolLog.notes`), then the caller's notes (attribution), then the
24
+ * write's own (a shortened commit subject, …).
25
+ */
26
+ readonly notes: readonly string[];
27
+ }
28
+ /** What every write function takes besides its input (design §6.1). */
29
+ export interface WriteOptions {
30
+ /** Commit the write; default `[policy] auto_commit`. */
31
+ readonly commit?: boolean;
32
+ /** The commit message to use instead of the `[commit] subject` one; used only when committing. */
33
+ readonly message?: string | null;
34
+ /** Notes to report before the write's own, such as the attribution notes. */
35
+ readonly notes?: readonly string[];
36
+ }
37
+ export interface ProposeInput {
38
+ readonly title: string;
39
+ /** Default `[log] default_template`. */
40
+ readonly template?: string;
41
+ /** Section id → markdown. */
42
+ readonly sections?: Readonly<Record<string, string>>;
43
+ /** Decisions this one is meant to replace, as ids or numbers. */
44
+ readonly supersedes?: readonly string[];
45
+ readonly actor: Actor;
46
+ }
47
+ /** What a plan needs besides the read: a clock, randomness and templates. */
48
+ export interface PlanContext {
49
+ /** Clock for `nextEventIdentity`. */
50
+ readonly now: () => number;
51
+ /** ULID and subject-id randomness. */
52
+ readonly random?: (buf: Uint8Array) => void;
53
+ /** Drafts' recorded template versions. */
54
+ readonly templates: TemplateSource;
55
+ /** Built-in template texts by name (§5.7), for `new`. */
56
+ readonly builtins: Readonly<Record<string, string>>;
57
+ }
58
+ /** Everything one write command does, computed without touching git or the disk. */
59
+ export interface WritePlan {
60
+ readonly command: "new" | "accept" | "supersede" | "event";
61
+ readonly subject: string;
62
+ /** New files, repo-relative path → bytes, in write order: body, materialized template, events. */
63
+ readonly files: Readonly<Record<string, Uint8Array>>;
64
+ readonly events: readonly EventEnvelope[];
65
+ /** Paths to stage: every new file, plus the existing body for accept. */
66
+ readonly stage: readonly string[];
67
+ /** Subject ids involved (D6): the subject, every superseded target, and `by` for supersede. Sorted. */
68
+ readonly involved: readonly string[];
69
+ /** Per involved subject: its body, its events dir and its recorded template. Sorted, deduplicated. */
70
+ readonly commitScope: readonly string[];
71
+ /** The events directories in `commitScope`, sorted; the rest of `commitScope` are file paths. */
72
+ readonly eventDirs: readonly string[];
73
+ /** For new: the body path, the rendered bytes and the template, for the editor step. */
74
+ readonly draft: {
75
+ readonly path: string;
76
+ readonly rendered: Uint8Array;
77
+ readonly template: Template;
78
+ } | null;
79
+ /** What the commit message is made of; `write()` renders it with `[commit] subject`. */
80
+ readonly commit: CommitParts | null;
81
+ /** One `ADR`/`ADR-Event` pair per plan event, in write order. */
82
+ readonly trailers: readonly Trailer[];
83
+ /** Views after the write, computed on `overlayTree(read.tree, files)`. */
84
+ readonly decision: DecisionView | null;
85
+ readonly affected: readonly DecisionView[];
86
+ /** Informational (new: the draft's template lint). */
87
+ readonly findings: readonly Finding[];
88
+ /** The write's own notes for the result (an already existing link, …). */
89
+ readonly notes?: readonly string[];
90
+ }
91
+ /** The parts of a commit message (design §4.3): the template's variables and the body. */
92
+ export interface CommitParts {
93
+ /** `{action}`, the verb phrase, e.g. `propose to` or `set the scope of`. */
94
+ readonly action: string;
95
+ /**
96
+ * The text after `{action}`, which `{title}` and `{raw_title}` come from: the decision's title
97
+ * as recorded for propose, accept, reject and withdraw, else its display (`ADR-0006`, or
98
+ * `ADR-0006 with ADR-0007` for supersede).
99
+ */
100
+ readonly title: string;
101
+ readonly id: string;
102
+ readonly display: string;
103
+ /** Body paragraphs, each wrapped at 72. */
104
+ readonly body: readonly string[];
105
+ }
106
+ export type PlanResult = {
107
+ readonly ok: true;
108
+ readonly plan: WritePlan;
109
+ } | {
110
+ readonly ok: false;
111
+ readonly decision: DecisionView | null;
112
+ readonly findings: readonly Finding[];
113
+ };
114
+ /**
115
+ * Plans `adr new`: a rendered body, a materialized template if needed, and `proposed`. With
116
+ * `refuseIncomplete` (a commit with no editor to fill the draft in, design §7.2), a draft that
117
+ * fails the template lint is refused with those findings before any file is planned.
118
+ */
119
+ export declare function planPropose(read: DecisionsRead, input: ProposeInput, c: PlanContext, o?: {
120
+ readonly refuseIncomplete?: boolean;
121
+ }): Promise<PlanResult>;
122
+ /** Plans `adr accept`: `accepted` with the body hash, and `superseded` on each declared target. */
123
+ export declare function planAccept(read: DecisionsRead, input: {
124
+ readonly id: string;
125
+ readonly actor: Actor;
126
+ }, c: PlanContext, o?: {
127
+ readonly copyGlobs?: readonly string[];
128
+ }): Promise<PlanResult>;
129
+ /** The scope an `accept` can copy from the decisions it supersedes (design §6.10). */
130
+ export interface ScopeOffer {
131
+ /** The decision being accepted. */
132
+ readonly id: string;
133
+ /** The superseded decisions with globs the accepted one lacks, sorted by id. */
134
+ readonly sources: readonly string[];
135
+ /** Their displays (`ADR-0002`, or the id), in `sources` order. */
136
+ readonly displays: readonly string[];
137
+ /** The globs to add: the union of their scopes minus the accepted decision's own, sorted. */
138
+ readonly globs: readonly string[];
139
+ }
140
+ /**
141
+ * Whether `accept` copies the superseded decisions' scope: always, never, never but with a note
142
+ * naming the `adr scope` command that would (the default), or as a function decides.
143
+ */
144
+ export type CopyScope = "copy" | "skip" | "suggest" | ((offer: ScopeOffer) => Promise<boolean>);
145
+ /**
146
+ * What accepting `id` could copy: the declared targets it will supersede (accepted or
147
+ * deprecated, as `planAccept` selects them) and the globs of theirs it doesn't have yet.
148
+ */
149
+ export declare function scopeToCopy(read: DecisionsRead, id: string): ScopeOffer;
150
+ /** Plans `adr supersede <id> --by <by>`: one `superseded` event on `id`. */
151
+ export declare function planSupersede(read: DecisionsRead, input: {
152
+ readonly id: string;
153
+ readonly by: string;
154
+ readonly reason?: string | null;
155
+ readonly actor: Actor;
156
+ }, c: PlanContext): Promise<PlanResult>;
157
+ /**
158
+ * Plans any one event on an existing decision: the state machine and the event's validation
159
+ * decide, then the precheck. Phase 2's reject, withdraw, deprecate, note, link and scope wrap it.
160
+ */
161
+ export declare function planEvent(read: DecisionsRead, input: {
162
+ readonly subject: string;
163
+ readonly type: string;
164
+ readonly data: Record<string, unknown>;
165
+ readonly reason?: string | null;
166
+ readonly actor: Actor;
167
+ }, c: PlanContext): Promise<PlanResult>;
168
+ /** Input of the lifecycle exits: the decision and the envelope's `reason`. */
169
+ export interface ReasonInput {
170
+ readonly id: string;
171
+ readonly reason?: string | null;
172
+ readonly actor: Actor;
173
+ }
174
+ /** Plans `adr reject`: `rejected` with its required reason, while proposed. */
175
+ export declare function planReject(read: DecisionsRead, input: ReasonInput, c: PlanContext): Promise<PlanResult>;
176
+ /** Plans `adr withdraw`: `withdrawn`, with a reason when given, while proposed. */
177
+ export declare function planWithdraw(read: DecisionsRead, input: ReasonInput, c: PlanContext): Promise<PlanResult>;
178
+ /** Plans `adr deprecate`: `deprecated` with its required reason, while accepted. */
179
+ export declare function planDeprecate(read: DecisionsRead, input: ReasonInput, c: PlanContext): Promise<PlanResult>;
180
+ /** Plans `adr note`: `noted` with the text as given, in every state. */
181
+ export declare function planNote(read: DecisionsRead, input: {
182
+ readonly id: string;
183
+ readonly text: string;
184
+ readonly actor: Actor;
185
+ }, c: PlanContext): Promise<PlanResult>;
186
+ export interface LinkInput {
187
+ readonly id: string;
188
+ /** A local id or number, or `<source>:<id>` naming a configured `[[sources]]` entry. */
189
+ readonly to: string;
190
+ /** Default `relates-to`. */
191
+ readonly rel?: string;
192
+ readonly actor: Actor;
193
+ }
194
+ /**
195
+ * Plans `adr link` (design §6.7): `linked` in every state. A qualified target must name one of
196
+ * `o.sources` (the log's `[[sources]]` names); it isn't looked up in the other log. A link that
197
+ * already exists plans nothing, with a note.
198
+ */
199
+ export declare function planLink(read: DecisionsRead, input: LinkInput, c: PlanContext, o?: {
200
+ readonly sources?: readonly string[];
201
+ }): Promise<PlanResult>;
202
+ export interface ScopeInput {
203
+ readonly id: string;
204
+ /** Replace (`set`, `[]` clears), or `add` and `remove` globs (design §3.2). */
205
+ readonly op: SetOp;
206
+ readonly actor: Actor;
207
+ /** How the caller names `set`, `add`, `remove` and `clear` in messages; default the field names. */
208
+ readonly names?: {
209
+ readonly set: string;
210
+ readonly add: string;
211
+ readonly remove: string;
212
+ readonly clear?: string;
213
+ };
214
+ }
215
+ /**
216
+ * Plans `adr scope` with a set operation (design §6.8): one `scoped` event with the whole new set
217
+ * while proposed, accepted or deprecated, or nothing when the set doesn't change. Invalid globs,
218
+ * conflicting operations and more than 200 globs are `UsageError`s. Notes name removed globs that
219
+ * weren't there, added ones that were, resulting globs matching none of `o.tracked` (when given)
220
+ * and a narrowed accepted scope.
221
+ */
222
+ export declare function planScope(read: DecisionsRead, input: ScopeInput, c: PlanContext, o?: {
223
+ readonly tracked?: readonly string[];
224
+ }): Promise<PlanResult>;
225
+ /** What the editor step decided (§5.4 step 4a). */
226
+ export interface AfterStage {
227
+ /** Draft lint findings to report instead of `plan.findings`; `null` keeps `plan.findings`. */
228
+ readonly findings: readonly Finding[] | null;
229
+ /** `false` skips the commit: the files stay written and staged, and `commit` is `null`. */
230
+ readonly commit: boolean;
231
+ }
232
+ /**
233
+ * Applies a plan (§5.4): refuses a commit whose scope has unstaged or needed untracked paths,
234
+ * 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.
236
+ */
237
+ export declare function applyPlan(log: DecisionLog, plan: WritePlan, o: {
238
+ readonly commit: boolean;
239
+ /** The commit message, without trailers; required when committing. */
240
+ readonly message?: string;
241
+ /** `message` is the user's own: append the trailers to its own trailer block, if it ends in one. */
242
+ readonly mergeTrailers?: boolean;
243
+ readonly afterStage?: () => Promise<AfterStage>;
244
+ }): Promise<{
245
+ written: string[];
246
+ commit: string | null;
247
+ findings: readonly Finding[];
248
+ }>;
249
+ /**
250
+ * The clock, the randomness and `applyPlan` the write functions use. Only tests pass their own
251
+ * (for example an `apply` that fails once); the default is the real ones.
252
+ */
253
+ export interface WriteDeps {
254
+ readonly now: () => number;
255
+ readonly random?: (buf: Uint8Array) => void;
256
+ readonly apply: typeof applyPlan;
257
+ }
258
+ /**
259
+ * `adr new`: renders a draft, writes the body, any materialized template and the `proposed`
260
+ * event, stages them, and commits with `commit` (default `[policy] auto_commit`).
261
+ *
262
+ * `edit` (CLI only; MCP never passes it) runs between staging and committing, on the absolute
263
+ * body path. It resolves to `null` when the editor exited 0, else to a note for the user (spawn
264
+ * error, exit status). `onNote` receives the notes about a skipped commit or a removed body,
265
+ * without the "not committed: " prefix the CLI adds when a commit was requested.
266
+ *
267
+ * A draft with unfilled sections is never committed (design §7.2): without `edit` a commit is
268
+ * refused (`ok: false`, nothing written); after `edit` the commit is skipped with a note.
269
+ */
270
+ export declare function proposeDecision(log: DecisionLog, input: ProposeInput, o?: WriteOptions & {
271
+ readonly edit?: (absBodyPath: string) => Promise<string | null>;
272
+ readonly onNote?: (note: string) => void;
273
+ }, deps?: WriteDeps): Promise<WriteResultJson>;
274
+ /**
275
+ * `adr accept`: lints the draft, hashes the body, writes `accepted` and a `superseded` event on
276
+ * each declared target, stages them with the body, and commits with `commit`. When the targets
277
+ * have globs the decision lacks, `copyScope` decides whether a `scoped` event copies them
278
+ * (default `"suggest"`: no copy, and a note with the `adr scope` command that would).
279
+ */
280
+ export declare function acceptDecision(log: DecisionLog, input: {
281
+ readonly id: string;
282
+ readonly actor: Actor;
283
+ }, o?: WriteOptions & {
284
+ readonly copyScope?: CopyScope;
285
+ }, deps?: WriteDeps): Promise<WriteResultJson>;
286
+ /** `adr supersede <id> --by <by>`: writes `superseded`, stages it, and commits with `commit`. */
287
+ export declare function supersedeDecision(log: DecisionLog, input: {
288
+ readonly id: string;
289
+ readonly by: string;
290
+ readonly reason?: string | null;
291
+ readonly actor: Actor;
292
+ }, o?: WriteOptions, deps?: WriteDeps): Promise<WriteResultJson>;
293
+ /**
294
+ * `adr reject <id> --reason <text>`: writes `rejected`, stages it, and commits with `commit`. The
295
+ * kind requires the reason; without one the write is refused (findings, nothing written).
296
+ */
297
+ export declare function rejectDecision(log: DecisionLog, input: ReasonInput, o?: WriteOptions, deps?: WriteDeps): Promise<WriteResultJson>;
298
+ /** `adr withdraw <id> [--reason <text>]`: writes `withdrawn`, stages it, and commits with `commit`. */
299
+ export declare function withdrawDecision(log: DecisionLog, input: ReasonInput, o?: WriteOptions, deps?: WriteDeps): Promise<WriteResultJson>;
300
+ /**
301
+ * `adr deprecate <id> --reason <text>`: writes `deprecated`, stages it, and commits with `commit`.
302
+ * The kind requires the reason, as for `rejectDecision`.
303
+ */
304
+ export declare function deprecateDecision(log: DecisionLog, input: ReasonInput, o?: WriteOptions, deps?: WriteDeps): Promise<WriteResultJson>;
305
+ /** `adr note <id> <text>`: writes `noted`, stages it, and commits with `commit`. */
306
+ export declare function noteDecision(log: DecisionLog, input: {
307
+ readonly id: string;
308
+ readonly text: string;
309
+ readonly actor: Actor;
310
+ }, o?: WriteOptions, deps?: WriteDeps): Promise<WriteResultJson>;
311
+ /**
312
+ * `adr link <id> <target> [--rel <rel>]`: writes `linked`, stages it, and commits with `commit`.
313
+ * Qualified targets name the log's `[[sources]]`; a link that exists already writes nothing.
314
+ */
315
+ export declare function linkDecision(log: DecisionLog, input: LinkInput, o?: WriteOptions, deps?: WriteDeps): Promise<WriteResultJson>;
316
+ /**
317
+ * `adr scope <id> --set|--add|--remove …`: writes one `scoped` event with the new set, stages it,
318
+ * and commits with `commit`. An unchanged set writes nothing.
319
+ */
320
+ export declare function scopeDecision(log: DecisionLog, input: ScopeInput, o?: WriteOptions, deps?: WriteDeps): Promise<WriteResultJson>;
321
+ /**
322
+ * `adr scope [<decision>] --follow-renames` (design §6.8): rewrites the scope globs of the given
323
+ * decision, or of every decision, after the renames staged against `HEAD`, and stages one
324
+ * `scoped` event per changed decision. It never commits, whatever `[policy] auto_commit` says
325
+ * (the events belong in the commit with the renames), so `o.commit: true` and a message are
326
+ * `InvalidArgumentError`s. `dryRun` plans the same events and notes without writing anything.
327
+ */
328
+ export declare function followScopeRenames(log: DecisionLog, input: {
329
+ readonly id?: string;
330
+ readonly actor: Actor;
331
+ }, o?: WriteOptions & {
332
+ readonly dryRun?: boolean;
333
+ }, deps?: WriteDeps): Promise<WriteResultJson>;