@nikovirtala/adr 0.1.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.
@@ -0,0 +1,51 @@
1
+ import { type Finding, type ScopeVia } from "@nikovirtala/ledger";
2
+ import type { DecisionStatus } from "./kind.js";
3
+ import type { DecisionLog, DecisionsRead } from "./log.js";
4
+ import { type NumberingJson } from "./views.js";
5
+ /** One decision that governs a path in `adr-for/v1` (design §6.9). */
6
+ export interface DecisionForJson {
7
+ readonly id: string;
8
+ readonly display: string;
9
+ readonly title: string;
10
+ readonly status: DecisionStatus;
11
+ readonly effective: string | null;
12
+ /** What to know before applying it: superseded, deprecated, never in force; `null` if nothing. */
13
+ readonly warning: string | null;
14
+ /** The globs that matched, then the anchor lines. */
15
+ readonly via: readonly ScopeVia[];
16
+ }
17
+ /** An anchor naming no decision of this log. */
18
+ export interface UnresolvedAnchorJson {
19
+ /** The id, or `<source>:<id>` for a qualified anchor. */
20
+ readonly ref: string;
21
+ readonly line: number;
22
+ readonly reason: "not-found" | "other-log";
23
+ }
24
+ /** `adr for <path>… --json` (design §6.9). */
25
+ export interface DecisionsForJson {
26
+ readonly format: "adr-for/v1";
27
+ readonly root: string;
28
+ readonly ref: string | null;
29
+ readonly commit: string | null;
30
+ readonly numbering: NumberingJson;
31
+ readonly paths: readonly {
32
+ readonly path: string;
33
+ /** In the index, or in the `--ref` tree. */
34
+ readonly tracked: boolean;
35
+ readonly decisions: readonly DecisionForJson[];
36
+ readonly unresolved: readonly UnresolvedAnchorJson[];
37
+ }[];
38
+ /** The read's findings of the listed decisions and of the log as a whole. */
39
+ readonly findings: readonly Finding[];
40
+ readonly notes: readonly string[];
41
+ }
42
+ /**
43
+ * Which decisions govern each path (design §6.9): globs of decisions that were ever in force, and
44
+ * comment anchors of any decision, read from the work tree or, with a `--ref` log, that commit.
45
+ * Paths are relative to `o.cwd` (default the repository root); directories and paths outside the
46
+ * repository are `UsageError`s. Paths need not be tracked.
47
+ */
48
+ export declare function decisionsFor(read: DecisionsRead, log: DecisionLog, o: {
49
+ readonly paths: readonly string[];
50
+ readonly cwd?: string;
51
+ }): Promise<DecisionsForJson>;
@@ -37,6 +37,8 @@ export interface DecisionShowJson {
37
37
  readonly findings: readonly Finding[];
38
38
  readonly notes: readonly string[];
39
39
  }
