@nielspeter/eess 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 (119) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +26 -0
  3. package/dist/ansi.d.ts +7 -0
  4. package/dist/ansi.d.ts.map +1 -0
  5. package/dist/ansi.js +16 -0
  6. package/dist/ansi.js.map +1 -0
  7. package/dist/apply-fixes.d.ts +25 -0
  8. package/dist/apply-fixes.d.ts.map +1 -0
  9. package/dist/apply-fixes.js +52 -0
  10. package/dist/apply-fixes.js.map +1 -0
  11. package/dist/baseline-generator.d.ts +18 -0
  12. package/dist/baseline-generator.d.ts.map +1 -0
  13. package/dist/baseline-generator.js +29 -0
  14. package/dist/baseline-generator.js.map +1 -0
  15. package/dist/baseline.d.ts +83 -0
  16. package/dist/baseline.d.ts.map +1 -0
  17. package/dist/baseline.js +115 -0
  18. package/dist/baseline.js.map +1 -0
  19. package/dist/check-options.d.ts +47 -0
  20. package/dist/check-options.d.ts.map +1 -0
  21. package/dist/check-options.js +2 -0
  22. package/dist/check-options.js.map +1 -0
  23. package/dist/code-frame.d.ts +17 -0
  24. package/dist/code-frame.d.ts.map +1 -0
  25. package/dist/code-frame.js +32 -0
  26. package/dist/code-frame.js.map +1 -0
  27. package/dist/combinators.d.ts +55 -0
  28. package/dist/combinators.d.ts.map +1 -0
  29. package/dist/combinators.js +43 -0
  30. package/dist/combinators.js.map +1 -0
  31. package/dist/condition.d.ts +56 -0
  32. package/dist/condition.d.ts.map +1 -0
  33. package/dist/condition.js +2 -0
  34. package/dist/condition.js.map +1 -0
  35. package/dist/correspondence.d.ts +78 -0
  36. package/dist/correspondence.d.ts.map +1 -0
  37. package/dist/correspondence.js +127 -0
  38. package/dist/correspondence.js.map +1 -0
  39. package/dist/define.d.ts +41 -0
  40. package/dist/define.d.ts.map +1 -0
  41. package/dist/define.js +42 -0
  42. package/dist/define.js.map +1 -0
  43. package/dist/diff-aware.d.ts +37 -0
  44. package/dist/diff-aware.d.ts.map +1 -0
  45. package/dist/diff-aware.js +69 -0
  46. package/dist/diff-aware.js.map +1 -0
  47. package/dist/environment.d.ts +14 -0
  48. package/dist/environment.d.ts.map +1 -0
  49. package/dist/environment.js +20 -0
  50. package/dist/environment.js.map +1 -0
  51. package/dist/errors.d.ts +14 -0
  52. package/dist/errors.d.ts.map +1 -0
  53. package/dist/errors.js +21 -0
  54. package/dist/errors.js.map +1 -0
  55. package/dist/exclusion-comments.d.ts +52 -0
  56. package/dist/exclusion-comments.d.ts.map +1 -0
  57. package/dist/exclusion-comments.js +183 -0
  58. package/dist/exclusion-comments.js.map +1 -0
  59. package/dist/execute-rule.d.ts +31 -0
  60. package/dist/execute-rule.d.ts.map +1 -0
  61. package/dist/execute-rule.js +119 -0
  62. package/dist/execute-rule.js.map +1 -0
  63. package/dist/format-github.d.ts +25 -0
  64. package/dist/format-github.d.ts.map +1 -0
  65. package/dist/format-github.js +44 -0
  66. package/dist/format-github.js.map +1 -0
  67. package/dist/format-json.d.ts +12 -0
  68. package/dist/format-json.d.ts.map +1 -0
  69. package/dist/format-json.js +30 -0
  70. package/dist/format-json.js.map +1 -0
  71. package/dist/format.d.ts +25 -0
  72. package/dist/format.d.ts.map +1 -0
  73. package/dist/format.js +66 -0
  74. package/dist/format.js.map +1 -0
  75. package/dist/index.d.ts +42 -0
  76. package/dist/index.d.ts.map +1 -0
  77. package/dist/index.js +40 -0
  78. package/dist/index.js.map +1 -0
  79. package/dist/matching.d.ts +31 -0
  80. package/dist/matching.d.ts.map +1 -0
  81. package/dist/matching.js +64 -0
  82. package/dist/matching.js.map +1 -0
  83. package/dist/predicate.d.ts +11 -0
  84. package/dist/predicate.d.ts.map +1 -0
  85. package/dist/predicate.js +2 -0
  86. package/dist/predicate.js.map +1 -0
  87. package/dist/preset-dispatch.d.ts +46 -0
  88. package/dist/preset-dispatch.d.ts.map +1 -0
  89. package/dist/preset-dispatch.js +48 -0
  90. package/dist/preset-dispatch.js.map +1 -0
  91. package/dist/report.d.ts +38 -0
  92. package/dist/report.d.ts.map +1 -0
  93. package/dist/report.js +45 -0
  94. package/dist/report.js.map +1 -0
  95. package/dist/rule-builder.d.ts +188 -0
  96. package/dist/rule-builder.d.ts.map +1 -0
  97. package/dist/rule-builder.js +323 -0
  98. package/dist/rule-builder.js.map +1 -0
  99. package/dist/rule-description.d.ts +12 -0
  100. package/dist/rule-description.d.ts.map +1 -0
  101. package/dist/rule-description.js +2 -0
  102. package/dist/rule-description.js.map +1 -0
  103. package/dist/rule-metadata.d.ts +17 -0
  104. package/dist/rule-metadata.d.ts.map +1 -0
  105. package/dist/rule-metadata.js +2 -0
  106. package/dist/rule-metadata.js.map +1 -0
  107. package/dist/silent-exclusion.d.ts +38 -0
  108. package/dist/silent-exclusion.d.ts.map +1 -0
  109. package/dist/silent-exclusion.js +30 -0
  110. package/dist/silent-exclusion.js.map +1 -0
  111. package/dist/terminal-builder.d.ts +83 -0
  112. package/dist/terminal-builder.d.ts.map +1 -0
  113. package/dist/terminal-builder.js +134 -0
  114. package/dist/terminal-builder.js.map +1 -0
  115. package/dist/violation.d.ts +51 -0
  116. package/dist/violation.d.ts.map +1 -0
  117. package/dist/violation.js +2 -0
  118. package/dist/violation.js.map +1 -0
  119. package/package.json +40 -0
