@nielspeter/eess 0.2.1 → 0.4.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 (157) hide show
  1. package/README.md +3 -3
  2. package/dist/assertion-advice.d.ts +26 -0
  3. package/dist/assertion-advice.d.ts.map +1 -0
  4. package/dist/assertion-advice.js +29 -0
  5. package/dist/assertion-advice.js.map +1 -0
  6. package/dist/baseline.d.ts +69 -4
  7. package/dist/baseline.d.ts.map +1 -1
  8. package/dist/baseline.js +119 -13
  9. package/dist/baseline.js.map +1 -1
  10. package/dist/cache-registry.d.ts +14 -0
  11. package/dist/cache-registry.d.ts.map +1 -0
  12. package/dist/cache-registry.js +37 -0
  13. package/dist/cache-registry.js.map +1 -0
  14. package/dist/cardinality.d.ts +35 -0
  15. package/dist/cardinality.d.ts.map +1 -0
  16. package/dist/cardinality.js +65 -0
  17. package/dist/cardinality.js.map +1 -0
  18. package/dist/check-options.d.ts +14 -0
  19. package/dist/check-options.d.ts.map +1 -1
  20. package/dist/comment-suppression.d.ts +60 -0
  21. package/dist/comment-suppression.d.ts.map +1 -0
  22. package/dist/comment-suppression.js +89 -0
  23. package/dist/comment-suppression.js.map +1 -0
  24. package/dist/condition.d.ts +6 -0
  25. package/dist/condition.d.ts.map +1 -1
  26. package/dist/correspondence-core.d.ts +35 -0
  27. package/dist/correspondence-core.d.ts.map +1 -0
  28. package/dist/correspondence-core.js +38 -0
  29. package/dist/correspondence-core.js.map +1 -0
  30. package/dist/correspondence.d.ts +27 -5
  31. package/dist/correspondence.d.ts.map +1 -1
  32. package/dist/correspondence.js +39 -5
  33. package/dist/correspondence.js.map +1 -1
  34. package/dist/dedupe-config-findings.d.ts +56 -0
  35. package/dist/dedupe-config-findings.d.ts.map +1 -0
  36. package/dist/dedupe-config-findings.js +74 -0
  37. package/dist/dedupe-config-findings.js.map +1 -0
  38. package/dist/define.d.ts +44 -2
  39. package/dist/define.d.ts.map +1 -1
  40. package/dist/define.js +45 -4
  41. package/dist/define.js.map +1 -1
  42. package/dist/diff-aware.d.ts +8 -0
  43. package/dist/diff-aware.d.ts.map +1 -1
  44. package/dist/diff-aware.js +11 -2
  45. package/dist/diff-aware.js.map +1 -1
  46. package/dist/diff-disclosure.d.ts +72 -0
  47. package/dist/diff-disclosure.d.ts.map +1 -0
  48. package/dist/diff-disclosure.js +110 -0
  49. package/dist/diff-disclosure.js.map +1 -0
  50. package/dist/disk-set.d.ts +27 -0
  51. package/dist/disk-set.d.ts.map +1 -0
  52. package/dist/disk-set.js +12 -0
  53. package/dist/disk-set.js.map +1 -0
  54. package/dist/edge-coverage.d.ts +96 -0
  55. package/dist/edge-coverage.d.ts.map +1 -0
  56. package/dist/edge-coverage.js +108 -0
  57. package/dist/edge-coverage.js.map +1 -0
  58. package/dist/errors.d.ts +25 -0
  59. package/dist/errors.d.ts.map +1 -1
  60. package/dist/errors.js +32 -0
  61. package/dist/errors.js.map +1 -1
  62. package/dist/execute-rule.d.ts +11 -5
  63. package/dist/execute-rule.d.ts.map +1 -1
  64. package/dist/execute-rule.js +87 -19
  65. package/dist/execute-rule.js.map +1 -1
  66. package/dist/format-github.d.ts.map +1 -1
  67. package/dist/format-github.js +37 -5
  68. package/dist/format-github.js.map +1 -1
  69. package/dist/format-json.d.ts +83 -1
  70. package/dist/format-json.d.ts.map +1 -1
  71. package/dist/format-json.js +70 -9
  72. package/dist/format-json.js.map +1 -1
  73. package/dist/format.d.ts +4 -2
  74. package/dist/format.d.ts.map +1 -1
  75. package/dist/format.js +40 -8
  76. package/dist/format.js.map +1 -1
  77. package/dist/glob-site.d.ts +232 -0
  78. package/dist/glob-site.d.ts.map +1 -0
  79. package/dist/glob-site.js +156 -0
  80. package/dist/glob-site.js.map +1 -0
  81. package/dist/identity-root.d.ts +55 -0
  82. package/dist/identity-root.d.ts.map +1 -0
  83. package/dist/identity-root.js +170 -0
  84. package/dist/identity-root.js.map +1 -0
  85. package/dist/index.d.ts +41 -3
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +33 -3
  88. package/dist/index.js.map +1 -1
  89. package/dist/owns-empty-discovery.d.ts +5 -0
  90. package/dist/owns-empty-discovery.d.ts.map +1 -0
  91. package/dist/owns-empty-discovery.js +77 -0
  92. package/dist/owns-empty-discovery.js.map +1 -0
  93. package/dist/path-universe.d.ts +44 -0
  94. package/dist/path-universe.d.ts.map +1 -0
  95. package/dist/path-universe.js +21 -0
  96. package/dist/path-universe.js.map +1 -0
  97. package/dist/predicate.d.ts +33 -0
  98. package/dist/predicate.d.ts.map +1 -1
  99. package/dist/preset-dispatch.d.ts +19 -0
  100. package/dist/preset-dispatch.d.ts.map +1 -1
  101. package/dist/preset-dispatch.js +43 -3
  102. package/dist/preset-dispatch.js.map +1 -1
  103. package/dist/project-relative.d.ts +25 -0
  104. package/dist/project-relative.d.ts.map +1 -0
  105. package/dist/project-relative.js +54 -0
  106. package/dist/project-relative.js.map +1 -0
  107. package/dist/report.d.ts +11 -5
  108. package/dist/report.d.ts.map +1 -1
  109. package/dist/report.js +17 -0
  110. package/dist/report.js.map +1 -1
  111. package/dist/rule-builder-like.d.ts +11 -0
  112. package/dist/rule-builder-like.d.ts.map +1 -0
  113. package/dist/rule-builder-like.js +2 -0
  114. package/dist/rule-builder-like.js.map +1 -0
  115. package/dist/rule-builder.d.ts +109 -84
  116. package/dist/rule-builder.d.ts.map +1 -1
  117. package/dist/rule-builder.js +202 -158
  118. package/dist/rule-builder.js.map +1 -1
  119. package/dist/rule-description.d.ts +33 -1
  120. package/dist/rule-description.d.ts.map +1 -1
  121. package/dist/rule-description.js +17 -1
  122. package/dist/rule-description.js.map +1 -1
  123. package/dist/rule-metadata.d.ts +20 -3
  124. package/dist/rule-metadata.d.ts.map +1 -1
  125. package/dist/selection-memo.d.ts +47 -0
  126. package/dist/selection-memo.d.ts.map +1 -0
  127. package/dist/selection-memo.js +68 -0
  128. package/dist/selection-memo.js.map +1 -0
  129. package/dist/shallow-clone.d.ts +20 -0
  130. package/dist/shallow-clone.d.ts.map +1 -0
  131. package/dist/shallow-clone.js +27 -0
  132. package/dist/shallow-clone.js.map +1 -0
  133. package/dist/stderr.d.ts +16 -0
  134. package/dist/stderr.d.ts.map +1 -0
  135. package/dist/stderr.js +73 -0
  136. package/dist/stderr.js.map +1 -0
  137. package/dist/terminal-builder.d.ts +209 -9
  138. package/dist/terminal-builder.d.ts.map +1 -1
  139. package/dist/terminal-builder.js +264 -20
  140. package/dist/terminal-builder.js.map +1 -1
  141. package/dist/type-guards.d.ts +38 -0
  142. package/dist/type-guards.d.ts.map +1 -0
  143. package/dist/type-guards.js +42 -0
  144. package/dist/type-guards.js.map +1 -0
  145. package/dist/unsuppressable.d.ts +54 -0
  146. package/dist/unsuppressable.d.ts.map +1 -0
  147. package/dist/unsuppressable.js +76 -0
  148. package/dist/unsuppressable.js.map +1 -0
  149. package/dist/vacuity-findings.d.ts +55 -0
  150. package/dist/vacuity-findings.d.ts.map +1 -0
  151. package/dist/vacuity-findings.js +124 -0
  152. package/dist/vacuity-findings.js.map +1 -0
  153. package/dist/violation.d.ts +152 -4
  154. package/dist/violation.d.ts.map +1 -1
  155. package/dist/violation.js +262 -1
  156. package/dist/violation.js.map +1 -1
  157. package/package.json +1 -1
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Memoize a builder's materialized selection — plan 0096.
3
+ *
4
+ * ## Why this exists
5
+ *
6
+ * Plan 0096 makes every family expose the set its conditions receive, so that
7
+ * `diagnose()`'s preview and (in plan 0098) the floor read the **same
8
+ * computation** the gate reads rather than a parallel one. Two readers of one
9
+ * method means the walk would run twice per rule — and `doctor` calls
10
+ * `diagnose()` once per rule file, so on a 600-file project a rule file with ten
11
+ * smell rules paid seconds to compute ten integers.
12
+ *
13
+ * ## Why a WeakMap and not an instance field
14
+ *
15
+ * Because a field would be **wrong**, not merely different. These builders are
16
+ * copy-on-write: every chain method returns `this.copy()`, which is
17
+ * `shallowClone` — `Object.assign(clone, source)` over own enumerable
18
+ * properties. A memo stored on the instance is therefore copied onto a builder
19
+ * that has just been given a **different predicate**, and the clone answers with
20
+ * its parent's selection. That is a stale-evidence bug of exactly the kind this
21
+ * plan exists to prevent, and it would have been hard to see: the number is
22
+ * plausible, and only a test that narrows *after* materializing would catch it.
23
+ *
24
+ * A `WeakMap` keyed on the builder has no such hazard by construction. A clone
25
+ * is a different object, so a different key, so no entry. Nothing to clear, no
26
+ * `copy()` override to remember in five families, and the entry is collected
27
+ * with the builder.
28
+ *
29
+ * ## Why a factory rather than one shared map
30
+ *
31
+ * So the element type survives without an assertion. One `WeakMap<object,
32
+ * unknown[]>` shared by every family would need a cast on the way out, which
33
+ * [ADR-005](../../../adr/005-no-any-no-type-assertions.md) forbids — and the
34
+ * honest alternatives (a `filter` with an always-true type predicate) are worse:
35
+ * they compile, allocate, and lie.
36
+ *
37
+ * @example
38
+ * const selectionOf = selectionMemo<ArchFunction>()
39
+ *
40
+ * private selected(): ArchFunction[] {
41
+ * return selectionOf(this, () =>
42
+ * this.getElements().filter((e) => this._predicates.every((p) => p.test(e))),
43
+ * )
44
+ * }
45
+ */
46
+ export declare function selectionMemo<T>(): (owner: object, compute: () => T[]) => T[];
47
+ //# sourceMappingURL=selection-memo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selection-memo.d.ts","sourceRoot":"","sources":["../src/selection-memo.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,wBAAgB,aAAa,CAAC,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,KAAK,CAAC,EAAE,CAmB7E"}
@@ -0,0 +1,68 @@
1
+ import { registerCacheReset } from './cache-registry.js';
2
+ /**
3
+ * Memoize a builder's materialized selection — plan 0096.
4
+ *
5
+ * ## Why this exists
6
+ *
7
+ * Plan 0096 makes every family expose the set its conditions receive, so that
8
+ * `diagnose()`'s preview and (in plan 0098) the floor read the **same
9
+ * computation** the gate reads rather than a parallel one. Two readers of one
10
+ * method means the walk would run twice per rule — and `doctor` calls
11
+ * `diagnose()` once per rule file, so on a 600-file project a rule file with ten
12
+ * smell rules paid seconds to compute ten integers.
13
+ *
14
+ * ## Why a WeakMap and not an instance field
15
+ *
16
+ * Because a field would be **wrong**, not merely different. These builders are
17
+ * copy-on-write: every chain method returns `this.copy()`, which is
18
+ * `shallowClone` — `Object.assign(clone, source)` over own enumerable
19
+ * properties. A memo stored on the instance is therefore copied onto a builder
20
+ * that has just been given a **different predicate**, and the clone answers with
21
+ * its parent's selection. That is a stale-evidence bug of exactly the kind this
22
+ * plan exists to prevent, and it would have been hard to see: the number is
23
+ * plausible, and only a test that narrows *after* materializing would catch it.
24
+ *
25
+ * A `WeakMap` keyed on the builder has no such hazard by construction. A clone
26
+ * is a different object, so a different key, so no entry. Nothing to clear, no
27
+ * `copy()` override to remember in five families, and the entry is collected
28
+ * with the builder.
29
+ *
30
+ * ## Why a factory rather than one shared map
31
+ *
32
+ * So the element type survives without an assertion. One `WeakMap<object,
33
+ * unknown[]>` shared by every family would need a cast on the way out, which
34
+ * [ADR-005](../../../adr/005-no-any-no-type-assertions.md) forbids — and the
35
+ * honest alternatives (a `filter` with an always-true type predicate) are worse:
36
+ * they compile, allocate, and lie.
37
+ *
38
+ * @example
39
+ * const selectionOf = selectionMemo<ArchFunction>()
40
+ *
41
+ * private selected(): ArchFunction[] {
42
+ * return selectionOf(this, () =>
43
+ * this.getElements().filter((e) => this._predicates.every((p) => p.test(e))),
44
+ * )
45
+ * }
46
+ */
47
+ export function selectionMemo() {
48
+ let cache = new WeakMap();
49
+ // Same staleness profile as `element-cache.ts`, so the same escape hatch — a
50
+ // consumer holding a builder across a mutation of the underlying ts-morph
51
+ // project gets the pre-mutation selection back, and identity has not changed
52
+ // so nothing else can notice. That is worse here than in the element cache:
53
+ // BOTH readers go through this memo, so a stale entry is a stale VERDICT and
54
+ // a stale count that agree with each other. Reasoning about the clone hazard
55
+ // and stopping one short of this one would read as "considered and safe".
56
+ registerCacheReset(() => {
57
+ cache = new WeakMap();
58
+ });
59
+ return (owner, compute) => {
60
+ const cached = cache.get(owner);
61
+ if (cached !== undefined)
62
+ return cached;
63
+ const computed = compute();
64
+ cache.set(owner, computed);
65
+ return computed;
66
+ };
67
+ }
68
+ //# sourceMappingURL=selection-memo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selection-memo.js","sourceRoot":"","sources":["../src/selection-memo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAExD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,MAAM,UAAU,aAAa;IAC3B,IAAI,KAAK,GAAG,IAAI,OAAO,EAAe,CAAA;IACtC,6EAA6E;IAC7E,0EAA0E;IAC1E,6EAA6E;IAC7E,4EAA4E;IAC5E,6EAA6E;IAC7E,6EAA6E;IAC7E,0EAA0E;IAC1E,kBAAkB,CAAC,GAAG,EAAE;QACtB,KAAK,GAAG,IAAI,OAAO,EAAe,CAAA;IACpC,CAAC,CAAC,CAAA;IACF,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;QACxB,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAC/B,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,MAAM,CAAA;QACvC,MAAM,QAAQ,GAAG,OAAO,EAAE,CAAA;QAC1B,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAA;QAC1B,OAAO,QAAQ,CAAA;IACjB,CAAC,CAAA;AACH,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * A same-prototype shallow copy of a builder.
3
+ *
4
+ * The one place in the codebase that reaches for `Object.create` /
5
+ * `Object.getPrototypeOf`, which are the JS-interop boundary ADR-005's
6
+ * `eslint-disable` carve-out exists for. Every copy-on-write builder goes
7
+ * through here so that carve-out is written once and reviewed once.
8
+ *
9
+ * Shallow is deliberate. It gives the clone its own *slots*, which is what
10
+ * makes `next._x = ...` safe; a field holding a mutable container still needs
11
+ * the owning class to replace it, which is what the `copy()` overrides do.
12
+ * A deep clone would instead copy ts-morph `Project` graphs — the opposite of
13
+ * cheap, and it would break identity comparisons on `SourceFile`.
14
+ *
15
+ * Class fields declared with a constructor parameter property (`private
16
+ * readonly project`) are ordinary instance properties at runtime, so they come
17
+ * across too: a clone is built against the same project, without re-loading it.
18
+ */
19
+ export declare function shallowClone<T extends object>(source: T): T;
20
+ //# sourceMappingURL=shallow-clone.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shallow-clone.d.ts","sourceRoot":"","sources":["../src/shallow-clone.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAO3D"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * A same-prototype shallow copy of a builder.
3
+ *
4
+ * The one place in the codebase that reaches for `Object.create` /
5
+ * `Object.getPrototypeOf`, which are the JS-interop boundary ADR-005's
6
+ * `eslint-disable` carve-out exists for. Every copy-on-write builder goes
7
+ * through here so that carve-out is written once and reviewed once.
8
+ *
9
+ * Shallow is deliberate. It gives the clone its own *slots*, which is what
10
+ * makes `next._x = ...` safe; a field holding a mutable container still needs
11
+ * the owning class to replace it, which is what the `copy()` overrides do.
12
+ * A deep clone would instead copy ts-morph `Project` graphs — the opposite of
13
+ * cheap, and it would break identity comparisons on `SourceFile`.
14
+ *
15
+ * Class fields declared with a constructor parameter property (`private
16
+ * readonly project`) are ordinary instance properties at runtime, so they come
17
+ * across too: a clone is built against the same project, without re-loading it.
18
+ */
19
+ export function shallowClone(source) {
20
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- Object.getPrototypeOf returns `any`
21
+ const proto = Object.getPrototypeOf(source);
22
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- Object.create returns `any`
23
+ const clone = Object.create(proto);
24
+ Object.assign(clone, source);
25
+ return clone;
26
+ }
27
+ //# sourceMappingURL=shallow-clone.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shallow-clone.js","sourceRoot":"","sources":["../src/shallow-clone.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,YAAY,CAAmB,MAAS;IACtD,0GAA0G;IAC1G,MAAM,KAAK,GAAW,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,CAAA;IACnD,kGAAkG;IAClG,MAAM,KAAK,GAAM,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IACrC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;IAC5B,OAAO,KAAK,CAAA;AACd,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Write one message to stderr so it survives a test runner.
3
+ *
4
+ * A trailing newline is added when absent, because the call sites this replaced
5
+ * used `console.warn`, which appends one — omitting it at ten call sites is a
6
+ * mistake waiting to happen, and a message run onto the next is the defect this
7
+ * channel exists to avoid.
8
+ */
9
+ export declare function writeStderr(message: string): void;
10
+ /**
11
+ * Reset the lazily-attached listener. **Tests only** — a suite that asserts on
12
+ * the attachment needs to observe it happening rather than inherit it from
13
+ * whichever earlier test wrote first.
14
+ */
15
+ export declare function resetStderrGuardForTests(): void;
16
+ //# sourceMappingURL=stderr.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stderr.d.ts","sourceRoot":"","sources":["../src/stderr.ts"],"names":[],"mappings":"AA8CA;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAQjD;AAED;;;;GAIG;AAEH,wBAAgB,wBAAwB,IAAI,IAAI,CAG/C"}
package/dist/stderr.js ADDED
@@ -0,0 +1,73 @@
1
+ /**
2
+ * The library's single stderr channel.
3
+ *
4
+ * [ts-archunit Bug 0024](https://github.com/nielspeter/ts-archunit/blob/main/bugs/fixed/0024-warn-terminal-is-invisible-inside-a-test-runner.md):
5
+ * every library-originated message went through `console.warn`, and vitest's
6
+ * default reporter **intercepts** console output and replays it only for
7
+ * **failing** tests. `.warn()` never fails a test, so its output was always
8
+ * dropped. Measured on a real child `vitest run` with the default reporter: a
9
+ * passing test whose rule has 4 real violations printed **nothing** — zero
10
+ * occurrences of the violation text.
11
+ *
12
+ * That silence was not confined to `.warn()`. The stale-exclusion warning, the
13
+ * unused-exclusion warning, the exclusion-comment parse warnings, the
14
+ * invalid-baseline warning, the diff-aware fallback warning and `expression()`'s
15
+ * escape-hatch warning were all invisible in a passing test — five files' worth.
16
+ * A finding nobody reads has not been reported (ADR-009 rule 1).
17
+ *
18
+ * **The EPIPE guard is not optional, and it is why this is a function rather
19
+ * than a bare write.** Node's `Console` is constructed with `ignoreErrors: true`
20
+ * and swallows write errors; `process.stderr.write` does not. With a closed
21
+ * downstream pipe — `eess-ts check 2>&1 | head` — the error arrives
22
+ * **asynchronously**, so neither `try`/`catch` nor the write callback can see
23
+ * it, and the process dies with an uncaught EPIPE. Measured over 20 000 lines:
24
+ *
25
+ * bare process.stderr.write node exits 1
26
+ * with an 'error' listener attached node exits 0
27
+ * console.warn node exits 0
28
+ *
29
+ * `writeReport` already wrote to `process.stderr` unguarded, so that exit-1 was
30
+ * a live defect before this existed: a piped `check` could fail for EPIPE rather
31
+ * than for findings, and the two are indistinguishable from the exit code.
32
+ *
33
+ * A persistent listener rather than `once`: `once` removes itself after the
34
+ * first error, leaving a second EPIPE uncaught, and re-adding per write leaks
35
+ * listeners. One listener, attached lazily, never removed — the same trade
36
+ * `ignoreErrors: true` makes, and the reason it is documented here rather than
37
+ * hidden.
38
+ *
39
+ * **The accepted cost:** vitest annotates intercepted console output with the
40
+ * test that produced it (`stderr | file > test name`), and a direct write loses
41
+ * that. For a violation report the rule's own identity is in the message, so the
42
+ * loss is real but small — and being attributed to a test that never printed is
43
+ * worse than being unattributed.
44
+ */
45
+ let listenerAttached = false;
46
+ /**
47
+ * Write one message to stderr so it survives a test runner.
48
+ *
49
+ * A trailing newline is added when absent, because the call sites this replaced
50
+ * used `console.warn`, which appends one — omitting it at ten call sites is a
51
+ * mistake waiting to happen, and a message run onto the next is the defect this
52
+ * channel exists to avoid.
53
+ */
54
+ export function writeStderr(message) {
55
+ if (!listenerAttached) {
56
+ // See the module docstring: the EPIPE is asynchronous, so this listener is
57
+ // the only thing that can catch it.
58
+ process.stderr.on('error', () => { });
59
+ listenerAttached = true;
60
+ }
61
+ process.stderr.write(message.endsWith('\n') ? message : `${message}\n`);
62
+ }
63
+ /**
64
+ * Reset the lazily-attached listener. **Tests only** — a suite that asserts on
65
+ * the attachment needs to observe it happening rather than inherit it from
66
+ * whichever earlier test wrote first.
67
+ */
68
+ // eess-exclude eess/no-unused-exports: re-exported from `src/index.ts`; this gate does not count a barrel `export … from` re-export as usage — see work/bugs/0168
69
+ export function resetStderrGuardForTests() {
70
+ process.stderr.removeAllListeners('error');
71
+ listenerAttached = false;
72
+ }
73
+ //# sourceMappingURL=stderr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stderr.js","sourceRoot":"","sources":["../src/stderr.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,IAAI,gBAAgB,GAAG,KAAK,CAAA;AAE5B;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACtB,2EAA2E;QAC3E,oCAAoC;QACpC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;QACpC,gBAAgB,GAAG,IAAI,CAAA;IACzB,CAAC;IACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,IAAI,CAAC,CAAA;AACzE,CAAC;AAED;;;;GAIG;AACH,kKAAkK;AAClK,MAAM,UAAU,wBAAwB;IACtC,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAA;IAC1C,gBAAgB,GAAG,KAAK,CAAA;AAC1B,CAAC"}
@@ -3,23 +3,78 @@ import type { CheckOptions } from './check-options.js';
3
3
  import type { RuleMetadata } from './rule-metadata.js';
