@nielspeter/eess-md 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.
Files changed (91) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +124 -0
  3. package/dist/builders/docs.d.ts +28 -0
  4. package/dist/builders/docs.d.ts.map +1 -0
  5. package/dist/builders/docs.js +44 -0
  6. package/dist/builders/docs.js.map +1 -0
  7. package/dist/builders/links.d.ts +26 -0
  8. package/dist/builders/links.d.ts.map +1 -0
  9. package/dist/builders/links.js +42 -0
  10. package/dist/builders/links.js.map +1 -0
  11. package/dist/builders/pointers.d.ts +26 -0
  12. package/dist/builders/pointers.d.ts.map +1 -0
  13. package/dist/builders/pointers.js +43 -0
  14. package/dist/builders/pointers.js.map +1 -0
  15. package/dist/builders/rows.d.ts +36 -0
  16. package/dist/builders/rows.d.ts.map +1 -0
  17. package/dist/builders/rows.js +42 -0
  18. package/dist/builders/rows.js.map +1 -0
  19. package/dist/builders/task-items.d.ts +21 -0
  20. package/dist/builders/task-items.d.ts.map +1 -0
  21. package/dist/builders/task-items.js +33 -0
  22. package/dist/builders/task-items.js.map +1 -0
  23. package/dist/builders/vocabulary.d.ts +66 -0
  24. package/dist/builders/vocabulary.d.ts.map +1 -0
  25. package/dist/builders/vocabulary.js +118 -0
  26. package/dist/builders/vocabulary.js.map +1 -0
  27. package/dist/conditions/pointer-resolve.d.ts +51 -0
  28. package/dist/conditions/pointer-resolve.d.ts.map +1 -0
  29. package/dist/conditions/pointer-resolve.js +164 -0
  30. package/dist/conditions/pointer-resolve.js.map +1 -0
  31. package/dist/conditions/resolve.d.ts +36 -0
  32. package/dist/conditions/resolve.d.ts.map +1 -0
  33. package/dist/conditions/resolve.js +108 -0
  34. package/dist/conditions/resolve.js.map +1 -0
  35. package/dist/conditions/structure.d.ts +13 -0
  36. package/dist/conditions/structure.d.ts.map +1 -0
  37. package/dist/conditions/structure.js +42 -0
  38. package/dist/conditions/structure.js.map +1 -0
  39. package/dist/conditions/table-rows.d.ts +28 -0
  40. package/dist/conditions/table-rows.d.ts.map +1 -0
  41. package/dist/conditions/table-rows.js +25 -0
  42. package/dist/conditions/table-rows.js.map +1 -0
  43. package/dist/corpus.d.ts +40 -0
  44. package/dist/corpus.d.ts.map +1 -0
  45. package/dist/corpus.js +73 -0
  46. package/dist/corpus.js.map +1 -0
  47. package/dist/index.d.ts +26 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +22 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/model/document.d.ts +78 -0
  52. package/dist/model/document.d.ts.map +1 -0
  53. package/dist/model/document.js +68 -0
  54. package/dist/model/document.js.map +1 -0
  55. package/dist/model/links.d.ts +32 -0
  56. package/dist/model/links.d.ts.map +1 -0
  57. package/dist/model/links.js +53 -0
  58. package/dist/model/links.js.map +1 -0
  59. package/dist/model/pointers.d.ts +29 -0
  60. package/dist/model/pointers.d.ts.map +1 -0
  61. package/dist/model/pointers.js +65 -0
  62. package/dist/model/pointers.js.map +1 -0
  63. package/dist/model/query.d.ts +6 -0
  64. package/dist/model/query.d.ts.map +1 -0
  65. package/dist/model/query.js +15 -0
  66. package/dist/model/query.js.map +1 -0
  67. package/dist/model/rows.d.ts +52 -0
  68. package/dist/model/rows.d.ts.map +1 -0
  69. package/dist/model/rows.js +42 -0
  70. package/dist/model/rows.js.map +1 -0
  71. package/dist/model/task-items.d.ts +28 -0
  72. package/dist/model/task-items.d.ts.map +1 -0
  73. package/dist/model/task-items.js +47 -0
  74. package/dist/model/task-items.js.map +1 -0
  75. package/dist/model/violation.d.ts +23 -0
  76. package/dist/model/violation.d.ts.map +1 -0
  77. package/dist/model/violation.js +22 -0
  78. package/dist/model/violation.js.map +1 -0
  79. package/dist/predicates/document.d.ts +8 -0
  80. package/dist/predicates/document.d.ts.map +1 -0
  81. package/dist/predicates/document.js +22 -0
  82. package/dist/predicates/document.js.map +1 -0
  83. package/dist/rules/adr.d.ts +34 -0
  84. package/dist/rules/adr.d.ts.map +1 -0
  85. package/dist/rules/adr.js +103 -0
  86. package/dist/rules/adr.js.map +1 -0
  87. package/dist/rules/ledger.d.ts +44 -0
  88. package/dist/rules/ledger.d.ts.map +1 -0
  89. package/dist/rules/ledger.js +119 -0
  90. package/dist/rules/ledger.js.map +1 -0
  91. package/package.json +60 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rows.d.ts","sourceRoot":"","sources":["../../src/model/rows.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAGxD;;;;;;;GAOG;AACH,MAAM,WAAW,KAAK;IACpB,gCAAgC;IAChC,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,oFAAoF;IACpF,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IACtC,iEAAiE;IACjE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,wCAAwC;IACxC,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAA;IACxB,qCAAqC;IACrC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CACxB;AAED,gFAAgF;AAChF,MAAM,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;AAEzD,gEAAgE;AAChE,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAClC;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAA;CAC7B;AAmBD;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,OAAO,CAAA;AAE1C;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,UAAU,EACf,IAAI,EAAE,eAAe,EACrB,IAAI,GAAE,YAAoB,GACzB,KAAK,EAAE,CAuBT"}