@@ -0,0 +1,43 @@
1
+ export function not(input) {
2
+ if (typeof input === 'function') {
3
+ return (value) => !input(value);
4
+ }
5
+ return {
6
+ description: `not (${input.description})`,
7
+ test: (element) => !input.test(element),
8
+ };
9
+ }
10
+ function assertHomogeneous(inputs) {
11
+ if (inputs.length === 0)
12
+ return;
13
+ const firstIsFunction = typeof inputs[0] === 'function';
14
+ if (inputs.some((i) => (typeof i === 'function') !== firstIsFunction)) {
15
+ throw new TypeError('Cannot mix Predicate objects and Matcher functions in and()/or()');
16
+ }
17
+ }
18
+ export function and(...inputs) {
19
+ assertHomogeneous(inputs);
20
+ if (typeof inputs[0] === 'function') {
21
+ const matchers = inputs.filter((input) => typeof input === 'function');
22
+ const fn = (value) => matchers.every((m) => m(value));
23
+ return fn;
24
+ }
25
+ const predicates = inputs.filter((input) => typeof input !== 'function');
26
+ return {
27
+ description: predicates.map((p) => p.description).join(' and '),
28
+ test: (element) => predicates.every((p) => p.test(element)),
29
+ };
30
+ }
31
+ export function or(...inputs) {
32
+ assertHomogeneous(inputs);
33
+ if (typeof inputs[0] === 'function') {
34
+ const matchers = inputs.filter((input) => typeof input === 'function');
35
+ return (value) => matchers.some((m) => m(value));
36
+ }
37
+ const predicates = inputs.filter((input) => typeof input !== 'function');
38
+ return {
39
+ description: predicates.map((p) => p.description).join(' or '),
40
+ test: (element) => predicates.some((p) => p.test(element)),
41
+ };
42
+ }
43
+ //# sourceMappingURL=combinators.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"combinators.js","sourceRoot":"","sources":["../src/combinators.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ import type { ArchViolation } from './violation.js';
2
+ /**
3
+ * Context passed to conditions during evaluation.
4
+ *
5
+ * Provides the rule description and optional rationale so that
6
+ * violations can include meaningful error messages.
7
+ */
8
+ export interface ConditionContext {
9
+ /** Human-readable rule description assembled from the fluent chain */
10
+ rule: string;
11
+ /** Optional rationale provided via .because() */
12
+ because?: string;
13
+ /** Unique rule identifier from .rule({ id }) */
14
+ ruleId?: string;
15
+ /** Actionable fix suggestion from .rule({ suggestion }) */
16
+ suggestion?: string;
17
+ /** Link to documentation from .rule({ docs }) */
18
+ docs?: string;
19
+ /**
20
+ * Index of the call argument to fold into the violation element/message.
21
+ *
22
+ * Read by `calls()` conditions in `src/conditions/call.ts` when building
23
+ * violations — threaded into `archCall.getName({ withArgument: ... })`
24
+ * so identity-keyed registrations (HTTP routes, event handlers,
25
+ * registry entries, etc.) can be excluded individually rather than
26
+ * only by file. See proposal 011 / plan 0057.
27
+ *
28
+ * Conditions for other builder types (class, function, module, JSX,
29
+ * etc.) simply ignore this field — it's a `calls()`-specific concern
30
+ * placed on the shared context because abstraction cost would exceed
31
+ * the leak for one optional primitive.
32
+ */
33
+ identifyByArgument?: number;
34
+ }
35
+ /**
36
+ * A condition that evaluates filtered elements and returns violations.
37
+ *
38
+ * Conditions receive the elements that passed predicate filtering.
39
+ * They return violations for elements that DON'T satisfy the condition.
40
+ *
41
+ * Most conditions check each element individually. Some (like notExist)
42
+ * check the entire set.
43
+ */
44
+ export interface Condition<T> {
45
+ /** Human-readable description of what this condition checks */
46
+ readonly description: string;
47
+ /**
48
+ * Evaluate elements against this condition.
49
+ *
50
+ * @param elements - The filtered elements (after predicates)
51
+ * @param context - Rule description and rationale
52
+ * @returns Violations for elements that don't satisfy the condition
53
+ */
54
+ evaluate(elements: T[], context: ConditionContext): ArchViolation[];
55
+ }
56
+ //# sourceMappingURL=condition.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"condition.d.ts","sourceRoot":"","sources":["../src/condition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAA;IACZ,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,2DAA2D;IAC3D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,iDAAiD;IACjD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;;;;;;;;;;OAaG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,SAAS,CAAC,CAAC;IAC1B,+DAA+D;IAC/D,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAE5B;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,gBAAgB,GAAG,aAAa,EAAE,CAAA;CACpE"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=condition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"condition.js","sourceRoot":"","sources":["../src/condition.ts"],"names":[],"mappings":""}
@@ -0,0 +1,78 @@
1
+ import type { ArchViolation } from './violation.js';
2
+ import { TerminalBuilder } from './terminal-builder.js';
3
+ /** How to identify an element for a two-sided violation message. */
4
+ export interface ElementInfo {
5
+ readonly name: string;
6
+ readonly file?: string;
7
+ readonly line?: number;
8
+ }
9
+ /**
10
+ * A labelled set of elements produced by a dialect's `.select()` terminal.
11
+ * Engine-neutral: `T` is opaque; `identify` yields message metadata.
12
+ */
13
+ export interface Selection<T> {
14
+ readonly elements: readonly T[];
15
+ readonly label: string;
16
+ readonly identify: (element: T) => ElementInfo;
17
+ }
18
+ export type Direction = 'left-to-right' | 'right-to-left' | 'both';
19
+ /** Extract related endpoints from an element (for `preserveRelations`). */
20
+ export interface RelationSpec<L, R> {
21
+ readonly left: (element: L) => readonly string[];
22
+ readonly right: (element: R) => readonly string[];
23
+ readonly direction?: Direction;
24
+ }
25
+ /**
26
+ * Join-key extraction for a correspondence (O(n+m) indexed matching).
27
+ *
28
+ * - A single function keys both sides — each element is `L | R`. Convenient
29
+ * when both sides share a key shape.
30
+ * - A `{ left, right }` pair keys each side with its own type — no
31
+ * union-narrowing at the call site, and the join key may differ from the
32
+ * display name (show "ADR 001", join on "001").
33
+ *
34
+ * Omit `keyBy` entirely to key by each side's `identify().name`.
35
+ */
36
+ export type KeyBy<L, R> = ((element: L | R) => string) | {
37
+ readonly left: (l: L) => string;
38
+ readonly right: (r: R) => string;
39
+ };
40
+ export interface CorrespondenceOptions<L, R> {
41
+ readonly left: Selection<L>;
42
+ readonly right: Selection<R>;
43
+ /** Fast path: extract a join key per element (O(n+m)). Defaults to `identify().name`. */
44
+ readonly keyBy?: KeyBy<L, R>;
45
+ /** General fallback: arbitrary predicate (O(n×m)). Use only when no key exists. */
46
+ readonly matchBy?: (left: L, right: R) => boolean;
47
+ /** Optional per-side fix suggestions for the two-sided message. */
48
+ readonly suggest?: {
49
+ readonly left?: (info: ElementInfo, right: Selection<R>) => string;
50
+ readonly right?: (info: ElementInfo, left: Selection<L>) => string;
51
+ };
52
+ }
53
+ /**
54
+ * Builder binding two element selections and asserting they correspond. A
55
+ * `TerminalBuilder` (it operates over two selections, like the pair builders),
56
+ * so `because`/`rule`/`warn`/`excluding`/baseline come for free. It does NOT
57
+ * compose with the `not`/`and`/`or` combinators (those act on
58
+ * `Predicate`/`Condition`). Chain checks with `.andShould()`.
59
+ */
60
+ export declare class CorrespondenceBuilder<L, R> extends TerminalBuilder {
61
+ private readonly opts;
62
+ private readonly _checks;
63
+ constructor(opts: CorrespondenceOptions<L, R>);
64
+ /** Readability marker; the condition phase is implicit. */
65
+ should(): this;
66
+ /** Chain another correspondence check (AND). */
67
+ andShould(): this;
68
+ /** Every element on the named side(s) has a counterpart on the other. */
69
+ beComplete(opts?: {
70
+ direction?: Direction;
71
+ }): this;
72
+ /** Related endpoints on one side have matching relations on the other. */
73
+ preserveRelations(spec: RelationSpec<L, R>): this;
74
+ protected collectViolations(): ArchViolation[];
75
+ }
76
+ /** Bind two selections into a correspondence. */
77
+ export declare function correspondence<L, R>(opts: CorrespondenceOptions<L, R>): CorrespondenceBuilder<L, R>;
78
+ //# sourceMappingURL=correspondence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"correspondence.d.ts","sourceRoot":"","sources":["../src/correspondence.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,127 @@
1
+ import { TerminalBuilder } from './terminal-builder.js';
2
+ import { matchSelections } from './matching.js';
3
+ function violationFor(info, message, ruleId) {
4
+ return {
5
+ rule: 'correspondence',
6
+ ruleId,
7
+ element: info.name,
8
+ file: info.file ?? '<selection>',
9
+ line: info.line ?? 0,
10
+ message,
11
+ codeFrame: undefined,
12
+ };
13
+ }
14
+ /**
15
+ * Builder binding two element selections and asserting they correspond. A
16
+ * `TerminalBuilder` (it operates over two selections, like the pair builders),
17
+ * so `because`/`rule`/`warn`/`excluding`/baseline come for free. It does NOT
18
+ * compose with the `not`/`and`/`or` combinators (those act on
19
+ * `Predicate`/`Condition`). Chain checks with `.andShould()`.
20
+ */
21
+ export class CorrespondenceBuilder extends TerminalBuilder {
22
+ opts;
23
+ _checks = [];
24
+ constructor(opts) {
25
+ super();
26
+ this.opts = opts;
27
+ }
28
+ /** Readability marker; the condition phase is implicit. */
29
+ should() {
30
+ return this;
31
+ }
32
+ /** Chain another correspondence check (AND). */
33
+ andShould() {
34
+ return this;
35
+ }
36
+ /** Every element on the named side(s) has a counterpart on the other. */
37
+ beComplete(opts = {}) {
38
+ this._checks.push((o, ruleId) => completeness(o, opts.direction ?? 'both', ruleId));
39
+ return this;
40
+ }
41
+ /** Related endpoints on one side have matching relations on the other. */
42
+ preserveRelations(spec) {
43
+ this._checks.push((o, ruleId) => relations(o, spec, ruleId));
44
+ return this;
45
+ }
46
+ collectViolations() {
47
+ const ruleId = this._metadata?.id;
48
+ return this._checks.flatMap((check) => check(this.opts, ruleId));
49
+ }
50
+ }
51
+ /** Bind two selections into a correspondence. */
52
+ export function correspondence(opts) {
53
+ return new CorrespondenceBuilder(opts);
54
+ }
55
+ // --- matching internals (delegate to the shared matchSelections engine) ---
56
+ function matchOptsFor(o) {
57
+ if (o.matchBy)
58
+ return { matchBy: o.matchBy };
59
+ const { keyBy, left, right } = o;
60
+ // Omitted → key by each side's identify().name. A function → same key both
61
+ // sides. A { left, right } pair → each side keyed by its own extractor.
62
+ if (keyBy === undefined) {
63
+ return {
64
+ leftKey: (el) => left.identify(el).name,
65
+ rightKey: (el) => right.identify(el).name,
66
+ };
67
+ }
68
+ if (typeof keyBy === 'function') {
69
+ return { leftKey: keyBy, rightKey: keyBy };
70
+ }
71
+ return { leftKey: keyBy.left, rightKey: keyBy.right };
72
+ }
73
+ function completeness(o, direction, ruleId) {
74
+ const out = [];
75
+ const suggestLeft = o.suggest?.left;
76
+ const suggestRight = o.suggest?.right;
77
+ const result = matchSelections(o.left.elements, o.right.elements, matchOptsFor(o));
78
+ if (direction !== 'right-to-left') {
79
+ for (const l of result.leftUnmatched) {
80
+ const info = o.left.identify(l);
81
+ const extra = suggestLeft ? `\n ${suggestLeft(info, o.right)}` : '';
82
+ out.push(violationFor(info, `${o.left.label} "${info.name}" has no matching ${o.right.label}${extra}`, ruleId));
83
+ }
84
+ for (const l of result.leftAmbiguous) {
85
+ const info = o.left.identify(l);
86
+ out.push(violationFor(info, `${o.left.label} "${info.name}" matches multiple ${o.right.label}s — the correspondence is ambiguous`, ruleId));
87
+ }
88
+ }
89
+ if (direction !== 'left-to-right') {
90
+ for (const r of result.rightUnmatched) {
91
+ const info = o.right.identify(r);
92
+ const extra = suggestRight ? `\n ${suggestRight(info, o.left)}` : '';
93
+ out.push(violationFor(info, `${o.right.label} "${info.name}" has no matching ${o.left.label}${extra}`, ruleId));
94
+ }
95
+ }
96
+ return out;
97
+ }
98
+ function relations(o, spec, ruleId) {
99
+ const out = [];
100
+ const direction = spec.direction ?? 'both';
101
+ const result = matchSelections(o.left.elements, o.right.elements, matchOptsFor(o));
102
+ // group right counterparts by their left element (object identity)
103
+ const byLeft = new Map();
104
+ for (const p of result.pairs) {
105
+ const list = byLeft.get(p.left);
106
+ if (list)
107
+ list.push(p.right);
108
+ else
109
+ byLeft.set(p.left, [p.right]);
110
+ }
111
+ if (direction !== 'right-to-left') {
112
+ for (const l of o.left.elements) {
113
+ const counterparts = byLeft.get(l) ?? [];
114
+ if (counterparts.length === 0)
115
+ continue;
116
+ const rightRelations = new Set(counterparts.flatMap((r) => spec.right(r)));
117
+ const info = o.left.identify(l);
118
+ for (const rel of spec.left(l)) {
119
+ if (!rightRelations.has(rel)) {
120
+ out.push(violationFor(info, `${o.left.label} "${info.name}" relates to "${rel}" but its ${o.right.label} counterpart does not`, ruleId));
121
+ }
122
+ }
123
+ }
124
+ }
125
+ return out;
126
+ }
127
+ //# sourceMappingURL=correspondence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"correspondence.js","sourceRoot":"","sources":["../src/correspondence.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ import type { Predicate } from './predicate.js';
2
+ import type { Condition, ConditionContext } from './condition.js';
3
+ import type { ArchViolation } from './violation.js';
4
+ /**
5
+ * Create a custom predicate for use in `.that().satisfy()` chains.
6
+ *
7
+ * The predicate filters elements — return `true` to keep, `false` to exclude.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * const isAbstract = definePredicate<ClassDeclaration>(
12
+ * 'is abstract',
13
+ * (cls) => cls.isAbstract()
14
+ * )
15
+ *
16
+ * classes(p).that().satisfy(isAbstract).should().beExported().check()
17
+ * ```
18
+ */
19
+ export declare function definePredicate<T>(description: string, test: (element: T) => boolean): Predicate<T>;
20
+ /**
21
+ * Create a custom condition for use in `.should().satisfy()` chains.
22
+ *
23
+ * The callback receives the filtered element array and rule context.
24
+ * Return an `ArchViolation[]` for elements that fail the condition.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * const useSharedHelper = defineCondition<ClassDeclaration>(
29
+ * 'use shared count helper',
30
+ * (classes, context) => {
31
+ * return classes
32
+ * .filter(cls => !usesHelper(cls))
33
+ * .map(cls => createViolation(cls, 'should use shared count helper', context))
34
+ * }
35
+ * )
36
+ *
37
+ * classes(p).that().extend('Base').should().satisfy(useSharedHelper).check()
38
+ * ```
39
+ */
40
+ export declare function defineCondition<T>(description: string, evaluate: (elements: T[], context: ConditionContext) => ArchViolation[]): Condition<T>;
41
+ //# sourceMappingURL=define.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define.d.ts","sourceRoot":"","sources":["../src/define.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAC/B,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,OAAO,GAC5B,SAAS,CAAC,CAAC,CAAC,CAEd;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAC/B,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,gBAAgB,KAAK,aAAa,EAAE,GACtE,SAAS,CAAC,CAAC,CAAC,CAEd"}
package/dist/define.js ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Create a custom predicate for use in `.that().satisfy()` chains.
3
+ *
4
+ * The predicate filters elements — return `true` to keep, `false` to exclude.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * const isAbstract = definePredicate<ClassDeclaration>(
9
+ * 'is abstract',
10
+ * (cls) => cls.isAbstract()
11
+ * )
12
+ *
13
+ * classes(p).that().satisfy(isAbstract).should().beExported().check()
14
+ * ```
15
+ */
16
+ export function definePredicate(description, test) {
17
+ return { description, test };
18
+ }
19
+ /**
20
+ * Create a custom condition for use in `.should().satisfy()` chains.
21
+ *
22
+ * The callback receives the filtered element array and rule context.
23
+ * Return an `ArchViolation[]` for elements that fail the condition.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * const useSharedHelper = defineCondition<ClassDeclaration>(
28
+ * 'use shared count helper',
29
+ * (classes, context) => {
30
+ * return classes
31
+ * .filter(cls => !usesHelper(cls))
32
+ * .map(cls => createViolation(cls, 'should use shared count helper', context))
33
+ * }
34
+ * )
35
+ *
36
+ * classes(p).that().extend('Base').should().satisfy(useSharedHelper).check()
37
+ * ```
38
+ */
39
+ export function defineCondition(description, evaluate) {
40
+ return { description, evaluate };
41
+ }
42
+ //# sourceMappingURL=define.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define.js","sourceRoot":"","sources":["../src/define.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,eAAe,CAC7B,WAAmB,EACnB,IAA6B;IAE7B,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,CAAA;AAC9B,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,eAAe,CAC7B,WAAmB,EACnB,QAAuE;IAEvE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAA;AAClC,CAAC"}
@@ -0,0 +1,37 @@
1
+ import type { ArchViolation } from './violation.js';
2
+ /**
3
+ * A diff filter that restricts violation reporting to files
4
+ * changed since a base branch.
5
+ *
6
+ * IMPORTANT: Rules evaluate the FULL project (needed for cross-file
7
+ * rules like cycles and layer ordering). Only the REPORTING is filtered
8
+ * to changed files. This ensures correctness — a new file that creates
9
+ * a cycle is detected even though the cycle involves unchanged files.
10
+ */
11
+ export declare class DiffFilter {
12
+ private readonly changedFiles;
13
+ constructor(changedFiles: Set<string> | null);
14
+ /**
15
+ * Filter violations to only those in changed files.
16
+ * If changedFiles is null (git error), returns all violations unfiltered.
17
+ */
18
+ filterToChanged(violations: ArchViolation[]): ArchViolation[];
19
+ /** Number of changed files detected, or -1 if diff unavailable */
20
+ get size(): number;
21
+ }
22
+ /**
23
+ * Create a diff filter from git, comparing HEAD against a base branch.
24
+ *
25
+ * Uses `git diff --name-only <base>...HEAD` to find changed files.
26
+ * Resolves relative paths to absolute paths for matching against
27
+ * violation file paths (which are always absolute).
28
+ *
29
+ * @param baseBranch - The base branch to diff against (default: 'main')
30
+ * @returns A DiffFilter for use with check(\{ diff \})
31
+ *
32
+ * @example
33
+ * // Only report violations in files changed since main
34
+ * classes(p).should().notContain(call('eval')).check(\{ diff: diffAware('main') \})
35
+ */
36
+ export declare function diffAware(baseBranch?: string): DiffFilter;
37
+ //# sourceMappingURL=diff-aware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-aware.d.ts","sourceRoot":"","sources":["../src/diff-aware.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD;;;;;;;;GAQG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAoB;gBAErC,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI;IAI5C;;;OAGG;IACH,eAAe,CAAC,UAAU,EAAE,aAAa,EAAE,GAAG,aAAa,EAAE;IAM7D,kEAAkE;IAClE,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,UAAU,GAAE,MAAe,GAAG,UAAU,CA6BjE"}
@@ -0,0 +1,69 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import path from 'node:path';
3
+ /**
4
+ * A diff filter that restricts violation reporting to files
5
+ * changed since a base branch.
6
+ *
7
+ * IMPORTANT: Rules evaluate the FULL project (needed for cross-file
8
+ * rules like cycles and layer ordering). Only the REPORTING is filtered
9
+ * to changed files. This ensures correctness — a new file that creates
10
+ * a cycle is detected even though the cycle involves unchanged files.
11
+ */
12
+ export class DiffFilter {
13
+ changedFiles;
14
+ constructor(changedFiles) {
15
+ this.changedFiles = changedFiles;
16
+ }
17
+ /**
18
+ * Filter violations to only those in changed files.
19
+ * If changedFiles is null (git error), returns all violations unfiltered.
20
+ */
21
+ filterToChanged(violations) {
22
+ const files = this.changedFiles;
23
+ if (files === null)
24
+ return violations;
25
+ return violations.filter((v) => files.has(v.file));
26
+ }
27
+ /** Number of changed files detected, or -1 if diff unavailable */
28
+ get size() {
29
+ return this.changedFiles === null ? -1 : this.changedFiles.size;
30
+ }
31
+ }
32
+ /**
33
+ * Create a diff filter from git, comparing HEAD against a base branch.
34
+ *
35
+ * Uses `git diff --name-only <base>...HEAD` to find changed files.
36
+ * Resolves relative paths to absolute paths for matching against
37
+ * violation file paths (which are always absolute).
38
+ *
39
+ * @param baseBranch - The base branch to diff against (default: 'main')
40
+ * @returns A DiffFilter for use with check(\{ diff \})
41
+ *
42
+ * @example
43
+ * // Only report violations in files changed since main
44
+ * classes(p).should().notContain(call('eval')).check(\{ diff: diffAware('main') \})
45
+ */
46
+ export function diffAware(baseBranch = 'main') {
47
+ const cwd = process.cwd();
48
+ let output;
49
+ try {
50
+ output = execFileSync('git', ['diff', '--name-only', `${baseBranch}...HEAD`], {
51
+ encoding: 'utf-8',
52
+ cwd,
53
+ stdio: ['pipe', 'pipe', 'pipe'],
54
+ }).trim();
55
+ }
56
+ catch (err) {
57
+ // Not a git repo, or base branch doesn't exist — skip filtering (report all violations)
58
+ const detail = err instanceof Error ? ` (${err.message.split('\n')[0] ?? ''})` : '';
59
+ console.warn(`[eess] Could not run git diff against '${baseBranch}'${detail}. All violations will be reported.`);
60
+ return new DiffFilter(null);
61
+ }
62
+ if (output === '') {
63
+ // No changes — empty set means nothing is "changed", so all violations are filtered out
64
+ return new DiffFilter(new Set());
65
+ }
66
+ const changedFiles = new Set(output.split('\n').map((relativePath) => path.resolve(cwd, relativePath)));
67
+ return new DiffFilter(changedFiles);
68
+ }
69
+ //# sourceMappingURL=diff-aware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-aware.js","sourceRoot":"","sources":["../src/diff-aware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,IAAI,MAAM,WAAW,CAAA;AAG5B;;;;;;;;GAQG;AACH,MAAM,OAAO,UAAU;IACJ,YAAY,CAAoB;IAEjD,YAAY,YAAgC;QAC1C,IAAI,CAAC,YAAY,GAAG,YAAY,CAAA;IAClC,CAAC;IAED;;;OAGG;IACH,eAAe,CAAC,UAA2B;QACzC,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,CAAA;QAC/B,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO,UAAU,CAAA;QACrC,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;IACpD,CAAC;IAED,kEAAkE;IAClE,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAA;IACjE,CAAC;CACF;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,SAAS,CAAC,aAAqB,MAAM;IACnD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAA;IAEzB,IAAI,MAAc,CAAA;IAClB,IAAI,CAAC;QACH,MAAM,GAAG,YAAY,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,UAAU,SAAS,CAAC,EAAE;YAC5E,QAAQ,EAAE,OAAO;YACjB,GAAG;YACH,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;SAChC,CAAC,CAAC,IAAI,EAAE,CAAA;IACX,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,wFAAwF;QACxF,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,CAAA;QACnF,OAAO,CAAC,IAAI,CACV,0CAA0C,UAAU,IAAI,MAAM,oCAAoC,CACnG,CAAA;QACD,OAAO,IAAI,UAAU,CAAC,IAAI,CAAC,CAAA;IAC7B,CAAC;IAED,IAAI,MAAM,KAAK,EAAE,EAAE,CAAC;QAClB,wFAAwF;QACxF,OAAO,IAAI,UAAU,CAAC,IAAI,GAAG,EAAE,CAAC,CAAA;IAClC,CAAC;IAED,MAAM,YAAY,GAAG,IAAI,GAAG,CAC1B,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC,CAC1E,CAAA;IAED,OAAO,IAAI,UAAU,CAAC,YAAY,CAAC,CAAA;AACrC,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { OutputFormat } from './check-options.js';
2
+ /**
3
+ * Detect the current CI environment and return the appropriate output format.
4
+ *
5
+ * - GitHub Actions: detected via GITHUB_ACTIONS env var
6
+ * - Other CI: detected via CI env var (falls back to terminal)
7
+ * - Local: terminal
8
+ */
9
+ export declare function detectFormat(): OutputFormat;
10
+ /**
11
+ * Check if running in any CI environment.
12
+ */
13
+ export declare function isCI(): boolean;
14
+ //# sourceMappingURL=environment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"environment.d.ts","sourceRoot":"","sources":["../src/environment.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEtD;;;;;;GAMG;AACH,wBAAgB,YAAY,IAAI,YAAY,CAK3C;AAED;;GAEG;AACH,wBAAgB,IAAI,IAAI,OAAO,CAE9B"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Detect the current CI environment and return the appropriate output format.
3
+ *
4
+ * - GitHub Actions: detected via GITHUB_ACTIONS env var
5
+ * - Other CI: detected via CI env var (falls back to terminal)
6
+ * - Local: terminal
7
+ */
8
+ export function detectFormat() {
9
+ if (process.env['GITHUB_ACTIONS'] === 'true') {
10
+ return 'github';
11
+ }
12
+ return 'terminal';
13
+ }
14
+ /**
15
+ * Check if running in any CI environment.
16
+ */
17
+ export function isCI() {
18
+ return process.env['CI'] === 'true' || process.env['GITHUB_ACTIONS'] === 'true';
19
+ }
20
+ //# sourceMappingURL=environment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"environment.js","sourceRoot":"","sources":["../src/environment.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,UAAU,YAAY;IAC1B,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,KAAK,MAAM,EAAE,CAAC;QAC7C,OAAO,QAAQ,CAAA;IACjB,CAAC;IACD,OAAO,UAAU,CAAA;AACnB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,IAAI;IAClB,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,KAAK,MAAM,CAAA;AACjF,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { ArchViolation } from './violation.js';
2
+ /**
3
+ * Thrown by `.check()` when architecture violations are found.
4
+ *
5
+ * Integrates naturally with vitest/jest — the test fails with a readable
6
+ * error message listing all violations and their locations.
7
+ */
8
+ export declare class ArchRuleError extends Error {
9
+ #private;
10
+ constructor(violations: ArchViolation[], reason?: string);
11
+ /** The violations that failed the rule, in report order. */
12
+ get violations(): ArchViolation[];
13
+ }
14
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD;;;;;GAKG;AACH,qBAAa,aAAc,SAAQ,KAAK;;gBAG1B,UAAU,EAAE,aAAa,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM;IAQxD,4DAA4D;IAC5D,IAAI,UAAU,IAAI,aAAa,EAAE,CAEhC;CACF"}
package/dist/errors.js ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Thrown by `.check()` when architecture violations are found.
3
+ *
4
+ * Integrates naturally with vitest/jest — the test fails with a readable
5
+ * error message listing all violations and their locations.
6
+ */
7
+ export class ArchRuleError extends Error {
8
+ #violations;
9
+ constructor(violations, reason) {
10
+ const summary = `Architecture violation${violations.length === 1 ? '' : 's'} (${String(violations.length)} found)`;
11
+ const reasonLine = reason ? ` — ${reason}` : '';
12
+ super(`${summary}${reasonLine}`);
13
+ this.name = 'ArchRuleError';
14
+ this.#violations = violations;
15
+ }
16
+ /** The violations that failed the rule, in report order. */
17
+ get violations() {
18
+ return this.#violations;
19
+ }
20
+ }
21
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,OAAO,aAAc,SAAQ,KAAK;IAC7B,WAAW,CAAiB;IAErC,YAAY,UAA2B,EAAE,MAAe;QACtD,MAAM,OAAO,GAAG,yBAAyB,UAAU,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,SAAS,CAAA;QAClH,MAAM,UAAU,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;QAC/C,KAAK,CAAC,GAAG,OAAO,GAAG,UAAU,EAAE,CAAC,CAAA;QAChC,IAAI,CAAC,IAAI,GAAG,eAAe,CAAA;QAC3B,IAAI,CAAC,WAAW,GAAG,UAAU,CAAA;IAC/B,CAAC;IAED,4DAA4D;IAC5D,IAAI,UAAU;QACZ,OAAO,IAAI,CAAC,WAAW,CAAA;IACzB,CAAC;CACF"}