adr-kit 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.
- package/LICENSE +21 -0
- package/README.md +150 -0
- package/README.zh.md +131 -0
- package/bin/openadr.js +5 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +176 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/accept.d.ts +2 -0
- package/dist/commands/accept.d.ts.map +1 -0
- package/dist/commands/accept.js +33 -0
- package/dist/commands/accept.js.map +1 -0
- package/dist/commands/completion.d.ts +2 -0
- package/dist/commands/completion.d.ts.map +1 -0
- package/dist/commands/completion.js +40 -0
- package/dist/commands/completion.js.map +1 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +23 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/decide.d.ts +2 -0
- package/dist/commands/decide.d.ts.map +1 -0
- package/dist/commands/decide.js +27 -0
- package/dist/commands/decide.js.map +1 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +18 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/instructions.d.ts +2 -0
- package/dist/commands/instructions.d.ts.map +1 -0
- package/dist/commands/instructions.js +60 -0
- package/dist/commands/instructions.js.map +1 -0
- package/dist/commands/list.d.ts +2 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +43 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/propose.d.ts +2 -0
- package/dist/commands/propose.d.ts.map +1 -0
- package/dist/commands/propose.js +26 -0
- package/dist/commands/propose.js.map +1 -0
- package/dist/commands/reject.d.ts +2 -0
- package/dist/commands/reject.d.ts.map +1 -0
- package/dist/commands/reject.js +26 -0
- package/dist/commands/reject.js.map +1 -0
- package/dist/commands/show.d.ts +2 -0
- package/dist/commands/show.d.ts.map +1 -0
- package/dist/commands/show.js +11 -0
- package/dist/commands/show.js.map +1 -0
- package/dist/commands/status.d.ts +6 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +46 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/supersede.d.ts +2 -0
- package/dist/commands/supersede.d.ts.map +1 -0
- package/dist/commands/supersede.js +36 -0
- package/dist/commands/supersede.js.map +1 -0
- package/dist/commands/update.d.ts +2 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +13 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/validate.d.ts +6 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +38 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/core/adr.d.ts +44 -0
- package/dist/core/adr.d.ts.map +1 -0
- package/dist/core/adr.js +143 -0
- package/dist/core/adr.js.map +1 -0
- package/dist/core/config.d.ts +22 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +63 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/repository.d.ts +19 -0
- package/dist/core/repository.d.ts.map +1 -0
- package/dist/core/repository.js +190 -0
- package/dist/core/repository.js.map +1 -0
- package/dist/core/slug.d.ts +10 -0
- package/dist/core/slug.d.ts.map +1 -0
- package/dist/core/slug.js +19 -0
- package/dist/core/slug.js.map +1 -0
- package/dist/core/templates.d.ts +11 -0
- package/dist/core/templates.d.ts.map +1 -0
- package/dist/core/templates.js +126 -0
- package/dist/core/templates.js.map +1 -0
- package/dist/core/tool-integrations.d.ts +16 -0
- package/dist/core/tool-integrations.d.ts.map +1 -0
- package/dist/core/tool-integrations.js +256 -0
- package/dist/core/tool-integrations.js.map +1 -0
- package/dist/core/validate.d.ts +9 -0
- package/dist/core/validate.d.ts.map +1 -0
- package/dist/core/validate.js +183 -0
- package/dist/core/validate.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/package.json +49 -0
package/dist/core/adr.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { basename } from 'node:path';
|
|
3
|
+
export class AdrFormatError extends Error {
|
|
4
|
+
path;
|
|
5
|
+
constructor(message, path) {
|
|
6
|
+
super(`${message} (${path})`);
|
|
7
|
+
this.path = path;
|
|
8
|
+
this.name = 'AdrFormatError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export function statusForFolder(folder) {
|
|
12
|
+
switch (folder) {
|
|
13
|
+
case 'decisions':
|
|
14
|
+
return 'accepted';
|
|
15
|
+
case 'proposed':
|
|
16
|
+
return 'proposed';
|
|
17
|
+
case 'rejected':
|
|
18
|
+
return 'rejected';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function folderForStatus(status) {
|
|
22
|
+
switch (status) {
|
|
23
|
+
case 'accepted':
|
|
24
|
+
case 'superseded':
|
|
25
|
+
return 'decisions';
|
|
26
|
+
case 'proposed':
|
|
27
|
+
return 'proposed';
|
|
28
|
+
case 'rejected':
|
|
29
|
+
return 'rejected';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function section(record, heading) {
|
|
33
|
+
return record.sections.find((candidate) => candidate.heading === heading)?.body;
|
|
34
|
+
}
|
|
35
|
+
export function hasMeaningfulBody(body) {
|
|
36
|
+
if (body === undefined)
|
|
37
|
+
return false;
|
|
38
|
+
const withoutComments = body.replace(/<!--[\s\S]*?-->/g, '');
|
|
39
|
+
return withoutComments.trim().length > 0;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Parse a single ADR markdown file into its structured parts.
|
|
43
|
+
*
|
|
44
|
+
* The format is deliberately plain Markdown, with no front matter:
|
|
45
|
+
*
|
|
46
|
+
* # ADR: <title or "NNNN Title">
|
|
47
|
+
* Status: proposed | accepted | rejected — <reason>
|
|
48
|
+
* <blank line>
|
|
49
|
+
* ## Problem
|
|
50
|
+
* ...
|
|
51
|
+
*/
|
|
52
|
+
export function parseAdrFile(filePath) {
|
|
53
|
+
const text = readFileSync(filePath, 'utf8');
|
|
54
|
+
const lines = text.split(/\r?\n/);
|
|
55
|
+
const titleLine = lines[0] ?? '';
|
|
56
|
+
if (!titleLine.startsWith('# ADR: ')) {
|
|
57
|
+
throw new AdrFormatError('first line must be "# ADR: <title>"', filePath);
|
|
58
|
+
}
|
|
59
|
+
const title = titleLine.slice('# ADR: '.length).trim();
|
|
60
|
+
if (title.length === 0) {
|
|
61
|
+
throw new AdrFormatError('ADR title must not be empty', filePath);
|
|
62
|
+
}
|
|
63
|
+
const statusLine = lines[1] ?? '';
|
|
64
|
+
if (!statusLine.startsWith('Status: ')) {
|
|
65
|
+
throw new AdrFormatError('second line must be "Status: <status>"', filePath);
|
|
66
|
+
}
|
|
67
|
+
const statusText = statusLine.slice('Status: '.length).trim();
|
|
68
|
+
let status;
|
|
69
|
+
let rejectionReason;
|
|
70
|
+
let supersededBy;
|
|
71
|
+
if (statusText === 'proposed') {
|
|
72
|
+
status = 'proposed';
|
|
73
|
+
}
|
|
74
|
+
else if (statusText === 'accepted') {
|
|
75
|
+
status = 'accepted';
|
|
76
|
+
}
|
|
77
|
+
else if (statusText.startsWith('rejected — ')) {
|
|
78
|
+
status = 'rejected';
|
|
79
|
+
rejectionReason = statusText.slice('rejected — '.length).trim();
|
|
80
|
+
if (rejectionReason.length === 0) {
|
|
81
|
+
throw new AdrFormatError('rejected status must include a reason', filePath);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else if (/^superseded by \d{1,4}$/.test(statusText)) {
|
|
85
|
+
status = 'superseded';
|
|
86
|
+
supersededBy = Number(statusText.slice('superseded by '.length));
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
throw new AdrFormatError('status must be "proposed", "accepted", "rejected — <reason>", or "superseded by NNNN"', filePath);
|
|
90
|
+
}
|
|
91
|
+
if ((lines[2] ?? '') !== '') {
|
|
92
|
+
throw new AdrFormatError('line 3 must be blank', filePath);
|
|
93
|
+
}
|
|
94
|
+
const sections = [];
|
|
95
|
+
let current = null;
|
|
96
|
+
for (const line of lines.slice(3)) {
|
|
97
|
+
if (line.startsWith('## ')) {
|
|
98
|
+
if (current !== null)
|
|
99
|
+
sections.push(current);
|
|
100
|
+
const heading = line.slice(3).trim();
|
|
101
|
+
if (heading.length === 0) {
|
|
102
|
+
throw new AdrFormatError('section heading must not be empty', filePath);
|
|
103
|
+
}
|
|
104
|
+
current = { heading, body: '' };
|
|
105
|
+
}
|
|
106
|
+
else if (current !== null) {
|
|
107
|
+
current.body += `${line}\n`;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (current !== null)
|
|
111
|
+
sections.push(current);
|
|
112
|
+
const numberMatch = title.match(/^(\d{4})\s+(.+)$/);
|
|
113
|
+
const parsed = {
|
|
114
|
+
folder: 'proposed',
|
|
115
|
+
path: filePath,
|
|
116
|
+
fileName: basename(filePath),
|
|
117
|
+
title,
|
|
118
|
+
status,
|
|
119
|
+
sections,
|
|
120
|
+
};
|
|
121
|
+
if (rejectionReason !== undefined)
|
|
122
|
+
parsed.rejectionReason = rejectionReason;
|
|
123
|
+
if (supersededBy !== undefined)
|
|
124
|
+
parsed.supersededBy = supersededBy;
|
|
125
|
+
if (numberMatch !== null) {
|
|
126
|
+
parsed.number = Number(numberMatch[1]);
|
|
127
|
+
}
|
|
128
|
+
return parsed;
|
|
129
|
+
}
|
|
130
|
+
export function renderAdr(options) {
|
|
131
|
+
const lines = [`# ADR: ${options.title}`, `Status: ${options.status}`, ''];
|
|
132
|
+
for (const section of options.sections) {
|
|
133
|
+
lines.push(`## ${section.heading}`, '');
|
|
134
|
+
if (section.body.trim().length > 0) {
|
|
135
|
+
lines.push(section.body.replace(/\n+$/, ''), '');
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
lines.push('', '');
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return `${lines.join('\n').trimEnd()}\n`;
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=adr.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adr.js","sourceRoot":"","sources":["../../src/core/adr.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare const ADR_DIR = "adr";
|
|
2
|
+
export declare const CONFIG_FILE = "config.yaml";
|
|
3
|
+
export interface OpenAdrConfig {
|
|
4
|
+
/** Project context shown to agents and humans when records are created. */
|
|
5
|
+
context?: string;
|
|
6
|
+
/** Optional per-status rules, e.g. `rules.proposal: ["Keep it short"]`. */
|
|
7
|
+
rules?: Record<string, string[]>;
|
|
8
|
+
/** AI tool integrations selected at init time, e.g. `["claude", "codex"]`. */
|
|
9
|
+
tools?: string[];
|
|
10
|
+
/** The raw parsed YAML document (for forward compatibility). */
|
|
11
|
+
raw: Record<string, unknown>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Walk from `startDir` upward to find the nearest directory that contains
|
|
15
|
+
* `adr/config.yaml`. Returns the project root (the parent of `adr/`), or
|
|
16
|
+
* `undefined` when no OpenADR repository exists.
|
|
17
|
+
*/
|
|
18
|
+
export declare function findRoot(startDir: string): string | undefined;
|
|
19
|
+
export declare function requireRoot(startDir: string): string;
|
|
20
|
+
export declare function configPath(root: string): string;
|
|
21
|
+
export declare function readConfig(root: string): OpenAdrConfig;
|
|
22
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,OAAO,QAAQ,CAAC;AAC7B,eAAO,MAAM,WAAW,gBAAgB,CAAC;AAEzC,MAAM,WAAW,aAAa;IAC5B,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACjC,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,gEAAgE;IAChE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC9B;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAU7D;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQpD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CA8BtD"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join, resolve } from 'node:path';
|
|
3
|
+
import { isMap, parseDocument } from 'yaml';
|
|
4
|
+
export const ADR_DIR = 'adr';
|
|
5
|
+
export const CONFIG_FILE = 'config.yaml';
|
|
6
|
+
/**
|
|
7
|
+
* Walk from `startDir` upward to find the nearest directory that contains
|
|
8
|
+
* `adr/config.yaml`. Returns the project root (the parent of `adr/`), or
|
|
9
|
+
* `undefined` when no OpenADR repository exists.
|
|
10
|
+
*/
|
|
11
|
+
export function findRoot(startDir) {
|
|
12
|
+
let current = resolve(startDir);
|
|
13
|
+
for (;;) {
|
|
14
|
+
if (existsSync(join(current, ADR_DIR, CONFIG_FILE))) {
|
|
15
|
+
return current;
|
|
16
|
+
}
|
|
17
|
+
const parent = dirname(current);
|
|
18
|
+
if (parent === current)
|
|
19
|
+
return undefined;
|
|
20
|
+
current = parent;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function requireRoot(startDir) {
|
|
24
|
+
const root = findRoot(startDir);
|
|
25
|
+
if (root === undefined) {
|
|
26
|
+
throw new Error(`no OpenADR repository found from "${startDir}" (run "openadr init" first)`);
|
|
27
|
+
}
|
|
28
|
+
return root;
|
|
29
|
+
}
|
|
30
|
+
export function configPath(root) {
|
|
31
|
+
return join(root, ADR_DIR, CONFIG_FILE);
|
|
32
|
+
}
|
|
33
|
+
export function readConfig(root) {
|
|
34
|
+
const file = configPath(root);
|
|
35
|
+
const text = readFileSync(file, 'utf8');
|
|
36
|
+
const document = parseDocument(text, { prettyErrors: true });
|
|
37
|
+
const firstError = document.errors[0];
|
|
38
|
+
if (firstError !== undefined) {
|
|
39
|
+
throw new Error(`invalid ${ADR_DIR}/${CONFIG_FILE}: ${firstError.message}`);
|
|
40
|
+
}
|
|
41
|
+
if (!isMap(document.contents)) {
|
|
42
|
+
throw new Error(`invalid ${ADR_DIR}/${CONFIG_FILE}: top-level value must be a mapping`);
|
|
43
|
+
}
|
|
44
|
+
const raw = document.contents.toJSON();
|
|
45
|
+
const config = { raw };
|
|
46
|
+
if (typeof raw.context === 'string') {
|
|
47
|
+
config.context = raw.context;
|
|
48
|
+
}
|
|
49
|
+
if (Array.isArray(raw.tools) && raw.tools.every((entry) => typeof entry === 'string')) {
|
|
50
|
+
config.tools = raw.tools;
|
|
51
|
+
}
|
|
52
|
+
if (raw.rules !== null && typeof raw.rules === 'object' && !Array.isArray(raw.rules)) {
|
|
53
|
+
const rules = {};
|
|
54
|
+
for (const [key, value] of Object.entries(raw.rules)) {
|
|
55
|
+
if (Array.isArray(value) && value.every((entry) => typeof entry === 'string')) {
|
|
56
|
+
rules[key] = value;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
config.rules = rules;
|
|
60
|
+
}
|
|
61
|
+
return config;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type AdrFolder, type AdrRecord } from './adr.js';
|
|
2
|
+
export declare const FOLDERS: AdrFolder[];
|
|
3
|
+
export interface InitResult {
|
|
4
|
+
root: string;
|
|
5
|
+
created: string[];
|
|
6
|
+
}
|
|
7
|
+
export declare function initRepository(targetDir: string, tools?: string[]): InitResult;
|
|
8
|
+
export declare function adrRoot(root: string): string;
|
|
9
|
+
export declare function folderPath(root: string, folder: AdrFolder): string;
|
|
10
|
+
export declare function listRecords(root: string): AdrRecord[];
|
|
11
|
+
export declare function resolveRecord(root: string, query: string): AdrRecord;
|
|
12
|
+
export declare function nextDecisionNumber(root: string): number;
|
|
13
|
+
export declare function todayStamp(): string;
|
|
14
|
+
export declare function writeRecord(root: string, folder: AdrFolder, fileName: string, content: string): string;
|
|
15
|
+
export declare function removeRecord(record: AdrRecord): void;
|
|
16
|
+
export declare function readRecord(record: AdrRecord): string;
|
|
17
|
+
export declare function displayName(record: AdrRecord): string;
|
|
18
|
+
export declare function relativePath(record: AdrRecord): string;
|
|
19
|
+
//# sourceMappingURL=repository.d.ts.map
|
|
@@ -0,0 +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;AAwDD,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;AAED,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,UAAU,IAAI,MAAM,CAMnC;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,CAOrD;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAEtD"}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync, } from 'node:fs';
|
|
2
|
+
import { basename, join, relative, resolve } from 'node:path';
|
|
3
|
+
import { ADR_DIR, CONFIG_FILE, configPath } from './config.js';
|
|
4
|
+
import { parseAdrFile } from './adr.js';
|
|
5
|
+
export const FOLDERS = ['decisions', 'proposed', 'rejected'];
|
|
6
|
+
function initConfig(tools) {
|
|
7
|
+
// tools 始终作为顶层键输出:全注释的 YAML 文档会被解析为空,
|
|
8
|
+
// readConfig 的 isMap 检查会报 "top-level value must be a mapping"。
|
|
9
|
+
const toolsYaml = `tools: [${tools.join(', ')}]\n`;
|
|
10
|
+
return `# OpenADR configuration
|
|
11
|
+
# Fill in \`context\` and it is injected as a comment into every new
|
|
12
|
+
# proposal/decision draft (openadr propose / openadr decide).
|
|
13
|
+
# context: |
|
|
14
|
+
# Tech stack: TypeScript
|
|
15
|
+
# Conventions that decision records should respect.
|
|
16
|
+
|
|
17
|
+
# AI tool integrations written by openadr init --tools.
|
|
18
|
+
${toolsYaml}
|
|
19
|
+
# Optional per-status conventions. These are hints for writers and agents;
|
|
20
|
+
# validate does not enforce them (it cannot check prose).
|
|
21
|
+
# rules:
|
|
22
|
+
# proposal:
|
|
23
|
+
# - Keep proposals under 500 words.
|
|
24
|
+
# decision:
|
|
25
|
+
# - Always name the owning team.
|
|
26
|
+
`;
|
|
27
|
+
}
|
|
28
|
+
const INIT_README = `# Architecture Decision Records
|
|
29
|
+
|
|
30
|
+
This directory is an OpenADR repository. Each record is plain Markdown with a
|
|
31
|
+
machine-checkable header and lifecycle folders.
|
|
32
|
+
|
|
33
|
+
## Folders
|
|
34
|
+
|
|
35
|
+
| Folder | Meaning |
|
|
36
|
+
| --- | --- |
|
|
37
|
+
| \`decisions/\` | Accepted decisions, numbered \`NNNN\`, current truth |
|
|
38
|
+
| \`proposed/\` | Proposals that are not yet accepted or rejected |
|
|
39
|
+
| \`rejected/\` | Rejected proposals, frozen for the record |
|
|
40
|
+
|
|
41
|
+
## Record format
|
|
42
|
+
|
|
43
|
+
Every record starts with exactly:
|
|
44
|
+
|
|
45
|
+
\`\`\`markdown
|
|
46
|
+
# ADR: <title>
|
|
47
|
+
|
|
48
|
+
Status: proposed | accepted | rejected — <reason>
|
|
49
|
+
\`\`\`
|
|
50
|
+
|
|
51
|
+
Accepted decisions use \`# ADR: NNNN <title>\` and require
|
|
52
|
+
\`Problem\`, \`Decision\`, \`Alternatives considered\`, and \`Consequences\`.
|
|
53
|
+
Proposals require \`Problem\`, \`Proposal\`, \`Alternatives considered\`,
|
|
54
|
+
\`Acceptance criteria\`, and \`Risks\`.
|
|
55
|
+
|
|
56
|
+
Run \`openadr validate\` to check every record.
|
|
57
|
+
`;
|
|
58
|
+
export function initRepository(targetDir, tools = []) {
|
|
59
|
+
const root = resolve(targetDir);
|
|
60
|
+
const adrRoot = join(root, ADR_DIR);
|
|
61
|
+
if (existsSync(adrRoot)) {
|
|
62
|
+
throw new Error(`OpenADR already exists at ${adrRoot}`);
|
|
63
|
+
}
|
|
64
|
+
const created = [];
|
|
65
|
+
mkdirSync(adrRoot, { recursive: true });
|
|
66
|
+
created.push(ADR_DIR);
|
|
67
|
+
for (const folder of FOLDERS) {
|
|
68
|
+
const path = join(adrRoot, folder);
|
|
69
|
+
mkdirSync(path, { recursive: true });
|
|
70
|
+
writeFileSync(join(path, '.gitkeep'), '');
|
|
71
|
+
created.push(`${ADR_DIR}/${folder}`);
|
|
72
|
+
}
|
|
73
|
+
const config = configPath(root);
|
|
74
|
+
writeFileSync(config, initConfig(tools));
|
|
75
|
+
created.push(`${ADR_DIR}/${CONFIG_FILE}`);
|
|
76
|
+
const readme = join(adrRoot, 'README.md');
|
|
77
|
+
writeFileSync(readme, INIT_README);
|
|
78
|
+
created.push(`${ADR_DIR}/README.md`);
|
|
79
|
+
return { root, created };
|
|
80
|
+
}
|
|
81
|
+
export function adrRoot(root) {
|
|
82
|
+
return join(root, ADR_DIR);
|
|
83
|
+
}
|
|
84
|
+
export function folderPath(root, folder) {
|
|
85
|
+
return join(adrRoot(root), folder);
|
|
86
|
+
}
|
|
87
|
+
export function listRecords(root) {
|
|
88
|
+
const records = [];
|
|
89
|
+
for (const folder of FOLDERS) {
|
|
90
|
+
const dir = folderPath(root, folder);
|
|
91
|
+
if (!existsSync(dir))
|
|
92
|
+
continue;
|
|
93
|
+
for (const entry of readdirSync(dir)) {
|
|
94
|
+
if (!entry.endsWith('.md'))
|
|
95
|
+
continue;
|
|
96
|
+
const path = join(dir, entry);
|
|
97
|
+
try {
|
|
98
|
+
const record = parseAdrFile(path);
|
|
99
|
+
record.folder = folder;
|
|
100
|
+
records.push(record);
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
104
|
+
throw new Error(`failed to parse ${relative(root, path)}: ${message}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
records.sort((a, b) => {
|
|
109
|
+
const folderOrder = (folder) => FOLDERS.indexOf(folder);
|
|
110
|
+
const folderDelta = folderOrder(a.folder) - folderOrder(b.folder);
|
|
111
|
+
if (folderDelta !== 0)
|
|
112
|
+
return folderDelta;
|
|
113
|
+
if (a.folder === 'decisions')
|
|
114
|
+
return (a.number ?? 0) - (b.number ?? 0);
|
|
115
|
+
return a.fileName.localeCompare(b.fileName);
|
|
116
|
+
});
|
|
117
|
+
return records;
|
|
118
|
+
}
|
|
119
|
+
export function resolveRecord(root, query) {
|
|
120
|
+
const records = listRecords(root);
|
|
121
|
+
const normalized = query.trim();
|
|
122
|
+
const candidates = records.filter((record) => {
|
|
123
|
+
const bareName = record.fileName.replace(/\.md$/, '');
|
|
124
|
+
const slug = bareName.replace(/^\d{4}-\d{2}-\d{2}-/, '').replace(/^\d{4}-/, '');
|
|
125
|
+
if (record.fileName === normalized || bareName === normalized || slug === normalized) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
if (record.title === normalized || `# ADR: ${record.title}` === normalized) {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
if (record.folder === 'decisions') {
|
|
132
|
+
const padded = String(record.number ?? 0).padStart(4, '0');
|
|
133
|
+
if (padded === normalized || String(record.number ?? 0) === normalized) {
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return false;
|
|
138
|
+
});
|
|
139
|
+
if (candidates.length === 0) {
|
|
140
|
+
throw new Error(`no ADR matches "${query}"`);
|
|
141
|
+
}
|
|
142
|
+
if (candidates.length > 1) {
|
|
143
|
+
const paths = candidates.map((record) => relative(root, record.path)).join(', ');
|
|
144
|
+
throw new Error(`"${query}" is ambiguous; matches: ${paths}`);
|
|
145
|
+
}
|
|
146
|
+
const first = candidates[0];
|
|
147
|
+
if (first === undefined) {
|
|
148
|
+
throw new Error(`no ADR matches "${query}"`);
|
|
149
|
+
}
|
|
150
|
+
return first;
|
|
151
|
+
}
|
|
152
|
+
export function nextDecisionNumber(root) {
|
|
153
|
+
let max = 0;
|
|
154
|
+
for (const record of listRecords(root)) {
|
|
155
|
+
if (record.folder === 'decisions' && (record.number ?? 0) > max) {
|
|
156
|
+
max = record.number ?? 0;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return max + 1;
|
|
160
|
+
}
|
|
161
|
+
export function todayStamp() {
|
|
162
|
+
const now = new Date();
|
|
163
|
+
const year = now.getFullYear();
|
|
164
|
+
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
165
|
+
const day = String(now.getDate()).padStart(2, '0');
|
|
166
|
+
return `${year}-${month}-${day}`;
|
|
167
|
+
}
|
|
168
|
+
export function writeRecord(root, folder, fileName, content) {
|
|
169
|
+
const path = join(folderPath(root, folder), fileName);
|
|
170
|
+
writeFileSync(path, content);
|
|
171
|
+
return path;
|
|
172
|
+
}
|
|
173
|
+
export function removeRecord(record) {
|
|
174
|
+
rmSync(record.path);
|
|
175
|
+
}
|
|
176
|
+
export function readRecord(record) {
|
|
177
|
+
return readFileSync(record.path, 'utf8');
|
|
178
|
+
}
|
|
179
|
+
export function displayName(record) {
|
|
180
|
+
if (record.folder === 'decisions' && Number.isInteger(record.number)) {
|
|
181
|
+
const padded = String(record.number).padStart(4, '0');
|
|
182
|
+
const titleWithoutNumber = record.title.replace(/^\d{4}\s+/, '');
|
|
183
|
+
return `[${padded}] ${titleWithoutNumber}`;
|
|
184
|
+
}
|
|
185
|
+
return record.title;
|
|
186
|
+
}
|
|
187
|
+
export function relativePath(record) {
|
|
188
|
+
return join(ADR_DIR, record.folder, basename(record.path));
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=repository.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repository.js","sourceRoot":"","sources":["../../src/core/repository.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert an ADR title into a file-name slug.
|
|
3
|
+
*
|
|
4
|
+
* The slug keeps ASCII letters, digits, and CJK characters so that English
|
|
5
|
+
* and Chinese titles both remain readable in file names. Everything else
|
|
6
|
+
* becomes a single dash, and the result is capped so generated paths stay
|
|
7
|
+
* reasonable on every file system.
|
|
8
|
+
*/
|
|
9
|
+
export declare function slugify(title: string): string;
|
|
10
|
+
//# sourceMappingURL=slug.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slug.d.ts","sourceRoot":"","sources":["../../src/core/slug.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAU7C"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert an ADR title into a file-name slug.
|
|
3
|
+
*
|
|
4
|
+
* The slug keeps ASCII letters, digits, and CJK characters so that English
|
|
5
|
+
* and Chinese titles both remain readable in file names. Everything else
|
|
6
|
+
* becomes a single dash, and the result is capped so generated paths stay
|
|
7
|
+
* reasonable on every file system.
|
|
8
|
+
*/
|
|
9
|
+
export function slugify(title) {
|
|
10
|
+
const slug = title
|
|
11
|
+
.trim()
|
|
12
|
+
.toLowerCase()
|
|
13
|
+
.replace(/['"`'']/g, '')
|
|
14
|
+
.replace(/[^a-z0-9\u4e00-\u9fff]+/g, '-')
|
|
15
|
+
.replace(/^-+|-+$/g, '')
|
|
16
|
+
.slice(0, 80);
|
|
17
|
+
return slug.length > 0 ? slug : 'untitled';
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=slug.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slug.js","sourceRoot":"","sources":["../../src/core/slug.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,MAAM,IAAI,GAAG,KAAK;SACf,IAAI,EAAE;SACN,WAAW,EAAE;SACb,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;SACvB,OAAO,CAAC,0BAA0B,EAAE,GAAG,CAAC;SACxC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;SACvB,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEhB,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC;AAC7C,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AdrRecord } from './adr.js';
|
|
2
|
+
export declare function proposalTemplate(title: string, context?: string): string;
|
|
3
|
+
export declare function decisionTemplate(number: number, title: string, context?: string): string;
|
|
4
|
+
export declare function rejectedTemplate(title: string, reason: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Convert a proposal into an accepted decision using the same mechanical
|
|
7
|
+
* rewrite the format requires: Proposal becomes Decision, and the
|
|
8
|
+
* acceptance criteria and risks are folded into Consequences.
|
|
9
|
+
*/
|
|
10
|
+
export declare function proposalToDecision(proposal: AdrRecord, number: number): string;
|
|
11
|
+
//# sourceMappingURL=templates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../src/core/templates.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAiB1C,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAyBxE;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,CAgBtE;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAqC9E"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 把 config.yaml 的 context 注入模板:放在标题块之后、第一个 section 之前。
|
|
3
|
+
* 解析器忽略游离文本(current 为 null 时不收集),accept 的机械改写也会
|
|
4
|
+
* 丢弃它——注释只在草案期可见,正是写作者需要项目上下文的时刻。
|
|
5
|
+
*/
|
|
6
|
+
function contextBlock(context) {
|
|
7
|
+
const text = context?.trim() ?? '';
|
|
8
|
+
// 空 context 或纯占位注释(老 init 模板遗留)都不注入
|
|
9
|
+
if (text.length === 0 || text.startsWith('<!--'))
|
|
10
|
+
return '';
|
|
11
|
+
return `<!-- Project context (adr/config.yaml):
|
|
12
|
+
${text}
|
|
13
|
+
-->
|
|
14
|
+
`;
|
|
15
|
+
}
|
|
16
|
+
export function proposalTemplate(title, context) {
|
|
17
|
+
return `# ADR: ${title}
|
|
18
|
+
Status: proposed
|
|
19
|
+
|
|
20
|
+
${contextBlock(context)}## Problem
|
|
21
|
+
|
|
22
|
+
<!-- What problem or opportunity does this decision address? Why now? -->
|
|
23
|
+
|
|
24
|
+
## Proposal
|
|
25
|
+
|
|
26
|
+
<!-- The proposed decision. Be specific about what will change. -->
|
|
27
|
+
|
|
28
|
+
## Alternatives considered
|
|
29
|
+
|
|
30
|
+
<!-- Each genuine alternative and why it lost. Keep this section: it is the
|
|
31
|
+
part of a decision record that prevents re-litigating old choices. -->
|
|
32
|
+
|
|
33
|
+
## Acceptance criteria
|
|
34
|
+
|
|
35
|
+
<!-- Observable state that means the proposal is done. -->
|
|
36
|
+
|
|
37
|
+
## Risks
|
|
38
|
+
|
|
39
|
+
<!-- What could go wrong, and what the change knowingly gives up. -->
|
|
40
|
+
`;
|
|
41
|
+
}
|
|
42
|
+
export function decisionTemplate(number, title, context) {
|
|
43
|
+
const padded = String(number).padStart(4, '0');
|
|
44
|
+
return `# ADR: ${padded} ${title}
|
|
45
|
+
Status: accepted
|
|
46
|
+
|
|
47
|
+
${contextBlock(context)}## Problem
|
|
48
|
+
|
|
49
|
+
<!-- What problem or opportunity does this decision address? -->
|
|
50
|
+
|
|
51
|
+
## Decision
|
|
52
|
+
|
|
53
|
+
<!-- The decision that shipped, in present tense. -->
|
|
54
|
+
|
|
55
|
+
## Alternatives considered
|
|
56
|
+
|
|
57
|
+
<!-- Each genuine alternative and why it lost. Keep this section: it is the
|
|
58
|
+
part of a decision record that prevents re-litigating old choices. -->
|
|
59
|
+
|
|
60
|
+
## Consequences
|
|
61
|
+
|
|
62
|
+
<!-- What the trade-off cost and bought. -->
|
|
63
|
+
`;
|
|
64
|
+
}
|
|
65
|
+
export function rejectedTemplate(title, reason) {
|
|
66
|
+
return `# ADR: ${title}
|
|
67
|
+
Status: rejected — ${reason}
|
|
68
|
+
|
|
69
|
+
## Problem
|
|
70
|
+
|
|
71
|
+
<!-- What problem or opportunity was this proposal addressing? -->
|
|
72
|
+
|
|
73
|
+
## Proposal
|
|
74
|
+
|
|
75
|
+
<!-- The rejected proposal. -->
|
|
76
|
+
|
|
77
|
+
## Alternatives considered
|
|
78
|
+
|
|
79
|
+
<!-- Each genuine alternative and why it lost. -->
|
|
80
|
+
`;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Convert a proposal into an accepted decision using the same mechanical
|
|
84
|
+
* rewrite the format requires: Proposal becomes Decision, and the
|
|
85
|
+
* acceptance criteria and risks are folded into Consequences.
|
|
86
|
+
*/
|
|
87
|
+
export function proposalToDecision(proposal, number) {
|
|
88
|
+
const problem = sectionBody(proposal, 'Problem');
|
|
89
|
+
const decision = sectionBody(proposal, 'Proposal');
|
|
90
|
+
const alternatives = sectionBody(proposal, 'Alternatives considered');
|
|
91
|
+
const acceptance = sectionBody(proposal, 'Acceptance criteria');
|
|
92
|
+
const risks = sectionBody(proposal, 'Risks');
|
|
93
|
+
const consequences = [];
|
|
94
|
+
if (acceptance.trim().length > 0) {
|
|
95
|
+
consequences.push('### Acceptance criteria', '', acceptance.trim(), '');
|
|
96
|
+
}
|
|
97
|
+
if (risks.trim().length > 0) {
|
|
98
|
+
consequences.push('### Risks', '', risks.trim(), '');
|
|
99
|
+
}
|
|
100
|
+
if (consequences.length === 0) {
|
|
101
|
+
consequences.push('_No consequences recorded._', '');
|
|
102
|
+
}
|
|
103
|
+
const padded = String(number).padStart(4, '0');
|
|
104
|
+
return `# ADR: ${padded} ${proposal.title}
|
|
105
|
+
Status: accepted
|
|
106
|
+
|
|
107
|
+
## Problem
|
|
108
|
+
|
|
109
|
+
${problem.trim() || '_No problem recorded._'}
|
|
110
|
+
|
|
111
|
+
## Decision
|
|
112
|
+
|
|
113
|
+
${decision.trim() || '_No decision recorded._'}
|
|
114
|
+
|
|
115
|
+
## Alternatives considered
|
|
116
|
+
|
|
117
|
+
${alternatives.trim() || '_No alternatives recorded._'}
|
|
118
|
+
|
|
119
|
+
## Consequences
|
|
120
|
+
|
|
121
|
+
${consequences.join('\n')}\n`;
|
|
122
|
+
}
|
|
123
|
+
function sectionBody(record, heading) {
|
|
124
|
+
return record.sections.find((candidate) => candidate.heading === heading)?.body.trim() ?? '';
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=templates.js.map
|