4
4
  import type { RuleDescription } from './rule-description.js';
5
5
  import type { SilentExclusion } from './silent-exclusion.js';
6
+ /**
7
+ * A rule's verdict, carrying evidence of examination alongside the findings —
8
+ * ADR-010. `examined` is the count of units the family's own examining seam
9
+ * actually iterated (never a count of matches, never upstream discovery like
10
+ * files loaded) — each concrete `collectViolations()` names its own unit.
11
+ *
12
+ * `sourceEmpty` — ADR-010 part 3's precedence rule: "an empty project
13
+ * outranks every token." Set `true` when `examined === 0` because the
14
+ * family's own upstream source (files loaded, a diagram parsed, an SDL
15
+ * string) produced nothing at all — BEFORE any predicate/selection
16
+ * narrowed it — as distinct from a predicate narrowing a non-empty source
17
+ * down to zero. This is a stronger claim than a bare zero-examined dead
18
+ * selector: `.expectEmpty()` cannot rescue it, because there is no
19
+ * selection to widen — the instrument itself never loaded anything to
20
+ * examine. Omit (or `false`) when the distinction doesn't apply (the
21
+ * family has no separate source/selection stages) or the zero came from
22
+ * narrowing, not upstream emptiness.
23
+ */
24
+ export interface CollectResult {
25
+ readonly violations: ArchViolation[];
26
+ readonly examined: number;
27
+ readonly sourceEmpty?: boolean;
28
+ /**
29
+ * A specific dead-glob explanation for a zero-examined rule, pre-formatted
30
+ * by the dialect that computed it (`RuleBuilder.deadGlobDiagnosis()`) —
31
+ * the kernel never touches picomatch or a project type to build this
32
+ * itself. `undefined` means no diagnosis is available (no glob declared,
33
+ * or the declared glob isn't actually dead), which falls through to the
34
+ * generic zero-examined finding the same as before this field existed.
35
+ */
36
+ readonly deadGlob?: string;
37
+ }
6
38
  /**
7
39
  * Abstract base class for builders that share the terminal method pattern
8
40
  * (because, rule, excluding, check, warn, severity) but have different
9
41
  * element collection and evaluation models.
10
42
  *
11
43
  * Used by SliceRuleBuilder, SchemaRuleBuilder, ResolverRuleBuilder,
12
- * PairFinalBuilder, and SmellBuilder — all of which implement the same
13
- * terminal methods but differ in how they collect and evaluate elements.
14
- *
15
- * RuleBuilder<T> does NOT extend this because it predates it and has
16
- * additional concerns (predicate/condition pipeline, fork semantics).
44
+ * PairFinalBuilder, SmellBuilder, CorrespondenceBuilder, TsconfigBuilder, and
45
+ * (as of plan 0088 Phase 4) RuleBuilder — all of which implement the same
46
+ * terminal methods and now share evidence-of-examination (ADR-010) alongside
47
+ * evaluation-model differences.
17
48
  */
