@kampus/decisions-index 0.1.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/README.md +48 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +88 -0
- package/dist/bin.js.map +1 -0
- package/dist/decisions-index.d.ts +78 -0
- package/dist/decisions-index.d.ts.map +1 -0
- package/dist/decisions-index.js +166 -0
- package/dist/decisions-index.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# @kampus/decisions-index
|
|
2
|
+
|
|
3
|
+
Generate `.decisions/index.md` from the ADR files (ADR
|
|
4
|
+
[0066](../../.decisions/0066-generate-decisions-index.md)).
|
|
5
|
+
|
|
6
|
+
`.decisions/index.md` is **generated output**, not a hand-maintained file. The
|
|
7
|
+
source of truth is each `.decisions/NNNN-*.md` file's YAML front-matter (`id`,
|
|
8
|
+
`title`, `status`, `date`); the index table is derived from it, ordered ascending
|
|
9
|
+
by `id`. Two doc PRs that add two different ADR files no longer share a textual
|
|
10
|
+
anchor (the tail of the table), so they can't collide on `index.md` — the
|
|
11
|
+
concurrent-merge friction ADR 0066 removes. The same gate folds in the sibling
|
|
12
|
+
problem: a **duplicate ADR `id`** across files fails the check.
|
|
13
|
+
|
|
14
|
+
## Shape
|
|
15
|
+
|
|
16
|
+
Per the repo's mechanical-tooling idiom (`leak-guard` / `epic-ledger` /
|
|
17
|
+
`crabbox-manifest`): a pure, unit-tested core + a thin Effect CLI bin.
|
|
18
|
+
|
|
19
|
+
- `src/decisions-index.ts` — the pure core. `buildIndex(files)` parses every
|
|
20
|
+
file's front-matter, fails on a duplicate id (`DuplicateIdError`) or a
|
|
21
|
+
malformed file (`FrontmatterError`), and renders the deterministic table.
|
|
22
|
+
Status/title render **verbatim** (they may carry inline markdown, e.g. a linked
|
|
23
|
+
`superseded by [0009](0009-slug.md)`).
|
|
24
|
+
- `src/bin.ts` — the `effect/unstable/cli` bin (`generate` + `check`).
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# Rewrite .decisions/index.md from the ADR files (authors / the /adr skill):
|
|
30
|
+
pnpm --filter @kampus/decisions-index generate
|
|
31
|
+
|
|
32
|
+
# CI gate — exit 1 on a stale index OR a duplicate ADR id:
|
|
33
|
+
pnpm --filter @kampus/decisions-index check
|
|
34
|
+
|
|
35
|
+
# Point at a different directory:
|
|
36
|
+
node packages/decisions-index/src/bin.ts check --dir path/to/.decisions
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Exit codes: `0` clean, `1` gate failure (stale index or duplicate id; reason on
|
|
40
|
+
stderr), any other non-zero = the run could not complete (e.g. unreadable dir).
|
|
41
|
+
|
|
42
|
+
CI runs `check` on every PR via
|
|
43
|
+
[`.github/workflows/decisions-index.yml`](../../.github/workflows/decisions-index.yml).
|
|
44
|
+
|
|
45
|
+
## Do not hand-edit `index.md`
|
|
46
|
+
|
|
47
|
+
Edit the ADR file's front-matter and regenerate. A hand-appended row is exactly
|
|
48
|
+
the collision this package removes.
|
package/dist/bin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":""}
|
package/dist/bin.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `decisions-index` CLI — the author + CI surface for ADR 0066.
|
|
4
|
+
*
|
|
5
|
+
* node src/bin.ts generate # rewrite .decisions/index.md from the ADR files
|
|
6
|
+
* node src/bin.ts check # CI gate: exit non-zero on a stale index or a dup id
|
|
7
|
+
* node src/bin.ts <mode> --dir <d> # point at a different .decisions dir (default: ./.decisions)
|
|
8
|
+
*
|
|
9
|
+
* `generate` is what the `/adr` skill (and an author) runs instead of hand-editing
|
|
10
|
+
* the table; `check` is the CI gate that fails on (a) a committed `index.md` that
|
|
11
|
+
* differs from the generated one (stale) and (b) a duplicate ADR `id` — closing the
|
|
12
|
+
* number-collision class in the same step (ADR 0066).
|
|
13
|
+
*
|
|
14
|
+
* Exit-code contract: 0 = clean (check passed / generate wrote), 1 = the gate
|
|
15
|
+
* failed (stale index or duplicate id; report on stderr); any OTHER non-zero means
|
|
16
|
+
* the run could not complete (e.g. the dir is unreadable). Wired per effect-smol's
|
|
17
|
+
* CLI guidance (mirrors `@kampus/epic-ledger` / `@kampus/leak-guard` /
|
|
18
|
+
* `changelog-derive`): `effect/unstable/cli`, the Node platform over
|
|
19
|
+
* `NodeServices.layer`, run via `NodeRuntime.runMain`.
|
|
20
|
+
*/
|
|
21
|
+
import { readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
22
|
+
import { join } from "node:path";
|
|
23
|
+
import { NodeRuntime, NodeServices } from "@effect/platform-node";
|
|
24
|
+
import { Console, Data, Effect } from "effect";
|
|
25
|
+
import { Command, Flag } from "effect/unstable/cli";
|
|
26
|
+
import { buildIndex, DuplicateIdError } from "./decisions-index.js";
|
|
27
|
+
const INDEX_FILE = "index.md";
|
|
28
|
+
const ADR_FILE = /^\d+[A-Za-z]*-.+\.md$/;
|
|
29
|
+
const GATE_FAIL_EXIT_CODE = 1;
|
|
30
|
+
// A directory/file IO failure that should crash (non-1 exit): the run couldn't complete.
|
|
31
|
+
class IoError extends Data.TaggedError("IoError") {
|
|
32
|
+
}
|
|
33
|
+
// Carries the non-zero gate-fail exit (the report is already on stderr). Distinct from
|
|
34
|
+
// IoError so a stale index / dup id exits 1 while an unreadable dir exits differently.
|
|
35
|
+
class CheckFailed extends Data.TaggedError("CheckFailed") {
|
|
36
|
+
}
|
|
37
|
+
/** Read every ADR file (NNNN[a]-slug.md) in `dir`, excluding the generated index. */
|
|
38
|
+
const readAdrFiles = (dir) => Effect.try({
|
|
39
|
+
try: () => readdirSync(dir)
|
|
40
|
+
.filter((f) => f !== INDEX_FILE && ADR_FILE.test(f))
|
|
41
|
+
.sort()
|
|
42
|
+
.map((file) => ({ file, text: readFileSync(join(dir, file), "utf8") })),
|
|
43
|
+
catch: (cause) => new IoError({ path: dir, cause }),
|
|
44
|
+
});
|
|
45
|
+
/** Build the index, folding a duplicate id into a CheckFailed gate failure. */
|
|
46
|
+
const build = (files) => Effect.try({
|
|
47
|
+
try: () => buildIndex(files),
|
|
48
|
+
catch: (cause) => cause instanceof DuplicateIdError
|
|
49
|
+
? new CheckFailed({ reason: cause.message })
|
|
50
|
+
: new CheckFailed({ reason: String(cause?.message ?? cause) }),
|
|
51
|
+
});
|
|
52
|
+
const dirFlag = Flag.string("dir").pipe(Flag.withDefault(".decisions"), Flag.withDescription("the .decisions directory to read ADR files from (default: .decisions)"));
|
|
53
|
+
const generate = Command.make("generate", { dir: dirFlag }, Effect.fn(function* ({ dir }) {
|
|
54
|
+
const markdown = yield* readAdrFiles(dir).pipe(Effect.flatMap(build));
|
|
55
|
+
const target = join(dir, INDEX_FILE);
|
|
56
|
+
yield* Effect.try({
|
|
57
|
+
try: () => writeFileSync(target, markdown),
|
|
58
|
+
catch: (cause) => new IoError({ path: target, cause }),
|
|
59
|
+
});
|
|
60
|
+
yield* Console.log(`decisions-index: wrote ${target}`);
|
|
61
|
+
})).pipe(Command.withDescription("Regenerate .decisions/index.md from the ADR files"));
|
|
62
|
+
const check = Command.make("check", { dir: dirFlag }, Effect.fn(function* ({ dir }) {
|
|
63
|
+
const expected = yield* readAdrFiles(dir).pipe(Effect.flatMap(build));
|
|
64
|
+
const target = join(dir, INDEX_FILE);
|
|
65
|
+
const committed = yield* Effect.try({
|
|
66
|
+
try: () => readFileSync(target, "utf8"),
|
|
67
|
+
catch: () => "",
|
|
68
|
+
}).pipe(Effect.orElseSucceed(() => ""));
|
|
69
|
+
if (committed === expected) {
|
|
70
|
+
yield* Console.log("decisions-index: index.md is up to date");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
return yield* Effect.fail(new CheckFailed({
|
|
74
|
+
reason: `${target} is stale — it does not match the generated index.\n` +
|
|
75
|
+
"Run `pnpm --filter @kampus/decisions-index generate` and commit the result\n" +
|
|
76
|
+
"(edit the ADR file's front-matter, never index.md by hand — ADR 0066).",
|
|
77
|
+
}));
|
|
78
|
+
})).pipe(Command.withDescription("Verify the committed index.md is fresh and has no duplicate ADR id"));
|
|
79
|
+
const cli = Command.make("decisions-index").pipe(Command.withSubcommands([generate, check]), Command.withDescription("Generate .decisions/index.md from the ADR files (ADR 0066)"));
|
|
80
|
+
cli.pipe(Command.run({ version: "0.1.0" }),
|
|
81
|
+
// CheckFailed is the expected gate-fail signal — print its reason on stderr and exit
|
|
82
|
+
// non-zero WITHOUT a stack trace; genuine crashes (IoError, etc.) still get the
|
|
83
|
+
// default error report (a different non-zero exit, per the exit-code contract).
|
|
84
|
+
Effect.catchTag("CheckFailed", (e) => Effect.sync(() => {
|
|
85
|
+
process.stderr.write(`decisions-index: ${e.reason}\n`);
|
|
86
|
+
process.exit(GATE_FAIL_EXIT_CODE);
|
|
87
|
+
})), Effect.provide(NodeServices.layer), NodeRuntime.runMain);
|
|
88
|
+
//# sourceMappingURL=bin.js.map
|
package/dist/bin.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@kampus/decisions-index` core — the pure, IO-free derivation of
|
|
3
|
+
* `.decisions/index.md` from the ADR files (ADR 0066).
|
|
4
|
+
*
|
|
5
|
+
* The single source of truth is each `.decisions/NNNN-*.md` file's YAML
|
|
6
|
+
* front-matter (`id`, `title`, `status`, `date`); the index table is *derived*
|
|
7
|
+
* output, deterministically ordered by `id` ascending. Two doc PRs that add two
|
|
8
|
+
* different ADR files no longer share a textual anchor (the tail of the table),
|
|
9
|
+
* so they cannot collide on `index.md` — the friction ADR 0066 removes.
|
|
10
|
+
*
|
|
11
|
+
* `buildIndex` folds the sibling problem in: a **duplicate `id`** across files is
|
|
12
|
+
* a `DuplicateIdError`, so the same gate that catches a stale index catches two
|
|
13
|
+
* PRs racing the same ADR number.
|
|
14
|
+
*
|
|
15
|
+
* Two non-obvious points, both load-bearing and pinned by the unit tests:
|
|
16
|
+
* - `title`/`status` render **verbatim** from front-matter — they may carry
|
|
17
|
+
* inline markdown (a linked `superseded by [0009](…)`), so the front-matter is
|
|
18
|
+
* the curated display text, not just a bare keyword. Make the file right; the
|
|
19
|
+
* table mirrors it.
|
|
20
|
+
* - ordering is numeric-then-suffix so a lettered id like `0034a` sorts between
|
|
21
|
+
* `0034` and `0035` (a plain string sort would too here, but the explicit
|
|
22
|
+
* numeric compare keeps it correct if ids ever stop being zero-padded).
|
|
23
|
+
*/
|
|
24
|
+
export interface AdrEntry {
|
|
25
|
+
/** The ADR id from front-matter, e.g. `0034` or `0034a`. */
|
|
26
|
+
readonly id: string;
|
|
27
|
+
/** Display title, rendered verbatim (may contain inline markdown). */
|
|
28
|
+
readonly title: string;
|
|
29
|
+
/** Display status, rendered verbatim (may contain inline markdown links). */
|
|
30
|
+
readonly status: string;
|
|
31
|
+
/** ISO date string, rendered verbatim. */
|
|
32
|
+
readonly date: string;
|
|
33
|
+
/** The file the index row links to, e.g. `0034-fate-native-sse-protocol.md`. */
|
|
34
|
+
readonly file: string;
|
|
35
|
+
}
|
|
36
|
+
/** A `.decisions/` file handed to the core: its base name + full text. */
|
|
37
|
+
export interface AdrFile {
|
|
38
|
+
/** Base name only, e.g. `0034-fate-native-sse-protocol.md` (no directory). */
|
|
39
|
+
readonly file: string;
|
|
40
|
+
/** The file's full UTF-8 contents. */
|
|
41
|
+
readonly text: string;
|
|
42
|
+
}
|
|
43
|
+
export declare class DuplicateIdError extends Error {
|
|
44
|
+
readonly id: string;
|
|
45
|
+
readonly files: ReadonlyArray<string>;
|
|
46
|
+
constructor(id: string, files: ReadonlyArray<string>);
|
|
47
|
+
}
|
|
48
|
+
export declare class FrontmatterError extends Error {
|
|
49
|
+
readonly file: string;
|
|
50
|
+
constructor(file: string, reason: string);
|
|
51
|
+
}
|
|
52
|
+
declare const FRONTMATTER_FIELDS: readonly ["id", "title", "status", "date"];
|
|
53
|
+
/**
|
|
54
|
+
* Parse the four index-relevant fields out of a `---`-delimited YAML block.
|
|
55
|
+
* Only the top-level `id`/`title`/`status`/`date` scalars are read; everything
|
|
56
|
+
* else (tags, body) is ignored. Returns the fields present; the caller validates.
|
|
57
|
+
*/
|
|
58
|
+
export declare const parseFrontmatter: (text: string) => Partial<Record<(typeof FRONTMATTER_FIELDS)[number], string>>;
|
|
59
|
+
/** Parse one ADR file into an entry, or throw `FrontmatterError` if a field is missing. */
|
|
60
|
+
export declare const parseAdrFile: ({ file, text }: AdrFile) => AdrEntry;
|
|
61
|
+
/** Entries sorted ascending by id (numeric part, then letter suffix). */
|
|
62
|
+
export declare const sortEntries: (entries: ReadonlyArray<AdrEntry>) => ReadonlyArray<AdrEntry>;
|
|
63
|
+
/** First duplicated id across the entries (lowest by sort order), or null if all unique. */
|
|
64
|
+
export declare const findDuplicateId: (entries: ReadonlyArray<AdrEntry>) => DuplicateIdError | null;
|
|
65
|
+
/**
|
|
66
|
+
* Render the canonical `.decisions/index.md` from sorted entries. The preamble +
|
|
67
|
+
* table header are fixed; rows are one per ADR. The trailing newline matches a
|
|
68
|
+
* POSIX text file (the committed index ends in a single `\n`).
|
|
69
|
+
*/
|
|
70
|
+
export declare const renderIndex: (entries: ReadonlyArray<AdrEntry>) => string;
|
|
71
|
+
/**
|
|
72
|
+
* Build the index markdown from the raw `.decisions/` files. Parses every file,
|
|
73
|
+
* fails on a duplicate id (`DuplicateIdError`) or a malformed file
|
|
74
|
+
* (`FrontmatterError`), and otherwise returns the deterministically-ordered table.
|
|
75
|
+
*/
|
|
76
|
+
export declare const buildIndex: (files: ReadonlyArray<AdrFile>) => string;
|
|
77
|
+
export {};
|
|
78
|
+
//# sourceMappingURL=decisions-index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decisions-index.d.ts","sourceRoot":"","sources":["../src/decisions-index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,WAAW,QAAQ;IACxB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,6EAA6E;IAC7E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,0CAA0C;IAC1C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AAED,0EAA0E;AAC1E,MAAM,WAAW,OAAO;IACvB,8EAA8E;IAC9E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,gBAAiB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;gBAC1B,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC;CAMpD;AAED,qBAAa,gBAAiB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBACV,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAKxC;AAED,QAAA,MAAM,kBAAkB,4CAA6C,CAAC;AAuBtE;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAC5B,MAAM,MAAM,KACV,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,kBAAkB,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,CAc7D,CAAC;AAEF,2FAA2F;AAC3F,eAAO,MAAM,YAAY,GAAI,gBAAc,OAAO,KAAG,QAcpD,CAAC;AASF,yEAAyE;AACzE,eAAO,MAAM,WAAW,GAAI,SAAS,aAAa,CAAC,QAAQ,CAAC,KAAG,aAAa,CAAC,QAAQ,CAMlF,CAAC;AAEJ,4FAA4F;AAC5F,eAAO,MAAM,eAAe,GAAI,SAAS,aAAa,CAAC,QAAQ,CAAC,KAAG,gBAAgB,GAAG,IAgBrF,CAAC;AAKF;;;;GAIG;AACH,eAAO,MAAM,WAAW,GAAI,SAAS,aAAa,CAAC,QAAQ,CAAC,KAAG,MAY9D,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,UAAU,GAAI,OAAO,aAAa,CAAC,OAAO,CAAC,KAAG,MAK1D,CAAC"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@kampus/decisions-index` core — the pure, IO-free derivation of
|
|
3
|
+
* `.decisions/index.md` from the ADR files (ADR 0066).
|
|
4
|
+
*
|
|
5
|
+
* The single source of truth is each `.decisions/NNNN-*.md` file's YAML
|
|
6
|
+
* front-matter (`id`, `title`, `status`, `date`); the index table is *derived*
|
|
7
|
+
* output, deterministically ordered by `id` ascending. Two doc PRs that add two
|
|
8
|
+
* different ADR files no longer share a textual anchor (the tail of the table),
|
|
9
|
+
* so they cannot collide on `index.md` — the friction ADR 0066 removes.
|
|
10
|
+
*
|
|
11
|
+
* `buildIndex` folds the sibling problem in: a **duplicate `id`** across files is
|
|
12
|
+
* a `DuplicateIdError`, so the same gate that catches a stale index catches two
|
|
13
|
+
* PRs racing the same ADR number.
|
|
14
|
+
*
|
|
15
|
+
* Two non-obvious points, both load-bearing and pinned by the unit tests:
|
|
16
|
+
* - `title`/`status` render **verbatim** from front-matter — they may carry
|
|
17
|
+
* inline markdown (a linked `superseded by [0009](…)`), so the front-matter is
|
|
18
|
+
* the curated display text, not just a bare keyword. Make the file right; the
|
|
19
|
+
* table mirrors it.
|
|
20
|
+
* - ordering is numeric-then-suffix so a lettered id like `0034a` sorts between
|
|
21
|
+
* `0034` and `0035` (a plain string sort would too here, but the explicit
|
|
22
|
+
* numeric compare keeps it correct if ids ever stop being zero-padded).
|
|
23
|
+
*/
|
|
24
|
+
export class DuplicateIdError extends Error {
|
|
25
|
+
id;
|
|
26
|
+
files;
|
|
27
|
+
constructor(id, files) {
|
|
28
|
+
super(`duplicate ADR id ${id} in: ${files.join(", ")}`);
|
|
29
|
+
this.name = "DuplicateIdError";
|
|
30
|
+
this.id = id;
|
|
31
|
+
this.files = files;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export class FrontmatterError extends Error {
|
|
35
|
+
file;
|
|
36
|
+
constructor(file, reason) {
|
|
37
|
+
super(`${file}: ${reason}`);
|
|
38
|
+
this.name = "FrontmatterError";
|
|
39
|
+
this.file = file;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const FRONTMATTER_FIELDS = ["id", "title", "status", "date"];
|
|
43
|
+
/**
|
|
44
|
+
* Strip one layer of surrounding quotes from a YAML scalar. A double-quoted value
|
|
45
|
+
* also has its `\"` / `\\` escapes resolved (YAML double-quote semantics), so a
|
|
46
|
+
* title quoted to protect an inner `"by path"` round-trips back to the literal text.
|
|
47
|
+
* A single-quoted value is taken verbatim (we never emit `''` escapes).
|
|
48
|
+
*/
|
|
49
|
+
const unquote = (value) => {
|
|
50
|
+
const v = value.trim();
|
|
51
|
+
if (v.length >= 2) {
|
|
52
|
+
const first = v[0];
|
|
53
|
+
const last = v[v.length - 1];
|
|
54
|
+
if (first === '"' && last === '"') {
|
|
55
|
+
return v.slice(1, -1).replace(/\\(["\\])/g, "$1");
|
|
56
|
+
}
|
|
57
|
+
if (first === "'" && last === "'") {
|
|
58
|
+
return v.slice(1, -1);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return v;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Parse the four index-relevant fields out of a `---`-delimited YAML block.
|
|
65
|
+
* Only the top-level `id`/`title`/`status`/`date` scalars are read; everything
|
|
66
|
+
* else (tags, body) is ignored. Returns the fields present; the caller validates.
|
|
67
|
+
*/
|
|
68
|
+
export const parseFrontmatter = (text) => {
|
|
69
|
+
const match = text.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
70
|
+
if (!match || match[1] === undefined)
|
|
71
|
+
return {};
|
|
72
|
+
const block = match[1];
|
|
73
|
+
const out = {};
|
|
74
|
+
for (const line of block.split(/\r?\n/)) {
|
|
75
|
+
const kv = line.match(/^([A-Za-z][A-Za-z0-9_-]*):\s?(.*)$/);
|
|
76
|
+
if (!kv || kv[1] === undefined || kv[2] === undefined)
|
|
77
|
+
continue;
|
|
78
|
+
const key = kv[1];
|
|
79
|
+
if (FRONTMATTER_FIELDS.includes(key)) {
|
|
80
|
+
out[key] = unquote(kv[2]);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
};
|
|
85
|
+
/** Parse one ADR file into an entry, or throw `FrontmatterError` if a field is missing. */
|
|
86
|
+
export const parseAdrFile = ({ file, text }) => {
|
|
87
|
+
const fm = parseFrontmatter(text);
|
|
88
|
+
for (const field of FRONTMATTER_FIELDS) {
|
|
89
|
+
if (fm[field] === undefined || fm[field] === "") {
|
|
90
|
+
throw new FrontmatterError(file, `missing front-matter field \`${field}\``);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
id: fm.id,
|
|
95
|
+
title: fm.title,
|
|
96
|
+
status: fm.status,
|
|
97
|
+
date: fm.date,
|
|
98
|
+
file,
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
/** Split an id into [numeric, letterSuffix] for ordering, e.g. `0034a` → [34, "a"]. */
|
|
102
|
+
const idSortKey = (id) => {
|
|
103
|
+
const m = id.match(/^(\d+)([A-Za-z]*)$/);
|
|
104
|
+
if (!m || m[1] === undefined)
|
|
105
|
+
return [Number.POSITIVE_INFINITY, id];
|
|
106
|
+
return [Number.parseInt(m[1], 10), m[2] ?? ""];
|
|
107
|
+
};
|
|
108
|
+
/** Entries sorted ascending by id (numeric part, then letter suffix). */
|
|
109
|
+
export const sortEntries = (entries) => [...entries].sort((a, b) => {
|
|
110
|
+
const [an, as] = idSortKey(a.id);
|
|
111
|
+
const [bn, bs] = idSortKey(b.id);
|
|
112
|
+
if (an !== bn)
|
|
113
|
+
return an - bn;
|
|
114
|
+
return as < bs ? -1 : as > bs ? 1 : 0;
|
|
115
|
+
});
|
|
116
|
+
/** First duplicated id across the entries (lowest by sort order), or null if all unique. */
|
|
117
|
+
export const findDuplicateId = (entries) => {
|
|
118
|
+
const byId = new Map();
|
|
119
|
+
for (const e of entries) {
|
|
120
|
+
const files = byId.get(e.id) ?? [];
|
|
121
|
+
files.push(e.file);
|
|
122
|
+
byId.set(e.id, files);
|
|
123
|
+
}
|
|
124
|
+
const dups = [...byId.entries()].filter(([, files]) => files.length > 1);
|
|
125
|
+
if (dups.length === 0)
|
|
126
|
+
return null;
|
|
127
|
+
dups.sort((a, b) => {
|
|
128
|
+
const [an] = idSortKey(a[0]);
|
|
129
|
+
const [bn] = idSortKey(b[0]);
|
|
130
|
+
return an - bn;
|
|
131
|
+
});
|
|
132
|
+
const [id, files] = dups[0];
|
|
133
|
+
return new DuplicateIdError(id, files);
|
|
134
|
+
};
|
|
135
|
+
const renderRow = (e) => `| [${e.id}](${e.file}) | ${e.title} | ${e.status} | ${e.date} |`;
|
|
136
|
+
/**
|
|
137
|
+
* Render the canonical `.decisions/index.md` from sorted entries. The preamble +
|
|
138
|
+
* table header are fixed; rows are one per ADR. The trailing newline matches a
|
|
139
|
+
* POSIX text file (the committed index ends in a single `\n`).
|
|
140
|
+
*/
|
|
141
|
+
export const renderIndex = (entries) => {
|
|
142
|
+
const sorted = sortEntries(entries);
|
|
143
|
+
const lines = [
|
|
144
|
+
"# Decisions",
|
|
145
|
+
"",
|
|
146
|
+
"One row per ADR. Read the file for the why.",
|
|
147
|
+
"",
|
|
148
|
+
"| # | Title | Status | Date |",
|
|
149
|
+
"|---|-------|--------|------|",
|
|
150
|
+
...sorted.map(renderRow),
|
|
151
|
+
];
|
|
152
|
+
return `${lines.join("\n")}\n`;
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* Build the index markdown from the raw `.decisions/` files. Parses every file,
|
|
156
|
+
* fails on a duplicate id (`DuplicateIdError`) or a malformed file
|
|
157
|
+
* (`FrontmatterError`), and otherwise returns the deterministically-ordered table.
|
|
158
|
+
*/
|
|
159
|
+
export const buildIndex = (files) => {
|
|
160
|
+
const entries = files.map(parseAdrFile);
|
|
161
|
+
const dup = findDuplicateId(entries);
|
|
162
|
+
if (dup)
|
|
163
|
+
throw dup;
|
|
164
|
+
return renderIndex(entries);
|
|
165
|
+
};
|
|
166
|
+
//# sourceMappingURL=decisions-index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decisions-index.js","sourceRoot":"","sources":["../src/decisions-index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@kampus/decisions-index` — derive `.decisions/index.md` from the ADR files
|
|
3
|
+
* (ADR 0066). The core (`buildIndex` + its parse/sort/dup helpers) is a pure,
|
|
4
|
+
* IO-free derivation; `bin.ts` wires it to the filesystem as an Effect CLI with
|
|
5
|
+
* `generate` (write the index) and `check` (CI gate: fail on a stale index or a
|
|
6
|
+
* duplicate ADR id) modes.
|
|
7
|
+
*/
|
|
8
|
+
export { type AdrEntry, type AdrFile, buildIndex, DuplicateIdError, FrontmatterError, findDuplicateId, parseAdrFile, parseFrontmatter, renderIndex, sortEntries, } from "./decisions-index.ts";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EACN,KAAK,QAAQ,EACb,KAAK,OAAO,EACZ,UAAU,EACV,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACX,WAAW,GACX,MAAM,sBAAsB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@kampus/decisions-index` — derive `.decisions/index.md` from the ADR files
|
|
3
|
+
* (ADR 0066). The core (`buildIndex` + its parse/sort/dup helpers) is a pure,
|
|
4
|
+
* IO-free derivation; `bin.ts` wires it to the filesystem as an Effect CLI with
|
|
5
|
+
* `generate` (write the index) and `check` (CI gate: fail on a stale index or a
|
|
6
|
+
* duplicate ADR id) modes.
|
|
7
|
+
*/
|
|
8
|
+
export { buildIndex, DuplicateIdError, FrontmatterError, findDuplicateId, parseAdrFile, parseFrontmatter, renderIndex, sortEntries, } from "./decisions-index.js";
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAGN,UAAU,EACV,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACX,WAAW,GACX,MAAM,sBAAsB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kampus/decisions-index",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "decisions-index — generate .decisions/index.md from the ADR files; CI --check fails on a stale index or a duplicate ADR id (ADR 0066)",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/kamp-us/phoenix.git",
|
|
9
|
+
"directory": "packages/decisions-index"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"bin": {
|
|
13
|
+
"decisions-index": "./dist/bin.js"
|
|
14
|
+
},
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"module": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@effect/platform-node": "4.0.0-beta.78",
|
|
32
|
+
"effect": "4.0.0-beta.78"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@effect/tsgo": "^0.5.2",
|
|
36
|
+
"@effect/vitest": "4.0.0-beta.78",
|
|
37
|
+
"@types/node": "^25.6.2",
|
|
38
|
+
"@typescript/native-preview": "^7.0.0-dev.20260509.2",
|
|
39
|
+
"typescript": "^6.0.3",
|
|
40
|
+
"vitest": "^4.1.5"
|
|
41
|
+
},
|
|
42
|
+
"volta": {
|
|
43
|
+
"node": "26.2.0"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsc -p tsconfig.build.json",
|
|
47
|
+
"typecheck": "tsgo -p tsconfig.json",
|
|
48
|
+
"test": "vitest run",
|
|
49
|
+
"generate": "node src/bin.ts generate",
|
|
50
|
+
"check": "node src/bin.ts check"
|
|
51
|
+
}
|
|
52
|
+
}
|