40
+ /** The `numbering` key of `adr-list/v1`, `adr-show/v1` and `adr-for/v1`. */
41
+ export declare const numberingJson: (read: DecisionsRead) => NumberingJson;
40
42
  /**
41
43
  * The `adr-list/v1` document: decisions in list order, filtered by `status` and truncated to
42
44
  * `limit` after ordering. `findings` are those of the listed decisions and of the log as a whole.
@@ -1,10 +1,10 @@
1
- import { type Actor, type EventEnvelope, type Finding, 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 } from "@nikovirtala/ledger";
2
2
  import { type DecisionView } from "./kind.js";
3
3
  import { type DecisionLog, type DecisionsRead } from "./log.js";
4
- /** `adr new|accept|supersede --json` (design §5.5). */
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";
7
+ readonly command: "new" | "accept" | "supersede" | "reject" | "withdraw" | "deprecate" | "note" | "link" | "scope";
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`).
@@ -19,9 +19,21 @@ export interface WriteResultJson {
19
19
  readonly commit: string | null;
20
20
  /** Refusal findings when `ok` is false; the draft's template lint for `new` (informational). */
21
21
  readonly findings: readonly Finding[];
22
- /** How the log was located (`ToolLog.notes`). */
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
+ */
23
26
  readonly notes: readonly string[];
24
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
+ }
25
37
  export interface ProposeInput {
26
38
  readonly title: string;
27
39
  /** Default `[log] default_template`. */
@@ -64,15 +76,32 @@ export interface WritePlan {
64
76
  readonly rendered: Uint8Array;
65
77
  readonly template: Template;
66
78
  } | null;
67
- /** The D6 commit message. */
68
- readonly message: string;
79
+ /** What the commit message is made of; `write()` renders it with `[commit] subject`. */
80
+ readonly commit: CommitParts | null;
69
81
  /** One `ADR`/`ADR-Event` pair per plan event, in write order. */
70
82
  readonly trailers: readonly Trailer[];
71
83
  /** Views after the write, computed on `overlayTree(read.tree, files)`. */
72
- readonly decision: DecisionView;
84
+ readonly decision: DecisionView | null;
73
85
  readonly affected: readonly DecisionView[];
74
86
  /** Informational (new: the draft's template lint). */
75
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[];
76
105
  }
77
106
  export type PlanResult = {
78
107
  readonly ok: true;
@@ -82,13 +111,42 @@ export type PlanResult = {
82
111
  readonly decision: DecisionView | null;
83
112
  readonly findings: readonly Finding[];
84
113
  };
85
- /** Plans `adr new`: a rendered body, a materialized template if needed, and `proposed`. */
86
- export declare function planPropose(read: DecisionsRead, input: ProposeInput, c: PlanContext): Promise<PlanResult>;
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>;
87
122
  /** Plans `adr accept`: `accepted` with the body hash, and `superseded` on each declared target. */
88
123
  export declare function planAccept(read: DecisionsRead, input: {
89
124
  readonly id: string;
90
125
  readonly actor: Actor;
91
- }, c: PlanContext): Promise<PlanResult>;
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;
92
150
  /** Plans `adr supersede <id> --by <by>`: one `superseded` event on `id`. */
93
151
  export declare function planSupersede(read: DecisionsRead, input: {
94
152
  readonly id: string;
@@ -107,6 +165,63 @@ export declare function planEvent(read: DecisionsRead, input: {
107
165
  readonly reason?: string | null;
108
166
  readonly actor: Actor;
109
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>;
110
225
  /** What the editor step decided (§5.4 step 4a). */
111
226
  export interface AfterStage {
112
227
  /** Draft lint findings to report instead of `plan.findings`; `null` keeps `plan.findings`. */
@@ -121,6 +236,10 @@ export interface AfterStage {
121
236
  */
122
237
  export declare function applyPlan(log: DecisionLog, plan: WritePlan, o: {
123
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;
124
243
  readonly afterStage?: () => Promise<AfterStage>;
125
244
  }): Promise<{
126
245
  written: string[];
@@ -144,21 +263,25 @@ export interface WriteDeps {
144
263
  * body path. It resolves to `null` when the editor exited 0, else to a note for the user (spawn
145
264
  * error, exit status). `onNote` receives the notes about a skipped commit or a removed body,
146
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.
147
269
  */
148
- export declare function proposeDecision(log: DecisionLog, input: ProposeInput, o?: {
149
- readonly commit?: boolean;
270
+ export declare function proposeDecision(log: DecisionLog, input: ProposeInput, o?: WriteOptions & {
150
271
  readonly edit?: (absBodyPath: string) => Promise<string | null>;
151
272
  readonly onNote?: (note: string) => void;
152
273
  }, deps?: WriteDeps): Promise<WriteResultJson>;
153
274
  /**
154
275
  * `adr accept`: lints the draft, hashes the body, writes `accepted` and a `superseded` event on
155
- * each declared target, stages them with the body, and commits with `commit`.
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).
156
279
  */
157
280
  export declare function acceptDecision(log: DecisionLog, input: {
158
281
  readonly id: string;
159
282
  readonly actor: Actor;
160
- }, o?: {
161
- readonly commit?: boolean;
283
+ }, o?: WriteOptions & {
284
+ readonly copyScope?: CopyScope;
162
285
  }, deps?: WriteDeps): Promise<WriteResultJson>;
163
286
  /** `adr supersede <id> --by <by>`: writes `superseded`, stages it, and commits with `commit`. */
164
287
  export declare function supersedeDecision(log: DecisionLog, input: {
@@ -166,6 +289,45 @@ export declare function supersedeDecision(log: DecisionLog, input: {
166
289
  readonly by: string;
167
290
  readonly reason?: string | null;
168
291
  readonly actor: Actor;
169
- }, o?: {
170
- readonly commit?: boolean;
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;
171
333
  }, deps?: WriteDeps): Promise<WriteResultJson>;