18
49
  export declare abstract class TerminalBuilder {
19
50
  protected _reason?: string;
20
51
  protected _metadata?: RuleMetadata;
21
- private readonly _exclusions;
22
- private readonly _silentIndices;
52
+ protected _exclusions: (string | RegExp)[];
53
+ protected _silentIndices: Set<number>;
54
+ /** ADR-010 part 3 — `.expectEmpty()`/`.expectNonEmpty()`. `undefined` = no declaration. */
55
+ protected _expectEmpty?: boolean;
56
+ /**
57
+ * Declare that this rule's examined set is expected to be empty. A pass is
58
+ * legitimate only while the declaration stays true — the day a unit is
59
+ * examined, this rule reddens (ADR-010 part 3's expiry property). The
60
+ * complement of `expectNonEmpty()`; declaring both is nonsensical and the
61
+ * later call wins (last-write, matching `.rule()`'s own semantics).
62
+ */
63
+ expectEmpty(): this;
64
+ /**
65
+ * Declare that this rule's examined set must be non-empty — the opposite
66
+ * assertion, for a corpus the author has positive knowledge should never
67
+ * legitimately be empty. Unlike a bare, undeclared rule, this **overrides
68
+ * `assertsCardinality()`'s exemption**: a `.notExist()`-shaped condition
69
+ * that finds zero subjects normally passes silently (emptiness satisfies
70
+ * what it asserts) — but if the author declared `.expectNonEmpty()`, that
71
+ * silence is exactly what they said should never happen, so it reddens
72
+ * instead, naming the declaration as the reason. This is the real
73
+ * difference from not declaring anything: without it, a `.notExist()` rule
74
+ * over an accidentally-emptied corpus (a glob typo, a temporarily-missing
75
+ * folder) passes and says nothing; with it, that same emptiness is caught.
76
+ */
77
+ expectNonEmpty(): this;
23
78
  /**
24
79
  * Attach a human-readable rationale to the rule.
25
80
  * Included in violation messages when `.check()` throws.
@@ -49,6 +104,8 @@ export declare abstract class TerminalBuilder {
49
104
  * Used by the `explain` CLI subcommand.
50
105
  */
51
106
  describeRule(): RuleDescription;
107
+ /** This rule's own account of itself, for `vacuity-findings.ts`. */
108
+ private facts;
52
109
  /**
53
110
  * Execute the rule and return violations after exclusion filtering.
54
111
  * Does not throw — use for programmatic access (presets, aggregation).
@@ -68,6 +125,47 @@ export declare abstract class TerminalBuilder {
68
125
  * @param options - Optional baseline, diff filtering, and output format
69
126
  */
70
127
  warn(options?: CheckOptions): void;
128
+ /**
129
+ * ADR-010 — the evidence gate. Calls `collectViolations()` once and turns
130
+ * its `{ violations, examined, sourceEmpty }` verdict into the
131
+ * `ArchViolation[]` every terminal method still emits, applying the
132
+ * declared-empty precedence:
133
+ *
134
+ * - `examined === 0`, `sourceEmpty === true`: ADR-010 part 3's precedence
135
+ * rule — "an empty project outranks every token." The family's own
136
+ * upstream source (files loaded, a diagram parsed) produced nothing at
137
+ * all, before any predicate ran. Neither `.expectEmpty()` nor
138
+ * `assertsCardinality()` can rescue this: there is no selection to
139
+ * widen, and a `.notExist()`-shaped condition "passing" against an
140
+ * instrument that never loaded anything is not evidence of anything.
141
+ * Always an unsuppressable configuration finding.
142
+ * - `examined === 0`, `.expectNonEmpty()` declared: the caller has positive
143
+ * knowledge this corpus should never be empty — this OVERRIDES
144
+ * `assertsCardinality()`'s exemption (a `.notExist()` rule that would
145
+ * otherwise pass silently on zero subjects reddens instead, since silent
146
+ * emptiness is exactly what the declaration rules out). Fails, naming the
147
+ * unmet declaration.
148
+ * - `examined === 0`, no `.expectEmpty()` declared: the rule's own
149
+ * instrument is broken (a dead selector, an unreachable seam) — this is
150
+ * a configuration finding, unsuppressable (`bypassFilters`), not a silent
151
+ * pass. This is the guarantee ADR-009/010 exist for.
152
+ * - `examined === 0`, `.expectEmpty()` declared: satisfied. Passes.
153
+ * - `examined === 0`, `assertsCardinality()` true (and no `.expectNonEmpty()`
154
+ * override above): the rule's own conditions are satisfied BY emptiness
155
+ * (e.g. `.notExist()`) — a different exemption from `.expectEmpty()`,
156
+ * because it is a property of what the condition asserts, not a caller's
157
+ * declaration about the corpus. Passes, with no finding at all (not even
158
+ * a silent one — the condition already reported truthfully that it found
159
+ * nothing wrong).
160
+ * - `examined > 0`, `.expectEmpty()` declared: the declaration has expired
161
+ * — a unit was examined despite the author's "this stays empty" claim.
162
+ * Fails, appending an expiry finding to whatever `collectViolations()`
163
+ * found (never replacing it — the underlying findings are still real).
164
+ * - `examined > 0`, no declaration or `.expectNonEmpty()` (the ordinary
165
+ * case — `.expectNonEmpty()` has nothing left to assert once subjects
166
+ * exist): the rule's own violations stand as computed.
167
+ */
168
+ private evidencedViolations;
71
169
  /**
72
170
  * Execute the rule with the given severity.
73
171
  * `.severity('error')` is equivalent to `.check()`.
@@ -75,9 +173,111 @@ export declare abstract class TerminalBuilder {
75
173
  */
76
174
  severity(level: 'error' | 'warn'): void;
77
175
  /**
78
- * Subclasses implement this to collect and evaluate violations.
176
+ * An independent copy of this builder.
177
+ *
178
+ * **A held selection is immutable** (a bug class ported alongside the
179
+ * kernel — plan 0088 Phase 4, matching `ts-archunit`'s own documented
180
+ * "bug 0016"). Before this, every chain method mutated `this` in place:
181
+ *
182
+ * ```ts
183
+ * const repos = classes(p).that().extend('BaseRepository')
184
+ * repos.that().haveNameEndingWith('Legacy').should().notExist().check()
185
+ * repos.should().beExported().check() // silently narrowed to Legacy too
186
+ * ```
187
+ *
188
+ * A held selection reused for a second rule silently inherited the first
189
+ * rule's narrowing, exclusions, and rule id — found live in this repo's own
190
+ * test suite once the ADR-010 evidence gate stopped treating "examined
191
+ * zero units because a stale narrowing ate the selection" as an ordinary,
192
+ * silent pass. `because`/`rule`/`excluding`/`expectEmpty`/`expectNonEmpty`
193
+ * on `TerminalBuilder`, and `that`/`addPredicate`/`addCondition` on
194
+ * `RuleBuilder`, all return a copy rather than mutating `this`.
195
+ *
196
+ * Subclasses with additional fields beyond `TerminalBuilder`'s own MUST
197
+ * override this (see `RuleBuilder.copy()`) and call `super.copy()` first.
198
+ */
199
+ protected copy(): this;
200
+ /**
201
+ * Give this builder independent copies of another's filter state.
202
+ *
203
+ * `copy()` shallow-copies every field via `Object.assign`, so without this
204
+ * a copy would share its source's `_exclusions` array and `_silentIndices`
205
+ * set by reference — `.excluding()` on one would silently mutate the
206
+ * other, the exact hazard `copy()` exists to close, one field deeper.
207
+ */
208
+ protected adoptFilterState(source: TerminalBuilder): void;
209
+ /**
210
+ * Subclasses implement this to collect and evaluate violations, and to
211
+ * report how many units they examined (ADR-010) — counted at the family's
212
+ * own examining seam, in whatever unit that family names as its own
213
+ * (post-predicate subjects for `RuleBuilder`, bodies compared for
214
+ * `SmellBuilder`, the key sets of both sides for `correspondence()`).
79
215
  * Called lazily during `.check()` / `.warn()`.
80
216
  */
81
- protected abstract collectViolations(): ArchViolation[];
217
+ protected abstract collectViolations(): CollectResult;
218
+ /**
219
+ * Is this rule's own assertion satisfied by an empty examined set, as a
220
+ * matter of what it asserts (`.notExist()`-shaped), not a caller's
221
+ * `.expectEmpty()` declaration? Default `false` — most families have no
222
+ * such condition and stay covered by the ordinary zero-examined gate.
223
+ * `RuleBuilder` overrides this to check its own `_conditions` against the
224
+ * kernel's `assertsCardinality()` registry (ADR-010, `cardinality.ts`).
225
+ */
226
+ protected assertsCardinality(): boolean;
82
227
  }
