adr-kit 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -9
- package/README.zh.md +25 -8
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +7 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/accept.d.ts.map +1 -1
- package/dist/commands/accept.js +3 -4
- package/dist/commands/accept.js.map +1 -1
- package/dist/commands/decide.d.ts.map +1 -1
- package/dist/commands/decide.js +4 -5
- package/dist/commands/decide.js.map +1 -1
- package/dist/commands/list.js +1 -1
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/propose.d.ts.map +1 -1
- package/dist/commands/propose.js +4 -3
- package/dist/commands/propose.js.map +1 -1
- package/dist/commands/reject.d.ts.map +1 -1
- package/dist/commands/reject.js +2 -3
- package/dist/commands/reject.js.map +1 -1
- package/dist/commands/supersede.d.ts.map +1 -1
- package/dist/commands/supersede.js +6 -6
- package/dist/commands/supersede.js.map +1 -1
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +14 -3
- package/dist/commands/validate.js.map +1 -1
- package/dist/core/adr.d.ts +19 -2
- package/dist/core/adr.d.ts.map +1 -1
- package/dist/core/adr.js +38 -8
- package/dist/core/adr.js.map +1 -1
- package/dist/core/repository.d.ts +0 -1
- package/dist/core/repository.d.ts.map +1 -1
- package/dist/core/repository.js +59 -22
- package/dist/core/repository.js.map +1 -1
- package/dist/core/templates.d.ts +11 -2
- package/dist/core/templates.d.ts.map +1 -1
- package/dist/core/templates.js +42 -8
- package/dist/core/templates.js.map +1 -1
- package/dist/core/tool-integrations.js +7 -7
- package/dist/core/validate.d.ts +7 -0
- package/dist/core/validate.d.ts.map +1 -1
- package/dist/core/validate.js +57 -26
- package/dist/core/validate.js.map +1 -1
- package/package.json +2 -2
package/dist/core/adr.js
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { basename } from 'node:path';
|
|
3
|
+
/** Sections that only make sense during the proposal era and must not appear in an accepted decision. */
|
|
4
|
+
export const PROPOSAL_ERA_HEADINGS = ['Proposal', 'Acceptance criteria', 'Risks', 'Plan', 'Migration plan'];
|
|
5
|
+
/** A `YYYY-MM-DD` date value. Shared by the parser (format) and validate (calendar validity). */
|
|
6
|
+
export const DATE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
7
|
+
/**
|
|
8
|
+
* Today's date as `YYYY-MM-DD` in local time. The single source of the date:
|
|
9
|
+
* it feeds the `Date:` header line that every record carries and the
|
|
10
|
+
* `YYYY-MM-DD-` prefix of proposal file names, so the two always agree.
|
|
11
|
+
*/
|
|
12
|
+
export function todayStamp() {
|
|
13
|
+
const now = new Date();
|
|
14
|
+
const year = now.getFullYear();
|
|
15
|
+
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
16
|
+
const day = String(now.getDate()).padStart(2, '0');
|
|
17
|
+
return `${year}-${month}-${day}`;
|
|
18
|
+
}
|
|
3
19
|
export class AdrFormatError extends Error {
|
|
4
20
|
path;
|
|
5
21
|
constructor(message, path) {
|
|
@@ -43,11 +59,16 @@ export function hasMeaningfulBody(body) {
|
|
|
43
59
|
*
|
|
44
60
|
* The format is deliberately plain Markdown, with no front matter:
|
|
45
61
|
*
|
|
46
|
-
* # ADR: <title or "
|
|
47
|
-
* Status: proposed | accepted | rejected — <reason>
|
|
62
|
+
* # ADR: <title or "N Title">
|
|
63
|
+
* Status: proposed | accepted | rejected — <reason> | superseded by N
|
|
64
|
+
* Date: YYYY-MM-DD
|
|
48
65
|
* <blank line>
|
|
49
66
|
* ## Problem
|
|
50
67
|
* ...
|
|
68
|
+
*
|
|
69
|
+
* The `Date:` line records the date the current status was reached and is
|
|
70
|
+
* stamped by the CLI at every lifecycle move (propose, decide, accept,
|
|
71
|
+
* reject, supersede).
|
|
51
72
|
*/
|
|
52
73
|
export function parseAdrFile(filePath) {
|
|
53
74
|
const text = readFileSync(filePath, 'utf8');
|
|
@@ -81,19 +102,27 @@ export function parseAdrFile(filePath) {
|
|
|
81
102
|
throw new AdrFormatError('rejected status must include a reason', filePath);
|
|
82
103
|
}
|
|
83
104
|
}
|
|
84
|
-
else if (/^superseded by \d
|
|
105
|
+
else if (/^superseded by [1-9]\d*$/.test(statusText)) {
|
|
85
106
|
status = 'superseded';
|
|
86
107
|
supersededBy = Number(statusText.slice('superseded by '.length));
|
|
87
108
|
}
|
|
88
109
|
else {
|
|
89
|
-
throw new AdrFormatError('status must be "proposed", "accepted", "rejected — <reason>", or "superseded by
|
|
110
|
+
throw new AdrFormatError('status must be "proposed", "accepted", "rejected — <reason>", or "superseded by N"', filePath);
|
|
111
|
+
}
|
|
112
|
+
const dateLine = lines[2] ?? '';
|
|
113
|
+
if (!dateLine.startsWith('Date: ')) {
|
|
114
|
+
throw new AdrFormatError('line 3 must be "Date: YYYY-MM-DD"', filePath);
|
|
115
|
+
}
|
|
116
|
+
const date = dateLine.slice('Date: '.length).trim();
|
|
117
|
+
if (!DATE_PATTERN.test(date)) {
|
|
118
|
+
throw new AdrFormatError('line 3 must be "Date: YYYY-MM-DD"', filePath);
|
|
90
119
|
}
|
|
91
|
-
if ((lines[
|
|
92
|
-
throw new AdrFormatError('line
|
|
120
|
+
if ((lines[3] ?? '') !== '') {
|
|
121
|
+
throw new AdrFormatError('line 4 must be blank', filePath);
|
|
93
122
|
}
|
|
94
123
|
const sections = [];
|
|
95
124
|
let current = null;
|
|
96
|
-
for (const line of lines.slice(
|
|
125
|
+
for (const line of lines.slice(4)) {
|
|
97
126
|
if (line.startsWith('## ')) {
|
|
98
127
|
if (current !== null)
|
|
99
128
|
sections.push(current);
|
|
@@ -109,13 +138,14 @@ export function parseAdrFile(filePath) {
|
|
|
109
138
|
}
|
|
110
139
|
if (current !== null)
|
|
111
140
|
sections.push(current);
|
|
112
|
-
const numberMatch = title.match(/^(\d
|
|
141
|
+
const numberMatch = title.match(/^([1-9]\d*)\s+(.+)$/);
|
|
113
142
|
const parsed = {
|
|
114
143
|
folder: 'proposed',
|
|
115
144
|
path: filePath,
|
|
116
145
|
fileName: basename(filePath),
|
|
117
146
|
title,
|
|
118
147
|
status,
|
|
148
|
+
date,
|
|
119
149
|
sections,
|
|
120
150
|
};
|
|
121
151
|
if (rejectionReason !== undefined)
|
package/dist/core/adr.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adr.js","sourceRoot":"","sources":["../../src/core/adr.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"adr.js","sourceRoot":"","sources":["../../src/core/adr.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,7 +10,6 @@ export declare function folderPath(root: string, folder: AdrFolder): string;
|
|
|
10
10
|
export declare function listRecords(root: string): AdrRecord[];
|
|
11
11
|
export declare function resolveRecord(root: string, query: string): AdrRecord;
|
|
12
12
|
export declare function nextDecisionNumber(root: string): number;
|
|
13
|
-
export declare function todayStamp(): string;
|
|
14
13
|
export declare function writeRecord(root: string, folder: AdrFolder, fileName: string, content: string): string;
|
|
15
14
|
export declare function removeRecord(record: AdrRecord): void;
|
|
16
15
|
export declare function readRecord(record: AdrRecord): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repository.d.ts","sourceRoot":"","sources":["../../src/core/repository.ts"],"names":[],"mappings":"AAUA,OAAO,EAAgB,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,UAAU,CAAC;AAExE,eAAO,MAAM,OAAO,EAAE,SAAS,EAA0C,CAAC;AAE1E,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;
|
|
1
|
+
{"version":3,"file":"repository.d.ts","sourceRoot":"","sources":["../../src/core/repository.ts"],"names":[],"mappings":"AAUA,OAAO,EAAgB,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,UAAU,CAAC;AAExE,eAAO,MAAM,OAAO,EAAE,SAAS,EAA0C,CAAC;AAE1E,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AA0DD,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,GAAE,MAAM,EAAO,GAAG,UAAU,CA0BlF;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,GAAG,MAAM,CAElE;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,CA0BrD;AA0CD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,CAiCpE;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQvD;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAItG;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAEpD;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAEpD;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAMrD;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAEtD"}
|
package/dist/core/repository.js
CHANGED
|
@@ -34,7 +34,7 @@ machine-checkable header and lifecycle folders.
|
|
|
34
34
|
|
|
35
35
|
| Folder | Meaning |
|
|
36
36
|
| --- | --- |
|
|
37
|
-
| \`decisions/\` | Accepted and superseded decisions, numbered
|
|
37
|
+
| \`decisions/\` | Accepted and superseded decisions, numbered sequentially, immutable history |
|
|
38
38
|
| \`proposed/\` | Proposals that are not yet accepted or rejected |
|
|
39
39
|
| \`rejected/\` | Rejected proposals, frozen for the record |
|
|
40
40
|
|
|
@@ -44,10 +44,13 @@ Every record starts with exactly:
|
|
|
44
44
|
|
|
45
45
|
\`\`\`markdown
|
|
46
46
|
# ADR: <title>
|
|
47
|
-
Status: proposed | accepted | rejected — <reason>
|
|
47
|
+
Status: proposed | accepted | rejected — <reason> | superseded by N
|
|
48
|
+
Date: YYYY-MM-DD
|
|
48
49
|
\`\`\`
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
The \`Date:\` line records when the current status was reached; the CLI
|
|
52
|
+
stamps it at every lifecycle move. Accepted decisions use \`# ADR: N <title>\`
|
|
53
|
+
and require
|
|
51
54
|
\`Problem\`, \`Decision\`, \`Alternatives considered\`, and \`Consequences\`.
|
|
52
55
|
Proposals require \`Problem\`, \`Proposal\`, \`Alternatives considered\`,
|
|
53
56
|
\`Acceptance criteria\`, and \`Risks\`.
|
|
@@ -115,21 +118,63 @@ export function listRecords(root) {
|
|
|
115
118
|
});
|
|
116
119
|
return records;
|
|
117
120
|
}
|
|
121
|
+
function fileNameMatchesQuery(fileName, needle) {
|
|
122
|
+
const bareName = fileName.replace(/\.md$/, '');
|
|
123
|
+
const slug = bareName.replace(/^\d{4}-\d{2}-\d{2}-/, '').replace(/^\d+-/, '');
|
|
124
|
+
return fileName === needle || bareName === needle || slug === needle;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Fallback scan for resolveRecord when listRecords throws on a corrupt
|
|
128
|
+
* record: parse what we can so a query for a healthy record still resolves.
|
|
129
|
+
* A corrupt file whose name matches the query re-throws its parse error,
|
|
130
|
+
* since that is the record the caller asked for.
|
|
131
|
+
*/
|
|
132
|
+
function listRecordsForResolve(root, needle) {
|
|
133
|
+
const records = [];
|
|
134
|
+
for (const folder of FOLDERS) {
|
|
135
|
+
const dir = folderPath(root, folder);
|
|
136
|
+
if (!existsSync(dir))
|
|
137
|
+
continue;
|
|
138
|
+
for (const entry of readdirSync(dir)) {
|
|
139
|
+
if (!entry.endsWith('.md'))
|
|
140
|
+
continue;
|
|
141
|
+
const path = join(dir, entry);
|
|
142
|
+
try {
|
|
143
|
+
const record = parseAdrFile(path);
|
|
144
|
+
record.folder = folder;
|
|
145
|
+
records.push(record);
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
const bareName = entry.replace(/\.md$/, '');
|
|
149
|
+
const numberMatch = folder === 'decisions' ? bareName.match(/^(\d+)-/) : null;
|
|
150
|
+
const isTarget = fileNameMatchesQuery(entry, needle) ||
|
|
151
|
+
(numberMatch !== null && numberMatch[1] === needle);
|
|
152
|
+
if (isTarget) {
|
|
153
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
154
|
+
throw new Error(`failed to parse ${relative(root, path)}: ${message}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return records;
|
|
160
|
+
}
|
|
118
161
|
export function resolveRecord(root, query) {
|
|
119
|
-
const
|
|
120
|
-
|
|
162
|
+
const needle = query.trim();
|
|
163
|
+
let records;
|
|
164
|
+
try {
|
|
165
|
+
records = listRecords(root);
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
records = listRecordsForResolve(root, needle);
|
|
169
|
+
}
|
|
121
170
|
const candidates = records.filter((record) => {
|
|
122
|
-
|
|
123
|
-
const slug = bareName.replace(/^\d{4}-\d{2}-\d{2}-/, '').replace(/^\d{4}-/, '');
|
|
124
|
-
if (record.fileName === normalized || bareName === normalized || slug === normalized) {
|
|
171
|
+
if (fileNameMatchesQuery(record.fileName, needle))
|
|
125
172
|
return true;
|
|
126
|
-
}
|
|
127
|
-
if (record.title === normalized || `# ADR: ${record.title}` === normalized) {
|
|
173
|
+
if (record.title === needle || `# ADR: ${record.title}` === needle) {
|
|
128
174
|
return true;
|
|
129
175
|
}
|
|
130
176
|
if (record.folder === 'decisions') {
|
|
131
|
-
|
|
132
|
-
if (padded === normalized || String(record.number ?? 0) === normalized) {
|
|
177
|
+
if (String(record.number ?? 0) === needle) {
|
|
133
178
|
return true;
|
|
134
179
|
}
|
|
135
180
|
}
|
|
@@ -157,13 +202,6 @@ export function nextDecisionNumber(root) {
|
|
|
157
202
|
}
|
|
158
203
|
return max + 1;
|
|
159
204
|
}
|
|
160
|
-
export function todayStamp() {
|
|
161
|
-
const now = new Date();
|
|
162
|
-
const year = now.getFullYear();
|
|
163
|
-
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
164
|
-
const day = String(now.getDate()).padStart(2, '0');
|
|
165
|
-
return `${year}-${month}-${day}`;
|
|
166
|
-
}
|
|
167
205
|
export function writeRecord(root, folder, fileName, content) {
|
|
168
206
|
const path = join(folderPath(root, folder), fileName);
|
|
169
207
|
writeFileSync(path, content);
|
|
@@ -177,9 +215,8 @@ export function readRecord(record) {
|
|
|
177
215
|
}
|
|
178
216
|
export function displayName(record) {
|
|
179
217
|
if (record.folder === 'decisions' && Number.isInteger(record.number)) {
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
return `[${padded}] ${titleWithoutNumber}`;
|
|
218
|
+
const titleWithoutNumber = record.title.replace(/^\d+\s+/, '');
|
|
219
|
+
return `[${record.number}] ${titleWithoutNumber}`;
|
|
183
220
|
}
|
|
184
221
|
return record.title;
|
|
185
222
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repository.js","sourceRoot":"","sources":["../../src/core/repository.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,SAAS,EACT,WAAW,EACX,YAAY,EACZ,MAAM,EACN,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAkC,MAAM,UAAU,CAAC;AAExE,MAAM,CAAC,MAAM,OAAO,GAAgB,CAAC,WAAW,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;AAO1E,SAAS,UAAU,CAAC,KAAe;IACjC,sCAAsC;IACtC,+DAA+D;IAC/D,MAAM,SAAS,GAAG,WAAW,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;IACnD,OAAO;;;;;;;;EAQP,SAAS;;;;;;;;CAQV,CAAC;AACF,CAAC;AAED,MAAM,WAAW,GAAG
|
|
1
|
+
{"version":3,"file":"repository.js","sourceRoot":"","sources":["../../src/core/repository.ts"],"names":[],"mappings":"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"}
|
package/dist/core/templates.d.ts
CHANGED
|
@@ -10,8 +10,17 @@ export declare function rejectedTemplate(title: string, reason: string): string;
|
|
|
10
10
|
export declare function proposalToDecision(proposal: AdrRecord, number: number): string;
|
|
11
11
|
/**
|
|
12
12
|
* Proposal sections that have no place in an accepted decision and would be
|
|
13
|
-
* silently discarded by `proposalToDecision`.
|
|
14
|
-
*
|
|
13
|
+
* silently discarded by `proposalToDecision`. Only proposal-era leftovers
|
|
14
|
+
* (for example `Plan`, `Migration plan`) qualify; every other extra section
|
|
15
|
+
* is carried through the rewrite. Callers should surface these so a
|
|
16
|
+
* mechanical lifecycle move never loses content without warning.
|
|
15
17
|
*/
|
|
16
18
|
export declare function droppedSections(proposal: AdrRecord): string[];
|
|
19
|
+
/**
|
|
20
|
+
* Rewrite a record's status line and re-stamp its `Date:` line for a
|
|
21
|
+
* lifecycle move. Every non-creating move (accept, reject, supersede) must
|
|
22
|
+
* stamp the date so the header always reflects the current status; the body
|
|
23
|
+
* is left untouched.
|
|
24
|
+
*/
|
|
25
|
+
export declare function stampLifecycleMove(content: string, statusLine: string): string;
|
|
17
26
|
//# sourceMappingURL=templates.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../src/core/templates.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../src/core/templates.ts"],"names":[],"mappings":"AAAA,OAAO,EAA8C,KAAK,SAAS,EAAE,MAAM,UAAU,CAAC;AAoCtF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CA0BxE;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAsBxF;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAiBtE;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAoD9E;AAMD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,SAAS,GAAG,MAAM,EAAE,CAI7D;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAK9E"}
|
package/dist/core/templates.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { section } from './adr.js';
|
|
1
|
+
import { PROPOSAL_ERA_HEADINGS, section, todayStamp } from './adr.js';
|
|
2
2
|
/** Proposal sections that survive the mechanical accept rewrite. */
|
|
3
3
|
const PRESERVED_SECTIONS = [
|
|
4
4
|
'Problem',
|
|
@@ -7,6 +7,13 @@ const PRESERVED_SECTIONS = [
|
|
|
7
7
|
'Acceptance criteria',
|
|
8
8
|
'Risks',
|
|
9
9
|
];
|
|
10
|
+
/**
|
|
11
|
+
* Proposal-era leftovers the accept rewrite cannot fold anywhere. The full
|
|
12
|
+
* PROPOSAL_ERA_HEADINGS set is wider (it also names Proposal/Acceptance
|
|
13
|
+
* criteria/Risks, which the rewrite folds into Decision/Consequences); only
|
|
14
|
+
* these headings are genuinely dropped, with a warning.
|
|
15
|
+
*/
|
|
16
|
+
const DROPPED_SECTION_HEADINGS = PROPOSAL_ERA_HEADINGS.filter((heading) => !PRESERVED_SECTIONS.includes(heading));
|
|
10
17
|
/**
|
|
11
18
|
* 把 config.yaml 的 context 注入模板:放在标题块之后、第一个 section 之前。
|
|
12
19
|
* 解析器忽略游离文本(current 为 null 时不收集),accept 的机械改写也会
|
|
@@ -25,6 +32,7 @@ ${text}
|
|
|
25
32
|
export function proposalTemplate(title, context) {
|
|
26
33
|
return `# ADR: ${title}
|
|
27
34
|
Status: proposed
|
|
35
|
+
Date: ${todayStamp()}
|
|
28
36
|
|
|
29
37
|
${contextBlock(context)}## Problem
|
|
30
38
|
|
|
@@ -49,9 +57,9 @@ ${contextBlock(context)}## Problem
|
|
|
49
57
|
`;
|
|
50
58
|
}
|
|
51
59
|
export function decisionTemplate(number, title, context) {
|
|
52
|
-
|
|
53
|
-
return `# ADR: ${padded} ${title}
|
|
60
|
+
return `# ADR: ${number} ${title}
|
|
54
61
|
Status: accepted
|
|
62
|
+
Date: ${todayStamp()}
|
|
55
63
|
|
|
56
64
|
${contextBlock(context)}## Problem
|
|
57
65
|
|
|
@@ -74,6 +82,7 @@ ${contextBlock(context)}## Problem
|
|
|
74
82
|
export function rejectedTemplate(title, reason) {
|
|
75
83
|
return `# ADR: ${title}
|
|
76
84
|
Status: rejected — ${reason}
|
|
85
|
+
Date: ${todayStamp()}
|
|
77
86
|
|
|
78
87
|
## Problem
|
|
79
88
|
|
|
@@ -109,9 +118,15 @@ export function proposalToDecision(proposal, number) {
|
|
|
109
118
|
if (consequences.length === 0) {
|
|
110
119
|
consequences.push('_No consequences recorded._', '');
|
|
111
120
|
}
|
|
112
|
-
|
|
113
|
-
|
|
121
|
+
// Extra sections (for example `## Implementation`) are neither canonical
|
|
122
|
+
// decision sections nor proposal-era leftovers, so they are carried through
|
|
123
|
+
// the rewrite verbatim (appended after the canonical sections) instead of
|
|
124
|
+
// being dropped: a lifecycle move must never lose written content silently.
|
|
125
|
+
const passthrough = proposal.sections.filter((candidate) => !PRESERVED_SECTIONS.includes(candidate.heading) &&
|
|
126
|
+
!DROPPED_SECTION_HEADINGS.includes(candidate.heading));
|
|
127
|
+
let output = `# ADR: ${number} ${proposal.title}
|
|
114
128
|
Status: accepted
|
|
129
|
+
Date: ${todayStamp()}
|
|
115
130
|
|
|
116
131
|
## Problem
|
|
117
132
|
|
|
@@ -128,18 +143,37 @@ ${alternatives.trim() || '_No alternatives recorded._'}
|
|
|
128
143
|
## Consequences
|
|
129
144
|
|
|
130
145
|
${consequences.join('\n')}\n`;
|
|
146
|
+
for (const extra of passthrough) {
|
|
147
|
+
const body = extra.body.trim();
|
|
148
|
+
output += body.length > 0 ? `\n## ${extra.heading}\n\n${body}\n` : `\n## ${extra.heading}\n`;
|
|
149
|
+
}
|
|
150
|
+
return output;
|
|
131
151
|
}
|
|
132
152
|
function sectionBody(record, heading) {
|
|
133
153
|
return section(record, heading)?.trim() ?? '';
|
|
134
154
|
}
|
|
135
155
|
/**
|
|
136
156
|
* Proposal sections that have no place in an accepted decision and would be
|
|
137
|
-
* silently discarded by `proposalToDecision`.
|
|
138
|
-
*
|
|
157
|
+
* silently discarded by `proposalToDecision`. Only proposal-era leftovers
|
|
158
|
+
* (for example `Plan`, `Migration plan`) qualify; every other extra section
|
|
159
|
+
* is carried through the rewrite. Callers should surface these so a
|
|
160
|
+
* mechanical lifecycle move never loses content without warning.
|
|
139
161
|
*/
|
|
140
162
|
export function droppedSections(proposal) {
|
|
141
163
|
return proposal.sections
|
|
142
164
|
.map((candidate) => candidate.heading)
|
|
143
|
-
.filter((heading) =>
|
|
165
|
+
.filter((heading) => DROPPED_SECTION_HEADINGS.includes(heading));
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Rewrite a record's status line and re-stamp its `Date:` line for a
|
|
169
|
+
* lifecycle move. Every non-creating move (accept, reject, supersede) must
|
|
170
|
+
* stamp the date so the header always reflects the current status; the body
|
|
171
|
+
* is left untouched.
|
|
172
|
+
*/
|
|
173
|
+
export function stampLifecycleMove(content, statusLine) {
|
|
174
|
+
const lines = content.split(/\r?\n/);
|
|
175
|
+
lines[1] = statusLine;
|
|
176
|
+
lines[2] = `Date: ${todayStamp()}`;
|
|
177
|
+
return lines.join('\n');
|
|
144
178
|
}
|
|
145
179
|
//# sourceMappingURL=templates.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/core/templates.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAkB,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/core/templates.ts"],"names":[],"mappings":"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"}
|
|
@@ -90,7 +90,7 @@ adrkit propose "<title>"
|
|
|
90
90
|
## Overview
|
|
91
91
|
|
|
92
92
|
Create an accepted decision draft directly in \`adr/decisions/\` with the
|
|
93
|
-
next \`
|
|
93
|
+
next \`N\` number.
|
|
94
94
|
|
|
95
95
|
## Steps
|
|
96
96
|
|
|
@@ -102,7 +102,7 @@ adrkit decide "<title>"
|
|
|
102
102
|
|
|
103
103
|
2. Edit the created file and fill \`## Problem\`, \`## Decision\`,
|
|
104
104
|
\`## Alternatives considered\`, and \`## Consequences\`.
|
|
105
|
-
3. Run \`adrkit validate <
|
|
105
|
+
3. Run \`adrkit validate <N>\` until it returns OK.
|
|
106
106
|
|
|
107
107
|
## Rules
|
|
108
108
|
|
|
@@ -142,7 +142,7 @@ adrkit validate [name] [--all] [--json]
|
|
|
142
142
|
## Overview
|
|
143
143
|
|
|
144
144
|
Accept a proposal. The CLI validates the proposal, assigns the next
|
|
145
|
-
\`
|
|
145
|
+
\`N\` decision number, rewrites \`## Proposal\` to \`## Decision\`, folds
|
|
146
146
|
\`Acceptance criteria\` and \`Risks\` into \`## Consequences\`, and moves the
|
|
147
147
|
file from \`adr/proposed/\` to \`adr/decisions/\`.
|
|
148
148
|
|
|
@@ -155,7 +155,7 @@ file from \`adr/proposed/\` to \`adr/decisions/\`.
|
|
|
155
155
|
adrkit accept "<name>"
|
|
156
156
|
\`\`\`
|
|
157
157
|
|
|
158
|
-
3. Confirm the output names the new \`adr/decisions/
|
|
158
|
+
3. Confirm the output names the new \`adr/decisions/N-*.md\` file.
|
|
159
159
|
|
|
160
160
|
## Rules
|
|
161
161
|
|
|
@@ -195,9 +195,9 @@ adrkit reject "<name>" --reason "<why it was rejected>"
|
|
|
195
195
|
|
|
196
196
|
## Overview
|
|
197
197
|
|
|
198
|
-
Mark an accepted decision as superseded. The CLI rewrites
|
|
199
|
-
|
|
200
|
-
\`adr/decisions/\` as frozen history.
|
|
198
|
+
Mark an accepted decision as superseded. The CLI rewrites its status line
|
|
199
|
+
to \`Status: superseded by N\`, stamps the superseded date on the \`Date:\`
|
|
200
|
+
line, and leaves the record in \`adr/decisions/\` as frozen history.
|
|
201
201
|
|
|
202
202
|
## Steps
|
|
203
203
|
|
package/dist/core/validate.d.ts
CHANGED
|
@@ -5,5 +5,12 @@ export interface ValidationIssue {
|
|
|
5
5
|
}
|
|
6
6
|
export declare function validateRecord(root: string, record: AdrRecord): ValidationIssue[];
|
|
7
7
|
export declare function validateRepository(root: string): ValidationIssue[];
|
|
8
|
+
/**
|
|
9
|
+
* Repository-level checks for a single record: a "superseded by N" reference
|
|
10
|
+
* must point at an existing decision that is not itself superseded.
|
|
11
|
+
* validateRecord only sees one file; this adds the cross-record half so
|
|
12
|
+
* `adrkit validate <name>` keeps the same promise as a full validate.
|
|
13
|
+
*/
|
|
14
|
+
export declare function validateRecordReferences(root: string, record: AdrRecord, records: AdrRecord[]): ValidationIssue[];
|
|
8
15
|
export declare function formatIssues(issues: ValidationIssue[]): string;
|
|
9
16
|
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/core/validate.ts"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/core/validate.ts"],"names":[],"mappings":"AAEA,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,UAAU,CAAC;AAIlB,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAMD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,GAAG,eAAe,EAAE,CA2HjF;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,EAAE,CAqDlE;AA0BD;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,SAAS,EAAE,GACnB,eAAe,EAAE,CAQnB;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,CAI9D"}
|
package/dist/core/validate.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { join, relative } from 'node:path';
|
|
3
|
-
import { hasMeaningfulBody, statusForFolder } from './adr.js';
|
|
3
|
+
import { DATE_PATTERN, hasMeaningfulBody, PROPOSAL_ERA_HEADINGS, statusForFolder, } from './adr.js';
|
|
4
4
|
import { ADR_DIR, CONFIG_FILE, readConfig } from './config.js';
|
|
5
5
|
import { FOLDERS, listRecords } from './repository.js';
|
|
6
6
|
const DECISION_REQUIRED = ['Problem', 'Decision', 'Alternatives considered', 'Consequences'];
|
|
7
7
|
const PROPOSED_REQUIRED = ['Problem', 'Proposal', 'Alternatives considered', 'Acceptance criteria', 'Risks'];
|
|
8
8
|
const REJECTED_REQUIRED = ['Problem', 'Proposal', 'Alternatives considered'];
|
|
9
|
-
const PROPOSAL_ERA_HEADINGS = ['Proposal', 'Acceptance criteria', 'Risks', 'Plan', 'Migration plan'];
|
|
10
9
|
export function validateRecord(root, record) {
|
|
11
10
|
const issues = [];
|
|
12
11
|
const path = relative(root, record.path);
|
|
@@ -27,17 +26,30 @@ export function validateRecord(root, record) {
|
|
|
27
26
|
issues.push({ path, message: 'a decision cannot supersede itself' });
|
|
28
27
|
}
|
|
29
28
|
}
|
|
29
|
+
// The parser already enforces the YYYY-MM-DD format, so this only checks
|
|
30
|
+
// calendar validity; the match is never null for a parsed record.
|
|
31
|
+
const dateMatch = record.date.match(DATE_PATTERN);
|
|
32
|
+
if (dateMatch !== null) {
|
|
33
|
+
const year = Number(dateMatch[1]);
|
|
34
|
+
const month = Number(dateMatch[2]);
|
|
35
|
+
const day = Number(dateMatch[3]);
|
|
36
|
+
const date = new Date(year, month - 1, day);
|
|
37
|
+
if (date.getFullYear() !== year ||
|
|
38
|
+
date.getMonth() !== month - 1 ||
|
|
39
|
+
date.getDate() !== day) {
|
|
40
|
+
issues.push({ path, message: 'Date line contains an invalid calendar date' });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
30
43
|
if (record.folder === 'decisions') {
|
|
31
|
-
if (
|
|
32
|
-
issues.push({ path, message: 'decision file name must be "
|
|
44
|
+
if (!/^[1-9]\d*-[a-z0-9\u4e00-\u9fff-]+\.md$/.test(record.fileName)) {
|
|
45
|
+
issues.push({ path, message: 'decision file name must be "N-slug.md"' });
|
|
33
46
|
}
|
|
34
47
|
if (record.number === undefined) {
|
|
35
|
-
issues.push({ path, message: 'accepted decision title must be "# ADR:
|
|
48
|
+
issues.push({ path, message: 'accepted decision title must be "# ADR: N <title>"' });
|
|
36
49
|
}
|
|
37
50
|
else {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
issues.push({ path, message: `file name number must match title number ${padded}` });
|
|
51
|
+
if (!record.fileName.startsWith(`${record.number}-`)) {
|
|
52
|
+
issues.push({ path, message: `file name number must match title number ${record.number}` });
|
|
41
53
|
}
|
|
42
54
|
}
|
|
43
55
|
}
|
|
@@ -147,33 +159,52 @@ export function validateRepository(root) {
|
|
|
147
159
|
if (decisionRecords.has(record.number)) {
|
|
148
160
|
issues.push({
|
|
149
161
|
path: relative(root, record.path),
|
|
150
|
-
message: `duplicate decision number ${
|
|
162
|
+
message: `duplicate decision number ${record.number}`,
|
|
151
163
|
});
|
|
152
164
|
}
|
|
153
165
|
decisionRecords.set(record.number, record);
|
|
154
166
|
}
|
|
155
167
|
}
|
|
156
168
|
for (const record of records) {
|
|
157
|
-
|
|
158
|
-
continue;
|
|
159
|
-
const path = relative(root, record.path);
|
|
160
|
-
const padded = String(record.supersededBy).padStart(4, '0');
|
|
161
|
-
const target = decisionRecords.get(record.supersededBy);
|
|
162
|
-
if (target === undefined) {
|
|
163
|
-
issues.push({
|
|
164
|
-
path,
|
|
165
|
-
message: `"superseded by ${padded}" references a missing decision`,
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
else if (target.status === 'superseded') {
|
|
169
|
-
issues.push({
|
|
170
|
-
path,
|
|
171
|
-
message: `"superseded by ${padded}" references a superseded decision; supersede that decision instead`,
|
|
172
|
-
});
|
|
173
|
-
}
|
|
169
|
+
issues.push(...supersedeReferenceIssues(root, record, decisionRecords));
|
|
174
170
|
}
|
|
175
171
|
return issues;
|
|
176
172
|
}
|
|
173
|
+
function supersedeReferenceIssues(root, record, decisions) {
|
|
174
|
+
if (record.supersededBy === undefined)
|
|
175
|
+
return [];
|
|
176
|
+
const path = relative(root, record.path);
|
|
177
|
+
const target = decisions.get(record.supersededBy);
|
|
178
|
+
if (target === undefined) {
|
|
179
|
+
return [
|
|
180
|
+
{ path, message: `"superseded by ${record.supersededBy}" references a missing decision` },
|
|
181
|
+
];
|
|
182
|
+
}
|
|
183
|
+
if (target.status === 'superseded') {
|
|
184
|
+
return [
|
|
185
|
+
{
|
|
186
|
+
path,
|
|
187
|
+
message: `"superseded by ${record.supersededBy}" references a superseded decision; supersede that decision instead`,
|
|
188
|
+
},
|
|
189
|
+
];
|
|
190
|
+
}
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Repository-level checks for a single record: a "superseded by N" reference
|
|
195
|
+
* must point at an existing decision that is not itself superseded.
|
|
196
|
+
* validateRecord only sees one file; this adds the cross-record half so
|
|
197
|
+
* `adrkit validate <name>` keeps the same promise as a full validate.
|
|
198
|
+
*/
|
|
199
|
+
export function validateRecordReferences(root, record, records) {
|
|
200
|
+
const decisions = new Map();
|
|
201
|
+
for (const candidate of records) {
|
|
202
|
+
if (candidate.folder === 'decisions' && candidate.number !== undefined) {
|
|
203
|
+
decisions.set(candidate.number, candidate);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return supersedeReferenceIssues(root, record, decisions);
|
|
207
|
+
}
|
|
177
208
|
export function formatIssues(issues) {
|
|
178
209
|
if (issues.length === 0)
|
|
179
210
|
return 'OK';
|