@@ -0,0 +1,42 @@
1
+ import { tableInSection } from './query.js';
2
+ /** Does a table's header contain a column for every required role? */
3
+ function tableHasColumns(table, columns) {
4
+ return Object.values(columns).every((re) => table.header.some((h) => re.test(h)));
5
+ }
6
+ /** Locate the header index for each role (−1 if absent). */
7
+ function columnIndices(table, columns) {
8
+ const colIndex = new Map();
9
+ for (const [role, re] of Object.entries(columns)) {
10
+ colIndex.set(role, table.header.findIndex((h) => re.test(h)));
11
+ }
12
+ return colIndex;
13
+ }
14
+ /**
15
+ * Extract table rows as first-class `MdRow` elements.
16
+ *
17
+ * Finds the tables in the document that sit under the requested section (if
18
+ * any) and have all the requested columns (`mode` decides all vs. first), then
19
+ * yields one `MdRow` per body row with its real source line.
20
+ */
21
+ export function matchTableRows(doc, opts, mode = 'all') {
22
+ const matching = doc.tables.filter((t) => tableInSection(t, opts.section) && tableHasColumns(t, opts.columns));
23
+ const tables = mode === 'first' ? matching.slice(0, 1) : matching;
24
+ return tables.flatMap((table) => {
25
+ const colIndex = columnIndices(table, opts.columns);
26
+ return table.rows.map((cells, rowIndex) => {
27
+ const get = (role) => {
28
+ const idx = colIndex.get(role);
29
+ return idx !== undefined && idx >= 0 ? (cells[idx] ?? '') : '';
30
+ };
31
+ return {
32
+ cells,
33
+ get,
34
+ rowIndex,
35
+ line: table.rowLines[rowIndex] ?? table.line,
36
+ doc,
37
+ table,
38
+ };
39
+ });
40
+ });
41
+ }
42
+ //# sourceMappingURL=rows.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rows.js","sourceRoot":"","sources":["../../src/model/rows.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAuC3C,sEAAsE;AACtE,SAAS,eAAe,CAAC,KAAc,EAAE,OAAmB;IAC1D,OAAO,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AACnF,CAAC;AAED,4DAA4D;AAC5D,SAAS,aAAa,CAAC,KAAc,EAAE,OAAmB;IACxD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAA;IAC1C,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACjD,QAAQ,CAAC,GAAG,CACV,IAAI,EACJ,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAC1C,CAAA;IACH,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC;AAWD;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAC5B,GAAe,EACf,IAAqB,EACrB,OAAqB,KAAK;IAE1B,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,CAChC,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,eAAe,CAAC,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,CAC3E,CAAA;IACD,MAAM,MAAM,GAAG,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAA;IAEjE,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;QAC9B,MAAM,QAAQ,GAAG,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,CAAA;QACnD,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE;YACxC,MAAM,GAAG,GAAG,CAAC,IAAY,EAAU,EAAE;gBACnC,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;gBAC9B,OAAO,GAAG,KAAK,SAAS,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;YAChE,CAAC,CAAA;YACD,OAAO;gBACL,KAAK;gBACL,GAAG;gBACH,QAAQ;gBACR,IAAI,EAAE,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC,IAAI;gBAC5C,GAAG;gBACH,KAAK;aACN,CAAA;QACH,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC"}
@@ -0,0 +1,28 @@
1
+ import type { Root } from 'mdast';
2
+ /** A GFM task-list item (`- [ ]` / `- [x]`) within a document. */
3
+ export interface MdTaskItemRef {
4
+ /** `false` for an open box `- [ ]`, `true` for a checked box `- [x]`. */
5
+ readonly checked: boolean;
6
+ /** The item's own text (its first paragraph), trimmed; nested sub-items excluded. */
7
+ readonly text: string;
8
+ /** 1-based line of the `- [ ]` in the source document. */
9
+ readonly line: number;
10
+ }
11
+ /**
12
+ * Collect GFM task-list items from a document tree.
13
+ *
14
+ * mdast supplies for free the false-positive guards a line-regex scanner has to
15
+ * emulate by hand:
16
+ * - a `- [ ]` inside fenced code is part of a `code` node, never a `listItem`,
17
+ * so it is naturally excluded;
18
+ * - a task item inside a blockquote is a `listItem` under a `blockquote`
19
+ * ancestor and is skipped here — a quoted box is not a live ledger entry.
20
+ *
21
+ * Only list items with a boolean `checked` are task items; ordinary list items
22
+ * have `checked: null`. The item's `text` is its first paragraph (its own line),
23
+ * so a disposition token on the box line is seen but a nested sub-item's text is
24
+ * not folded in — nested boxes are their own `listItem`s and are yielded
25
+ * separately.
26
+ */
27
+ export declare function collectTaskItems(root: Root): MdTaskItemRef[];
28
+ //# sourceMappingURL=task-items.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-items.d.ts","sourceRoot":"","sources":["../../src/model/task-items.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAS,MAAM,OAAO,CAAA;AAExC,kEAAkE;AAClE,MAAM,WAAW,aAAa;IAC5B,yEAAyE;IACzE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,qFAAqF;IACrF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,0DAA0D;IAC1D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAQD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,GAAG,aAAa,EAAE,CAoB5D"}
@@ -0,0 +1,47 @@
1
+ function textOf(node) {
2
+ if ('value' in node && typeof node.value === 'string')
3
+ return node.value;
4
+ if ('children' in node)
5
+ return node.children.map((c) => textOf(c)).join('');
6
+ return '';
7
+ }
8
+ /**
9
+ * Collect GFM task-list items from a document tree.
10
+ *
11
+ * mdast supplies for free the false-positive guards a line-regex scanner has to
12
+ * emulate by hand:
13
+ * - a `- [ ]` inside fenced code is part of a `code` node, never a `listItem`,
14
+ * so it is naturally excluded;
15
+ * - a task item inside a blockquote is a `listItem` under a `blockquote`
16
+ * ancestor and is skipped here — a quoted box is not a live ledger entry.
17
+ *
18
+ * Only list items with a boolean `checked` are task items; ordinary list items
19
+ * have `checked: null`. The item's `text` is its first paragraph (its own line),
20
+ * so a disposition token on the box line is seen but a nested sub-item's text is
21
+ * not folded in — nested boxes are their own `listItem`s and are yielded
22
+ * separately.
23
+ */
24
+ export function collectTaskItems(root) {
25
+ const out = [];
26
+ const visit = (node, inBlockquote) => {
27
+ if (node.type === 'blockquote') {
28
+ for (const child of node.children)
29
+ visit(child, true);
30
+ return;
31
+ }
32
+ if (node.type === 'listItem' && typeof node.checked === 'boolean') {
33
+ if (!inBlockquote) {
34
+ const firstParagraph = node.children.find((c) => c.type === 'paragraph');
35
+ const text = (firstParagraph ? textOf(firstParagraph) : textOf(node)).trim();
36
+ out.push({ checked: node.checked, text, line: node.position?.start.line ?? 0 });
37
+ }
38
+ }
39
+ if ('children' in node) {
40
+ for (const child of node.children)
41
+ visit(child, inBlockquote);
42
+ }
43
+ };
44
+ visit(root, false);
45
+ return out;
46
+ }
47
+ //# sourceMappingURL=task-items.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-items.js","sourceRoot":"","sources":["../../src/model/task-items.ts"],"names":[],"mappings":"AAYA,SAAS,MAAM,CAAC,IAAW;IACzB,IAAI,OAAO,IAAI,IAAI,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,KAAK,CAAA;IACxE,IAAI,UAAU,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAC3E,OAAO,EAAE,CAAA;AACX,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAU;IACzC,MAAM,GAAG,GAAoB,EAAE,CAAA;IAC/B,MAAM,KAAK,GAAG,CAAC,IAAW,EAAE,YAAqB,EAAQ,EAAE;QACzD,IAAI,IAAI,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;YAC/B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ;gBAAE,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;YACrD,OAAM;QACR,CAAC;QACD,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;YAClE,IAAI,CAAC,YAAY,EAAE,CAAC;gBAClB,MAAM,cAAc,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,CAAA;gBACxE,MAAM,IAAI,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;gBAC5E,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC,CAAA;YACjF,CAAC;QACH,CAAC;QACD,IAAI,UAAU,IAAI,IAAI,EAAE,CAAC;YACvB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ;gBAAE,KAAK,CAAC,KAAK,EAAE,YAAY,CAAC,CAAA;QAC/D,CAAC;IACH,CAAC,CAAA;IACD,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IAClB,OAAO,GAAG,CAAA;AACZ,CAAC"}
@@ -0,0 +1,23 @@
1
+ import { type ArchViolation, type ArchFix } from '@nielspeter/eess';
2
+ export type { ArchViolation };
3
+ /**
4
+ * Build an `ArchViolation` for a markdown-corpus finding. `sourceText` (the
5
+ * document text) yields a code frame around `line` when provided. `fix` attaches
6
+ * a deterministic autofix (plan 0066) when the repair is unique.
7
+ */
8
+ export declare function mdViolation(args: {
9
+ element: string;
10
+ file: string;
11
+ line: number;
12
+ message: string;
13
+ sourceText?: string;
14
+ fix?: ArchFix;
15
+ context: {
16
+ rule: string;
17
+ because?: string;
18
+ suggestion?: string;
19
+ ruleId?: string;
20
+ docs?: string;
21
+ };
22
+ }): ArchViolation;
23
+ //# sourceMappingURL=violation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"violation.d.ts","sourceRoot":"","sources":["../../src/model/violation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,aAAa,EAAE,KAAK,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAKtF,YAAY,EAAE,aAAa,EAAE,CAAA;AAE7B;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE;IAChC,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,OAAO,EAAE;QACP,IAAI,EAAE,MAAM,CAAA;QACZ,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,IAAI,CAAC,EAAE,MAAM,CAAA;KACd,CAAA;CACF,GAAG,aAAa,CAehB"}
@@ -0,0 +1,22 @@
1
+ import { generateCodeFrame } from '@nielspeter/eess';
2
+ /**
3
+ * Build an `ArchViolation` for a markdown-corpus finding. `sourceText` (the
4
+ * document text) yields a code frame around `line` when provided. `fix` attaches
5
+ * a deterministic autofix (plan 0066) when the repair is unique.
6
+ */
7
+ export function mdViolation(args) {
8
+ return {
9
+ rule: args.context.rule,
10
+ ruleId: args.context.ruleId,
11
+ element: args.element,
12
+ file: args.file,
13
+ line: args.line,
14
+ message: args.message,
15
+ because: args.context.because,
16
+ suggestion: args.context.suggestion,
17
+ docs: args.context.docs,
18
+ fix: args.fix,
19
+ codeFrame: args.sourceText !== undefined ? generateCodeFrame(args.sourceText, args.line) : undefined,
20
+ };
21
+ }
22
+ //# sourceMappingURL=violation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"violation.js","sourceRoot":"","sources":["../../src/model/violation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAoC,MAAM,kBAAkB,CAAA;AAOtF;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,IAc3B;IACC,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI;QACvB,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM;QAC3B,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,OAAO;QAC7B,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,UAAU;QACnC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI;QACvB,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,SAAS,EACP,IAAI,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,iBAAiB,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;KAC5F,CAAA;AACH,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { Predicate } from '@nielspeter/eess';
2
+ import type { MdDocument } from '../model/document.js';
3
+ export declare function resideInFolder(glob: string): Predicate<MdDocument>;
4
+ export declare function resideInFile(glob: string): Predicate<MdDocument>;
5
+ export declare function haveNameMatching(re: RegExp): Predicate<MdDocument>;
6
+ /** Predicate form of `haveSection` — "documents that have section X". */
7
+ export declare function haveSectionPredicate(name: string | RegExp): Predicate<MdDocument>;
8
+ //# sourceMappingURL=document.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../../src/predicates/document.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAEjD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAGtD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC,UAAU,CAAC,CAGlE;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC,UAAU,CAAC,CAGhE;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC,UAAU,CAAC,CAKlE;AAED,yEAAyE;AACzE,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC,UAAU,CAAC,CAEjF"}
@@ -0,0 +1,22 @@
1
+ import { basename } from 'node:path';
2
+ import picomatch from 'picomatch';
3
+ import { hasSection } from '../model/query.js';
4
+ export function resideInFolder(glob) {
5
+ const match = picomatch(glob);
6
+ return { description: `reside in folder ${glob}`, test: (d) => match(d.relPath) };
7
+ }
8
+ export function resideInFile(glob) {
9
+ const match = picomatch(glob);
10
+ return { description: `reside in file ${glob}`, test: (d) => match(d.relPath) };
11
+ }
12
+ export function haveNameMatching(re) {
13
+ return {
14
+ description: `have name matching ${String(re)}`,
15
+ test: (d) => re.test(basename(d.relPath)),
16
+ };
17
+ }
18
+ /** Predicate form of `haveSection` — "documents that have section X". */
19
+ export function haveSectionPredicate(name) {
20
+ return { description: `have section "${String(name)}"`, test: (d) => hasSection(d, name) };
21
+ }
22
+ //# sourceMappingURL=document.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document.js","sourceRoot":"","sources":["../../src/predicates/document.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAEpC,OAAO,SAAS,MAAM,WAAW,CAAA;AAEjC,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAE9C,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,CAAA;IAC7B,OAAO,EAAE,WAAW,EAAE,oBAAoB,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAA;AACnF,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,CAAA;IAC7B,OAAO,EAAE,WAAW,EAAE,kBAAkB,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAA;AACjF,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,EAAU;IACzC,OAAO;QACL,WAAW,EAAE,sBAAsB,MAAM,CAAC,EAAE,CAAC,EAAE;QAC/C,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;KAC1C,CAAA;AACH,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,oBAAoB,CAAC,IAAqB;IACxD,OAAO,EAAE,WAAW,EAAE,iBAAiB,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,CAAA;AAC5F,CAAC"}
@@ -0,0 +1,34 @@
1
+ import { type ArchViolation, type PresetBaseOptions } from '@nielspeter/eess';
2
+ import type { Corpus } from '../corpus.js';
3
+ /**
4
+ * Options for `adrEnforcement` — an OPINIONATED preset implementing the EESS
5
+ * enforcement-tier model (a `## Enforcement` table with tier + mechanism +
6
+ * status). Teams whose ADRs differ compose their own gate from the generic
7
+ * primitives (`haveSection`, `haveTable`, `haveTableRowsSatisfying`, `resolve`).
8
+ */
9
+ export interface AdrEnforcementOptions extends PresetBaseOptions {
10
+ /** Glob selecting ADR files. Default `docs/adr/**`. */
11
+ readonly dir?: string;
12
+ /** Heading of the enforcement section. Default `/^enforcement$/i`. */
13
+ readonly section?: string | RegExp;
14
+ /** Header patterns locating the tier/mechanism/status columns. */
15
+ readonly columns?: {
16
+ tier: RegExp;
17
+ mechanism: RegExp;
18
+ status: RegExp;
19
+ };
20
+ /** Valid tier numbers. Default `[1,2,3,4,5]`. */
21
+ readonly tiers?: readonly number[];
22
+ /** Verify cited file paths exist and cited `it('…')` titles resolve. Default `true`. */
23
+ readonly verifyCitations?: boolean;
24
+ }
25
+ /**
26
+ * OPINIONATED preset: gate a repo's ADRs on the EESS enforcement-tier model.
27
+ * Gates on *declaration* — an ADR fails for a missing `## Enforcement` table, a
28
+ * clause with no valid tier, or (when `verifyCitations`) a citation that doesn't
29
+ * resolve; a soft-tier clause declared as such passes.
30
+ *
31
+ * Emits per-rule ids so `overrides` can downgrade/disable individual checks.
32
+ */
33
+ export declare function adrEnforcement(corpus: Corpus, options?: AdrEnforcementOptions): ArchViolation[];
34
+ //# sourceMappingURL=adr.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adr.d.ts","sourceRoot":"","sources":["../../src/rules/adr.ts"],"names":[],"mappings":"AAEA,OAAO,EAKL,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACvB,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAW1C;;;;;GAKG;AACH,MAAM,WAAW,qBAAsB,SAAQ,iBAAiB;IAC9D,uDAAuD;IACvD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;IACrB,sEAAsE;IACtE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAClC,kEAAkE;IAClE,QAAQ,CAAC,OAAO,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;IACtE,iDAAiD;IACjD,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAClC,wFAAwF;IACxF,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAA;CACnC;AAuDD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,qBAA0B,GAClC,aAAa,EAAE,CA4CjB"}
@@ -0,0 +1,103 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { dispatchRule, finishPreset, validateOverrides, not, } from '@nielspeter/eess';
4
+ import { docs } from '../builders/docs.js';
5
+ import { haveNameMatching } from '../predicates/document.js';
6
+ import { haveTableRowsSatisfying } from '../conditions/table-rows.js';
7
+ // The ADR directory's index/schema doc (README.md, index.md) is not an ADR —
8
+ // exempt it so it isn't required to carry an `## Enforcement` table. the external repo's
9
+ // hand-built adr-enforcement.ts scopes to numbered files for the same reason;
10
+ // excluding by name (not "numbered-only") keeps MADR-style named ADRs in scope.
11
+ const notIndexDoc = not(haveNameMatching(/^(README|index)\.md$/i));
12
+ const RULE_IDS = ['adr/enforcement-declared', 'adr/valid-tiers', 'adr/citations-resolve'];
13
+ const PATH_RE = /`([A-Za-z0-9_./-]+\.[A-Za-z0-9]+)`/g;
14
+ const IT_CITE_RE = /it(?:\.\w+)?\(\s*['"]([^'"]+)['"]/g;
15
+ /** Does `testFile` define a test titled `title`? (Text-level; 0059 upgrades to AST.) */
16
+ function testDefinesIt(testAbs, title) {
17
+ if (!existsSync(testAbs))
18
+ return false;
19
+ const content = readFileSync(testAbs, 'utf8');
20
+ const esc = title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
21
+ return new RegExp(`it(?:\\.\\w+)?\\(\\s*['"\`]${esc}`).test(content);
22
+ }
23
+ function validateTier(ctx, tiers) {
24
+ const clause = ctx.cells[0] ?? '';
25
+ const match = ctx.get('tier').match(/[1-5]/);
26
+ if (!match || !tiers.has(Number(match[0]))) {
27
+ return [`clause "${clause}" has no valid tier (1–5): got "${ctx.get('tier')}"`];
28
+ }
29
+ return [];
30
+ }
31
+ function validateCitations(ctx, corpus) {
32
+ const clause = ctx.cells[0] ?? '';
33
+ const mech = ctx.get('mechanism');
34
+ const problems = [];
35
+ const citedPaths = [];
36
+ for (const m of mech.matchAll(PATH_RE)) {
37
+ const p = m[1];
38
+ if (p === undefined)
39
+ continue;
40
+ citedPaths.push(p);
41
+ if (!corpus.fileIndex.has(p)) {
42
+ problems.push(`clause "${clause}" cites missing file \`${p}\``);
43
+ }
44
+ }
45
+ const testFiles = citedPaths.filter((p) => p.endsWith('.ts'));
46
+ for (const m of mech.matchAll(IT_CITE_RE)) {
47
+ const title = m[1];
48
+ if (title === undefined)
49
+ continue;
50
+ const found = testFiles.some((p) => testDefinesIt(join(corpus.root, p), title));
51
+ if (!found) {
52
+ problems.push(`clause "${clause}" cites it('${title}') not found in ${testFiles.length ? testFiles.join(', ') : 'any cited test file'}`);
53
+ }
54
+ }
55
+ return problems;
56
+ }
57
+ /**
58
+ * OPINIONATED preset: gate a repo's ADRs on the EESS enforcement-tier model.
59
+ * Gates on *declaration* — an ADR fails for a missing `## Enforcement` table, a
60
+ * clause with no valid tier, or (when `verifyCitations`) a citation that doesn't
61
+ * resolve; a soft-tier clause declared as such passes.
62
+ *
63
+ * Emits per-rule ids so `overrides` can downgrade/disable individual checks.
64
+ */
65
+ export function adrEnforcement(corpus, options = {}) {
66
+ const dir = options.dir ?? 'docs/adr/**';
67
+ const section = options.section ?? /^enforcement$/i;
68
+ const columns = options.columns ?? {
69
+ tier: /tier/i,
70
+ mechanism: /mechanism/i,
71
+ status: /status/i,
72
+ };
73
+ const tiers = new Set(options.tiers ?? [1, 2, 3, 4, 5]);
74
+ const verify = options.verifyCitations ?? true;
75
+ validateOverrides(options.overrides, [...RULE_IDS]);
76
+ const declared = docs(corpus)
77
+ .that()
78
+ .resideInFolder(dir)
79
+ .satisfy(notIndexDoc)
80
+ .should()
81
+ .haveTable({ section, columns: [columns.tier, columns.mechanism, columns.status] });
82
+ const validTiers = docs(corpus)
83
+ .that()
84
+ .resideInFolder(dir)
85
+ .satisfy(notIndexDoc)
86
+ .should()
87
+ .satisfy(haveTableRowsSatisfying({ section, columns, row: (ctx) => validateTier(ctx, tiers) }));
88
+ const violations = [
89
+ ...dispatchRule(declared, 'adr/enforcement-declared', 'error', options.overrides),
90
+ ...dispatchRule(validTiers, 'adr/valid-tiers', 'error', options.overrides),
91
+ ];
92
+ if (verify) {
93
+ const citations = docs(corpus)
94
+ .that()
95
+ .resideInFolder(dir)
96
+ .satisfy(notIndexDoc)
97
+ .should()
98
+ .satisfy(haveTableRowsSatisfying({ section, columns, row: (ctx) => validateCitations(ctx, corpus) }));
99
+ violations.push(...dispatchRule(citations, 'adr/citations-resolve', 'error', options.overrides));
100
+ }
101
+ return finishPreset(violations, options);
102
+ }
103
+ //# sourceMappingURL=adr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adr.js","sourceRoot":"","sources":["../../src/rules/adr.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ import { type PresetReportOptions } from '@nielspeter/eess';
2
+ import type { Corpus } from '../corpus.js';
3
+ import type { ArchViolation } from '../model/violation.js';
4
+ /**
5
+ * `honestyAtClose` — the ledger-reconciliation gate for an engineering corpus.
6
+ *
7
+ * The working method's first firm principle: when an item is finished, every
8
+ * part of it ends as *done*, *done-otherwise*, *deferred to a named home*, or
9
+ * *dropped on purpose* — and the deferral count is said out loud. "Silence is
10
+ * not 'nothing deferred.'" A *done*-item that still carries an undisposed open
11
+ * box has silently lost scope.
12
+ *
13
+ * This preset enforces only the mechanically-knowable slice — the *silent* case.
14
+ * Whether a `deferred→<home>` is *truthful* is Tier-4 judgment for the reviewer.
15
+ * So it is a necessary-not-sufficient floor (form-gated / content-judgment).
16
+ *
17
+ * Three findings: `silent-open-box`, `deferred-none-lie`, `state-folder-mismatch`
18
+ * (see plan 0068 Phase 3). The GFM task boxes are read from mdast (via
19
+ * `collectTaskItems`), so a `- [ ]` in fenced code or a blockquote is excluded
20
+ * for free — no hand-rolled stripping. `deferred: none` summaries and the
21
+ * `State:` header token are unambiguous prose lines, scanned from the source.
22
+ */
23
+ export interface HonestyAtCloseOptions extends PresetReportOptions {
24
+ /**
25
+ * Path segments that mark a document as *done* (in addition to a terminal
26
+ * `State:` token in its header). Default: the common terminal folders.
27
+ */
28
+ readonly doneFolders?: readonly string[];
29
+ /** Board/index basenames that are not items and are never scanned. */
30
+ readonly boardFiles?: readonly string[];
31
+ /**
32
+ * When `true`, items close *in place* rather than moving to a done-folder, so
33
+ * the "a `State: Done` item must sit in a done-folder" half of the placement
34
+ * check is disabled. Default `false`.
35
+ */
36
+ readonly closeInPlace?: boolean;
37
+ }
38
+ /**
39
+ * Run the honesty-at-close gate over a corpus. Throws `ArchRuleError` on any
40
+ * finding. Placement is checked on every item; ledger reconciliation only on
41
+ * done-items (the inverse of the frozen-folder exemption).
42
+ */
43
+ export declare function honestyAtClose(corpus: Corpus, options?: HonestyAtCloseOptions): ArchViolation[];
44
+ //# sourceMappingURL=ledger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ledger.d.ts","sourceRoot":"","sources":["../../src/rules/ledger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AACzE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAE1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAG1D;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,qBAAsB,SAAQ,mBAAmB;IAChE;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACxC,sEAAsE;IACtE,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACvC;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAA;CAChC;AA6ID;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,qBAA0B,GAClC,aAAa,EAAE,CAkBjB"}
@@ -0,0 +1,119 @@
1
+ import { finishPreset } from '@nielspeter/eess';
2
+ import { collectTaskItems } from '../model/task-items.js';
3
+ const DEFAULT_DONE_FOLDERS = ['/completed/', '/fixed/', '/wont-do/', '/delivered/', '/archived/'];
4
+ const DEFAULT_BOARD_FILES = ['ROADMAP.md', 'BUGS.md', 'REFINEMENT.md', 'SUPPORT.md', 'README.md'];
5
+ // A terminal `State:` token in the header — `**State:** Done` / `Won't-do`.
6
+ const DONE_STATE_RE = /^\s*(?:[-*]\s+)?(?:\*\*)?State:?(?:\*\*)?\s*(Done|Won't-do)\b/im;
7
+ // The full neutral State enum on one line (for state↔folder coherence).
8
+ const STATE_TOKEN_LINE_RE = /^\s*(?:[-*]\s+)?(?:\*\*)?State:?(?:\*\*)?\s*(Draft|Ready|Open|Done|Won't-do)\b/i;
9
+ const TERMINAL_STATES = new Set(['Done', "Won't-do"]);
10
+ // Any one of these on a box's line marks it *disposed* (not silent). Tokens are
11
+ // the canonical hyphenated enum, word-bounded — so prose like "…the connection
12
+ // dropped in prod" does not falsely exempt a silent box. `deferred` must name a
13
+ // home other than "none".
14
+ const DISPOSITION_RE = /(\bdone-otherwise\b|\bdropped-on-purpose\b|\bvalidation-owed\b|deferred\s*(?:→|->|:|\bto\b)\s*(?!none\b)\S)/i;
15
+ const DEFERRED_DISPOSITION_RE = /deferred\s*(?:→|->|:|\bto\b)\s*(?!none\b)\S/i;
16
+ // A summary line asserting the deferral count out loud, e.g. `Deferred: none`.
17
+ const DEFERRED_SUMMARY_RE = /^[^\S\n]*(?:[-*+][^\S\n]+)?(?:\*\*)?deferred:?(?:\*\*)?[^\S\n]*(.+?)[^\S\n]*$/im;
18
+ const FENCE_RE = /(```|~~~)[\s\S]*?\1/g;
19
+ // Blank out fenced code in place (preserve line numbers) so an illustrative
20
+ // `**State:** Done` or `Deferred: none` in an example never misclassifies.
21
+ function stripFencedCode(s) {
22
+ return s.replace(FENCE_RE, (m) => '\n'.repeat((m.match(/\n/g) ?? []).length));
23
+ }
24
+ function headerRegion(text) {
25
+ return stripFencedCode(text).split(/^##\s/m)[0] ?? '';
26
+ }
27
+ function isDoneItem(doc, doneFolders) {
28
+ if (doneFolders.some((seg) => `/${doc.relPath}`.includes(seg)))
29
+ return true;
30
+ return DONE_STATE_RE.test(headerRegion(doc.text));
31
+ }
32
+ const v = (rule, doc, line, message, because) => ({
33
+ rule,
34
+ element: doc.relPath,
35
+ file: doc.file,
36
+ line,
37
+ message,
38
+ because,
39
+ });
40
+ /** State↔folder coherence — the two mechanically-knowable placement defects. */
41
+ function placementViolation(doc, inDoneFolder, closeInPlace) {
42
+ const lines = stripFencedCode(doc.text).split('\n');
43
+ let stateLine = 0;
44
+ let state = '';
45
+ for (let i = 0; i < lines.length; i++) {
46
+ if (/^##\s/.test(lines[i] ?? ''))
47
+ break; // header region only
48
+ const m = STATE_TOKEN_LINE_RE.exec(lines[i] ?? '');
49
+ if (m) {
50
+ stateLine = i + 1;
51
+ state = m[1] ?? '';
52
+ break;
53
+ }
54
+ }
55
+ if (!stateLine)
56
+ return null; // no neutral State token → nothing to check
57
+ const terminal = TERMINAL_STATES.has(state);
58
+ if (inDoneFolder && !terminal) {
59
+ return v('ledger/state-folder-mismatch', doc, stateLine, `State: ${state} but filed in a done-folder — move it back to the active lane or close it out.`, 'a done-folder item marked as not-done is a silent placement corruption');
60
+ }
61
+ if (!inDoneFolder && terminal && !closeInPlace) {
62
+ return v('ledger/state-folder-mismatch', doc, stateLine, `State: ${state} but not in a done-folder — the move-to-done was never made (orphaned close).`, 'a done item left in an active lane is an orphaned post-merge move');
63
+ }
64
+ return null;
65
+ }
66
+ /** Ledger reconciliation of one done-item: unchecked boxes + the `none`-summary lie. */
67
+ function ledgerViolations(doc) {
68
+ const out = [];
69
+ let anyDeferredDisposedBox = false;
70
+ for (const box of collectTaskItems(doc.root)) {
71
+ if (box.checked)
72
+ continue; // only open boxes are live ledger entries
73
+ if (DEFERRED_DISPOSITION_RE.test(box.text))
74
+ anyDeferredDisposedBox = true;
75
+ if (!DISPOSITION_RE.test(box.text)) {
76
+ out.push(v('ledger/silent-open-box', doc, box.line, 'unchecked box with no disposition (done-otherwise / deferred→<home> / dropped-on-purpose / validation-owed)', "silence is not 'nothing deferred' — a done-item with a silently-open box has lost scope"));
77
+ }
78
+ }
79
+ // A `none` deferral-summary while a box carries a defer disposition — the
80
+ // summary contradicts the boxes. (Absence of a summary is deliberately NOT gated.)
81
+ if (anyDeferredDisposedBox) {
82
+ const lines = stripFencedCode(doc.text).split('\n');
83
+ for (let i = 0; i < lines.length; i++) {
84
+ const raw = lines[i] ?? '';
85
+ if (/^\s*>/.test(raw))
86
+ continue;
87
+ const sm = raw.match(DEFERRED_SUMMARY_RE);
88
+ if (sm && /^none\b/i.test((sm[1] ?? '').trim())) {
89
+ out.push(v('ledger/deferred-none-lie', doc, i + 1, "'deferred: none' contradicts a box disposed as deferred→<home>", 'the out-loud summary must reconcile with the box states, not override them'));
90
+ break;
91
+ }
92
+ }
93
+ }
94
+ return out;
95
+ }
96
+ /**
97
+ * Run the honesty-at-close gate over a corpus. Throws `ArchRuleError` on any
98
+ * finding. Placement is checked on every item; ledger reconciliation only on
99
+ * done-items (the inverse of the frozen-folder exemption).
100
+ */
101
+ export function honestyAtClose(corpus, options = {}) {
102
+ const doneFolders = options.doneFolders ?? DEFAULT_DONE_FOLDERS;
103
+ const boardFiles = new Set(options.boardFiles ?? DEFAULT_BOARD_FILES);
104
+ const closeInPlace = options.closeInPlace ?? false;
105
+ const violations = [];
106
+ for (const doc of corpus.documents()) {
107
+ const base = doc.relPath.split('/').pop() ?? doc.relPath;
108
+ if (boardFiles.has(base))
109
+ continue;
110
+ const inDoneFolder = doneFolders.some((seg) => `/${doc.relPath}`.includes(seg));
111
+ const placement = placementViolation(doc, inDoneFolder, closeInPlace);
112
+ if (placement)
113
+ violations.push(placement);
114
+ if (isDoneItem(doc, doneFolders))
115
+ violations.push(...ledgerViolations(doc));
116
+ }
117
+ return finishPreset(violations, options);
118
+ }
119
+ //# sourceMappingURL=ledger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ledger.js","sourceRoot":"","sources":["../../src/rules/ledger.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@nielspeter/eess-md",
3
+ "version": "0.1.0",
4
+ "description": "Architecture testing for a Markdown corpus — the Markdown dialect of the eess family. Links resolve, code pointers ground, ADRs declare enforcement.",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "import": "./dist/index.js"
10
+ },
11
+ "./rules/adr": {
12
+ "types": "./dist/rules/adr.d.ts",
13
+ "import": "./dist/rules/adr.js"
14
+ },
15
+ "./rules/ledger": {
16
+ "types": "./dist/rules/ledger.d.ts",
17
+ "import": "./dist/rules/ledger.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "README.md",
23
+ "LICENSE"
24
+ ],
25
+ "engines": {
26
+ "node": ">=24.0.0"
27
+ },
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/nielspeter/eess.git",
31
+ "directory": "packages/md"
32
+ },
33
+ "author": "Niels Peter",
34
+ "license": "MIT",
35
+ "scripts": {
36
+ "prepublishOnly": "npm run build",
37
+ "build": "tsc -p tsconfig.build.json",
38
+ "test": "vitest run",
39
+ "test:watch": "vitest",
40
+ "typecheck": "tsc --noEmit"
41
+ },
42
+ "keywords": [
43
+ "markdown",
44
+ "architecture",
45
+ "testing",
46
+ "adr",
47
+ "documentation",
48
+ "eess"
49
+ ],
50
+ "dependencies": {
51
+ "@nielspeter/eess": "^0.1.0",
52
+ "mdast-util-from-markdown": "^2.0.3",
53
+ "mdast-util-gfm": "^3.1.0",
54
+ "micromark-extension-gfm": "^3.0.0",
55
+ "picomatch": "^4.0.4"
56
+ },
57
+ "devDependencies": {
58
+ "@types/mdast": "^4.0.4"
59
+ }
60
+ }