228
+ /**
229
+ * The configuration finding for a rule that asserts nothing — bug 0155.
230
+ *
231
+ * An assertion-less rule — subjects found, nothing asserted about them —
232
+ * cannot fail, so it certifies nothing while reading as coverage.
233
+ *
234
+ * **The guard was unreachable, not merely quiet.** It used to read
235
+ * `_conditions.length === 0 && _phase === 'predicate'`, and `should()` sets
236
+ * the phase to `'condition'`, so for every rule shape the DSL documents it
237
+ * could never fire — the defect passed in total silence, never even reaching
238
+ * the stderr warning it was routed to. Hence no `_phase` term at the call
239
+ * site.
240
+ *
241
+ * **A finding, not a warning**, per ADR-009 rule 1's discriminator: the remedy
242
+ * is not optional. There is no state in which "keeps asserting nothing" is
243
+ * correct — add a condition, or delete the rule. The two rules ADR-009 names
244
+ * as deliberately `warn` (`no-silent-catch`, `no-empty-bodies`) warn *because*
245
+ * they carry suppressible false positives a reader must judge case by case.
246
+ * This carries none.
247
+ *
248
+ * **A declared emptiness expectation is an assertion**, so `_expectEmpty`
249
+ * exempts a rule from this gate. `.expectNonEmpty()` reddens when the corpus
250
+ * it says must never be empty becomes empty; `.expectEmpty()` reddens the day
251
+ * the set it says must stay empty gains a member. Neither lives in
252
+ * `_conditions`. Without that term the gate called a working corpus guard
253
+ * assertion-less and told its author to "add a condition or delete the rule" —
254
+ * both of which destroy the guard — and for `.expectEmpty()` reported two
255
+ * findings for one fault. Found in PR #71's review.
256
+ *
257
+ * **Placed AFTER the zero-examined branch**, so a dead selector still reports
258
+ * as a dead selector. Measured: `resideInFolder('srcc/**')` with no condition
259
+ * reports the dead glob, not this finding. An earlier draft of this docstring
260
+ * (and of the changeset and bug record) claimed the opposite — "reports the
261
+ * missing assertion only" — and justified it as gate-first. Both were wrong:
262
+ * `getElements()` and the predicate filter already ran, so the ordering saves
263
+ * no work, and the behaviour is the reverse. The real precedence is the better
264
+ * one, and is now stated as what it is: this finding fires only when subjects
265
+ * were actually selected.
266
+ *
267
+ * **Exported** rather than private to `RuleBuilder`. Its five siblings
268
+ * (`zeroExaminedViolation`, `deadGlobViolation`, `unmetExpectNonEmptyViolation`,
269
+ * `expiredExpectEmptyViolation`, `zeroLoadedSourceViolation`) have since moved to
270
+ * `vacuity-findings.ts`; this one stayed, because unlike them it is constructed
271
+ * from a builder's own condition list rather than from `RuleFacts` alone. The *detection* has to stay per-builder —
272
+ * `TerminalBuilder` has no `_conditions` — but a private constructor meant
273
+ * `eess-ts`'s slice/schema/resolver builders could not reuse it and were left
274
+ * warning while the kernel's rules failed: the same defect, one DSL, four
275
+ * different answers.
276
+ *
277
+ * `bypassFilters` makes it a **configuration** finding — `error` regardless of
278
+ * `.asSeverity('warn')`, refused by `.excluding()`, skipped by diff and
279
+ * baseline. It reports that the rule's own instrument is broken, not a fault
280
+ * in what was examined, so a filter aimed at the latter must not suppress it.
281
+ */
282
+ export declare function assertionLessViolation(ruleId: string, advice?: string): ArchViolation;
83
283
  //# sourceMappingURL=terminal-builder.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"terminal-builder.d.ts","sourceRoot":"","sources":["../src/terminal-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAC5D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAI5D;;;;;;;;;;;GAWG;AACH,8BAAsB,eAAe;IACnC,SAAS,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IAC1B,SAAS,CAAC,SAAS,CAAC,EAAE,YAAY,CAAA;IAClC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA0B;IACtD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAyB;IAExD;;;OAGG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAK7B;;;;;OAKG;IACH,IAAI,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI;IAQlC;;;;;;;;;;OAUG;IACH,SAAS,CAAC,GAAG,QAAQ,EAAE,CAAC,MAAM,GAAG,MAAM,GAAG,eAAe,CAAC,EAAE,GAAG,IAAI;IAYnE;;;OAGG;IACH,YAAY,IAAI,eAAe;IAW/B;;;OAGG;IACH,UAAU,IAAI,aAAa,EAAE;IAU7B;;;;;OAKG;IACH,KAAK,CAAC,OAAO,CAAC,EAAE,YAAY,GAAG,IAAI;IAcnC;;;;;OAKG;IACH,IAAI,CAAC,OAAO,CAAC,EAAE,YAAY,GAAG,IAAI;IAclC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI;IAQvC;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,iBAAiB,IAAI,aAAa,EAAE;CACxD"}
1
+ {"version":3,"file":"terminal-builder.d.ts","sourceRoot":"","sources":["../src/terminal-builder.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAC5D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAK5D;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,UAAU,EAAE,aAAa,EAAE,CAAA;IACpC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAA;IAC9B;;;;;;;OAOG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED;;;;;;;;;;GAUG;AACH,8BAAsB,eAAe;IACnC,SAAS,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IAC1B,SAAS,CAAC,SAAS,CAAC,EAAE,YAAY,CAAA;IAClC,SAAS,CAAC,WAAW,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAK;IAC/C,SAAS,CAAC,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAY;IACjD,2FAA2F;IAC3F,SAAS,CAAC,YAAY,CAAC,EAAE,OAAO,CAAA;IAEhC;;;;;;OAMG;IACH,WAAW,IAAI,IAAI;IAMnB;;;;;;;;;;;;OAYG;IACH,cAAc,IAAI,IAAI;IAMtB;;;OAGG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAM7B;;;;;OAKG;IACH,IAAI,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI;IASlC;;;;;;;;;;OAUG;IACH,SAAS,CAAC,GAAG,QAAQ,EAAE,CAAC,MAAM,GAAG,MAAM,GAAG,eAAe,CAAC,EAAE,GAAG,IAAI;IAanE;;;OAGG;IACH,YAAY,IAAI,eAAe;IAW/B,oEAAoE;IACpE,OAAO,CAAC,KAAK;IAQb;;;OAGG;IACH,UAAU,IAAI,aAAa,EAAE;IAS7B;;;;;OAKG;IACH,KAAK,CAAC,OAAO,CAAC,EAAE,YAAY,GAAG,IAAI;IAanC;;;;;OAKG;IACH,IAAI,CAAC,OAAO,CAAC,EAAE,YAAY,GAAG,IAAI;IAalC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAuCG;IACH,OAAO,CAAC,mBAAmB;IAqB3B;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI;IAQvC;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,SAAS,CAAC,IAAI,IAAI,IAAI;IAOtB;;;;;;;OAOG;IACH,SAAS,CAAC,gBAAgB,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI;IAKzD;;;;;;;OAOG;IACH,SAAS,CAAC,QAAQ,CAAC,iBAAiB,IAAI,aAAa;IAErD;;;;;;;OAOG;IACH,SAAS,CAAC,kBAAkB,IAAI,OAAO;CAGxC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,aAAa,CAiCrF"}