@lifesavertech/archguard-core 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/config.js CHANGED
@@ -68,8 +68,10 @@ function validateArchitectureConfig(config) {
68
68
  if (!layer.allowedImports.every((entry) => typeof entry === "string")) {
69
69
  throw new Error(`Invalid layer config for '${layerName}': 'allowedImports' must contain strings`);
70
70
  }
71
+ const paths = parseLayerPaths(layer, layerName);
71
72
  layers[layerName] = {
72
73
  allowedImports: layer.allowedImports,
74
+ ...(paths ? { paths } : {}),
73
75
  };
74
76
  }
75
77
  if (Object.keys(layers).length === 0) {
@@ -105,12 +107,15 @@ function validateArchitectureConfig(config) {
105
107
  "enforceLayerBoundaries",
106
108
  "enforceFeatureBoundaries",
107
109
  "noCircularLayerDeps",
110
+ "noBarrelBoundaryBypass",
108
111
  ].includes(key));
109
112
  if (unknownRuleKeys.length > 0) {
110
113
  throw new Error(`Invalid architecture config: unknown rule(s): ${unknownRuleKeys.join(", ")}`);
111
114
  }
112
115
  const enforceLayerBoundaries = Boolean(rules.enforceLayerBoundaries ?? rules.enforceFeatureBoundaries);
116
+ const patterns = parsePatternsConfig(rawConfig.patterns, layerNames);
113
117
  return {
118
+ patterns,
114
119
  layers,
115
120
  uiLayers,
116
121
  rules: {
@@ -119,9 +124,97 @@ function validateArchitectureConfig(config) {
119
124
  enforceLayerBoundaries,
120
125
  enforceFeatureBoundaries: Boolean(rules.enforceFeatureBoundaries),
121
126
  noCircularLayerDeps: Boolean(rules.noCircularLayerDeps),
127
+ noBarrelBoundaryBypass: rules.noBarrelBoundaryBypass === undefined
128
+ ? enforceLayerBoundaries
129
+ : Boolean(rules.noBarrelBoundaryBypass),
122
130
  },
123
131
  };
124
132
  }
133
+ function parsePatternsConfig(rawPatterns, layerNames) {
134
+ if (rawPatterns === undefined) {
135
+ return undefined;
136
+ }
137
+ if (!rawPatterns || typeof rawPatterns !== "object") {
138
+ throw new Error("Invalid architecture config: 'patterns' must be an object");
139
+ }
140
+ const patterns = rawPatterns;
141
+ const result = {};
142
+ if (patterns.root !== undefined) {
143
+ if (typeof patterns.root !== "string" || patterns.root.trim().length === 0) {
144
+ throw new Error("Invalid architecture config: 'patterns.root' must be a non-empty string");
145
+ }
146
+ result.root = patterns.root.trim();
147
+ }
148
+ if (patterns.ignore !== undefined) {
149
+ if (!Array.isArray(patterns.ignore)) {
150
+ throw new Error("Invalid architecture config: 'patterns.ignore' must be an array of strings");
151
+ }
152
+ result.ignore = patterns.ignore.map((entry, index) => {
153
+ if (typeof entry !== "string" || entry.trim().length === 0) {
154
+ throw new Error(`Invalid architecture config: 'patterns.ignore[${index}]' must be a non-empty string`);
155
+ }
156
+ return entry.trim();
157
+ });
158
+ }
159
+ if (patterns.layers !== undefined) {
160
+ if (!patterns.layers || typeof patterns.layers !== "object") {
161
+ throw new Error("Invalid architecture config: 'patterns.layers' must be an object");
162
+ }
163
+ result.layers = {};
164
+ for (const [layerName, layerPatterns] of Object.entries(patterns.layers)) {
165
+ if (!layerNames.has(layerName)) {
166
+ throw new Error(`Invalid architecture config: patterns.layers references undefined layer '${layerName}'`);
167
+ }
168
+ if (!layerPatterns || typeof layerPatterns !== "object") {
169
+ throw new Error(`Invalid architecture config: patterns.layers.${layerName} must be an object`);
170
+ }
171
+ const rawLayerPatterns = layerPatterns;
172
+ const parsed = {};
173
+ if (rawLayerPatterns.include !== undefined) {
174
+ parsed.include = parsePatternList(rawLayerPatterns.include, `patterns.layers.${layerName}.include`);
175
+ }
176
+ if (rawLayerPatterns.exclude !== undefined) {
177
+ parsed.exclude = parsePatternList(rawLayerPatterns.exclude, `patterns.layers.${layerName}.exclude`);
178
+ }
179
+ result.layers[layerName] = parsed;
180
+ }
181
+ }
182
+ return Object.keys(result).length > 0 ? result : undefined;
183
+ }
184
+ function parsePatternList(raw, fieldName) {
185
+ if (!Array.isArray(raw)) {
186
+ throw new Error(`Invalid architecture config: '${fieldName}' must be an array of strings`);
187
+ }
188
+ return raw.map((entry, index) => {
189
+ if (typeof entry !== "string" || entry.trim().length === 0) {
190
+ throw new Error(`Invalid architecture config: '${fieldName}[${index}]' must be a non-empty string`);
191
+ }
192
+ return entry.trim();
193
+ });
194
+ }
195
+ function parseLayerPaths(layer, layerName) {
196
+ const raw = layer.paths ?? layer.path;
197
+ if (raw === undefined) {
198
+ return undefined;
199
+ }
200
+ if (typeof raw === "string") {
201
+ const trimmed = raw.trim();
202
+ if (trimmed.length === 0) {
203
+ throw new Error(`Invalid layer config for '${layerName}': 'path' must be a non-empty string`);
204
+ }
205
+ return [trimmed];
206
+ }
207
+ if (Array.isArray(raw)) {
208
+ const paths = raw.map((entry, index) => {
209
+ if (typeof entry !== "string" || entry.trim().length === 0) {
210
+ throw new Error(`Invalid layer config for '${layerName}': 'paths[${index}]' must be a non-empty string`);
211
+ }
212
+ return entry.trim();
213
+ });
214
+ return paths;
215
+ }
216
+ throw new Error(`Invalid layer config for '${layerName}': 'path'/'paths' must be a string or array of strings`);
217
+ }
125
218
  function findArchitectureConfig(startPath) {
126
219
  let currentPath = path.resolve(startPath);
127
220
  while (true) {
package/dist/diff.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ /** Git diff helpers and import-graph expansion for PR-scoped scans. */
2
+ import type { ImportGraph } from "./index";
3
+ import type { Violation } from "./violation";
4
+ export interface DiffScanOptions {
5
+ cwd: string;
6
+ scanRoot: string;
7
+ baseRef?: string;
8
+ /** Test hook: skip git and use explicit changed paths (absolute or scan-root relative). */
9
+ changedFiles?: string[];
10
+ }
11
+ export declare function findGitRoot(startDir: string): string | null;
12
+ export declare function resolveDiffBaseRef(cwd: string, requested?: string): string;
13
+ export declare function getChangedFiles(options: DiffScanOptions): string[];
14
+ export declare function expandAffectedFiles(changedFiles: string[], importGraph: ImportGraph): Set<string>;
15
+ export declare function filterViolationsForDiff(violations: Violation[], affectedFiles: Set<string>): Violation[];
16
+ export declare function resolveDiffAffectedFiles(options: DiffScanOptions, importGraph: ImportGraph): {
17
+ changedFiles: string[];
18
+ affectedFiles: Set<string>;
19
+ };
package/dist/diff.js ADDED
@@ -0,0 +1,199 @@
1
+ "use strict";
2
+ /** Git diff helpers and import-graph expansion for PR-scoped scans. */
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.findGitRoot = findGitRoot;
38
+ exports.resolveDiffBaseRef = resolveDiffBaseRef;
39
+ exports.getChangedFiles = getChangedFiles;
40
+ exports.expandAffectedFiles = expandAffectedFiles;
41
+ exports.filterViolationsForDiff = filterViolationsForDiff;
42
+ exports.resolveDiffAffectedFiles = resolveDiffAffectedFiles;
43
+ const node_child_process_1 = require("node:child_process");
44
+ const fs = __importStar(require("node:fs"));
45
+ const path = __importStar(require("node:path"));
46
+ const TYPESCRIPT_EXTENSIONS = new Set([".ts", ".tsx"]);
47
+ function findGitRoot(startDir) {
48
+ let current = path.resolve(startDir);
49
+ while (true) {
50
+ if (fs.existsSync(path.join(current, ".git"))) {
51
+ return current;
52
+ }
53
+ const parent = path.dirname(current);
54
+ if (parent === current) {
55
+ return null;
56
+ }
57
+ current = parent;
58
+ }
59
+ }
60
+ function resolveDiffBaseRef(cwd, requested) {
61
+ if (requested && requested.trim().length > 0) {
62
+ return requested.trim();
63
+ }
64
+ const candidates = ["origin/main", "main", "origin/master", "master"];
65
+ for (const candidate of candidates) {
66
+ if (gitRefExists(cwd, candidate)) {
67
+ return candidate;
68
+ }
69
+ }
70
+ return "HEAD~1";
71
+ }
72
+ function gitRefExists(cwd, ref) {
73
+ try {
74
+ (0, node_child_process_1.execFileSync)("git", ["rev-parse", "--verify", ref], {
75
+ cwd,
76
+ encoding: "utf-8",
77
+ stdio: ["ignore", "ignore", "ignore"],
78
+ });
79
+ return true;
80
+ }
81
+ catch {
82
+ return false;
83
+ }
84
+ }
85
+ function getChangedFiles(options) {
86
+ if (options.changedFiles && options.changedFiles.length > 0) {
87
+ return normalizeChangedPaths(options.changedFiles, options.scanRoot);
88
+ }
89
+ const gitRoot = findGitRoot(options.cwd);
90
+ if (!gitRoot) {
91
+ throw new Error("Diff mode requires a git repository. Run without --diff for a full scan, or initialize git first.");
92
+ }
93
+ const baseRef = resolveDiffBaseRef(gitRoot, options.baseRef);
94
+ const relativeScanRoot = path.relative(gitRoot, options.scanRoot);
95
+ const scanPrefix = relativeScanRoot === "" || relativeScanRoot === "." ? "" : `${relativeScanRoot}${path.sep}`;
96
+ const rawPaths = runGitDiffNameOnly(gitRoot, baseRef);
97
+ const absolutePaths = [];
98
+ for (const rawPath of rawPaths) {
99
+ if (scanPrefix && !rawPath.startsWith(scanPrefix)) {
100
+ continue;
101
+ }
102
+ const absolutePath = path.resolve(gitRoot, rawPath);
103
+ if (!absolutePath.startsWith(path.resolve(options.scanRoot))) {
104
+ continue;
105
+ }
106
+ if (!isTypeScriptSource(absolutePath)) {
107
+ continue;
108
+ }
109
+ if (!fs.existsSync(absolutePath)) {
110
+ continue;
111
+ }
112
+ absolutePaths.push(absolutePath);
113
+ }
114
+ return absolutePaths;
115
+ }
116
+ function runGitDiffNameOnly(gitRoot, baseRef) {
117
+ const attempts = [
118
+ ["diff", "--name-only", "--diff-filter=ACMR", `${baseRef}...HEAD`],
119
+ ["diff", "--name-only", "--diff-filter=ACMR", baseRef, "HEAD"],
120
+ ["diff", "--name-only", "--diff-filter=ACMR", "HEAD"],
121
+ ["diff", "--name-only", "--diff-filter=ACMR", "--cached"],
122
+ ];
123
+ for (const args of attempts) {
124
+ try {
125
+ const output = (0, node_child_process_1.execFileSync)("git", args, {
126
+ cwd: gitRoot,
127
+ encoding: "utf-8",
128
+ }).trim();
129
+ if (output.length === 0) {
130
+ continue;
131
+ }
132
+ return output.split("\n").map((line) => line.trim()).filter(Boolean);
133
+ }
134
+ catch {
135
+ continue;
136
+ }
137
+ }
138
+ return [];
139
+ }
140
+ function normalizeChangedPaths(changedFiles, scanRoot) {
141
+ const resolvedScanRoot = path.resolve(scanRoot);
142
+ return changedFiles
143
+ .map((filePath) => (path.isAbsolute(filePath) ? filePath : path.resolve(resolvedScanRoot, filePath)))
144
+ .filter((filePath) => filePath.startsWith(resolvedScanRoot))
145
+ .filter((filePath) => isTypeScriptSource(filePath) && fs.existsSync(filePath));
146
+ }
147
+ function isTypeScriptSource(filePath) {
148
+ return TYPESCRIPT_EXTENSIONS.has(path.extname(filePath));
149
+ }
150
+ function expandAffectedFiles(changedFiles, importGraph) {
151
+ const affected = new Set(changedFiles);
152
+ const reverseImporters = buildReverseImportIndex(importGraph);
153
+ for (const file of changedFiles) {
154
+ for (const imported of importGraph[file] ?? []) {
155
+ affected.add(imported);
156
+ }
157
+ for (const importer of reverseImporters.get(file) ?? []) {
158
+ affected.add(importer);
159
+ }
160
+ }
161
+ return affected;
162
+ }
163
+ function buildReverseImportIndex(importGraph) {
164
+ const reverse = new Map();
165
+ for (const [source, imports] of Object.entries(importGraph)) {
166
+ for (const target of imports) {
167
+ const existing = reverse.get(target) ?? [];
168
+ existing.push(source);
169
+ reverse.set(target, existing);
170
+ }
171
+ }
172
+ return reverse;
173
+ }
174
+ function filterViolationsForDiff(violations, affectedFiles) {
175
+ return violations.filter((violation) => {
176
+ if (affectedFiles.has(violation.file)) {
177
+ return true;
178
+ }
179
+ if (violation.rule === "no-circular-layer-deps") {
180
+ return violationTouchesAffectedFiles(violation.message, affectedFiles);
181
+ }
182
+ return false;
183
+ });
184
+ }
185
+ function violationTouchesAffectedFiles(message, affectedFiles) {
186
+ for (const file of affectedFiles) {
187
+ const relative = file.split(path.sep).join("/");
188
+ const basename = path.basename(file);
189
+ if (message.includes(relative) || message.includes(basename)) {
190
+ return true;
191
+ }
192
+ }
193
+ return false;
194
+ }
195
+ function resolveDiffAffectedFiles(options, importGraph) {
196
+ const changedFiles = getChangedFiles(options);
197
+ const affectedFiles = expandAffectedFiles(changedFiles, importGraph);
198
+ return { changedFiles, affectedFiles };
199
+ }
@@ -0,0 +1,19 @@
1
+ /** Trace barrel re-exports to detect layer boundary bypasses. */
2
+ import type { ArchitectureConfig } from "./index";
3
+ import type { ImportResolver } from "./resolver";
4
+ export interface BarrelLeak {
5
+ leakedLayer: string;
6
+ reexportFile: string;
7
+ chain: string[];
8
+ }
9
+ export declare function isBarrelBoundaryBypassEnabled(rules: ArchitectureConfig["rules"]): boolean;
10
+ export declare function collectBarrelLeaks(options: {
11
+ barrelPath: string;
12
+ importerLayer: string;
13
+ allowedLayers: Set<string>;
14
+ architecture: ArchitectureConfig;
15
+ projectRoot: string;
16
+ resolver: ImportResolver;
17
+ visited?: Set<string>;
18
+ }): BarrelLeak[];
19
+ export declare function formatBarrelChain(chain: string[], projectRoot?: string): string;
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ /** Trace barrel re-exports to detect layer boundary bypasses. */
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.isBarrelBoundaryBypassEnabled = isBarrelBoundaryBypassEnabled;
38
+ exports.collectBarrelLeaks = collectBarrelLeaks;
39
+ exports.formatBarrelChain = formatBarrelChain;
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ const layers_1 = require("./layers");
43
+ const parser_1 = require("./parser");
44
+ function isBarrelBoundaryBypassEnabled(rules) {
45
+ if (!rules.enforceLayerBoundaries && !rules.enforceFeatureBoundaries) {
46
+ return false;
47
+ }
48
+ if (rules.noBarrelBoundaryBypass === false) {
49
+ return false;
50
+ }
51
+ return rules.noBarrelBoundaryBypass ?? true;
52
+ }
53
+ function collectBarrelLeaks(options) {
54
+ const visited = options.visited ?? new Set();
55
+ if (visited.has(options.barrelPath)) {
56
+ return [];
57
+ }
58
+ visited.add(options.barrelPath);
59
+ if (!fs.existsSync(options.barrelPath)) {
60
+ return [];
61
+ }
62
+ const source = fs.readFileSync(options.barrelPath, "utf-8");
63
+ const ast = (0, parser_1.parseTypeScript)(source, options.barrelPath);
64
+ const reexports = (0, parser_1.extractReexports)(ast);
65
+ const leaks = [];
66
+ for (const reexport of reexports) {
67
+ if (reexport.isTypeOnly) {
68
+ continue;
69
+ }
70
+ const resolved = options.resolver.resolve(reexport.from, options.barrelPath);
71
+ if (!resolved) {
72
+ continue;
73
+ }
74
+ const targetLayer = (0, layers_1.detectLayerFromPath)(resolved, options.architecture, options.projectRoot);
75
+ if (targetLayer &&
76
+ targetLayer !== options.importerLayer &&
77
+ !options.allowedLayers.has(targetLayer)) {
78
+ leaks.push({
79
+ leakedLayer: targetLayer,
80
+ reexportFile: resolved,
81
+ chain: [options.barrelPath, resolved],
82
+ });
83
+ }
84
+ const nested = collectBarrelLeaks({
85
+ ...options,
86
+ barrelPath: resolved,
87
+ visited,
88
+ });
89
+ leaks.push(...nested);
90
+ }
91
+ return dedupeLeaks(leaks);
92
+ }
93
+ function dedupeLeaks(leaks) {
94
+ const seen = new Set();
95
+ const unique = [];
96
+ for (const leak of leaks) {
97
+ const key = `${leak.leakedLayer}:${leak.reexportFile}`;
98
+ if (seen.has(key)) {
99
+ continue;
100
+ }
101
+ seen.add(key);
102
+ unique.push(leak);
103
+ }
104
+ return unique;
105
+ }
106
+ function formatBarrelChain(chain, projectRoot) {
107
+ return chain
108
+ .map((filePath) => formatProjectRelativePath(filePath, projectRoot))
109
+ .join(" → ");
110
+ }
111
+ function formatProjectRelativePath(filePath, projectRoot) {
112
+ if (!projectRoot) {
113
+ return filePath.replace(/\\/g, "/");
114
+ }
115
+ const relativePath = path.relative(projectRoot, filePath);
116
+ if (relativePath && !relativePath.startsWith("..") && !path.isAbsolute(relativePath)) {
117
+ return relativePath.replace(/\\/g, "/");
118
+ }
119
+ return filePath.replace(/\\/g, "/");
120
+ }
package/dist/index.d.ts CHANGED
@@ -1,11 +1,7 @@
1
1
  /** Core rule engine types and abstractions. */
2
- export interface Violation {
3
- file: string;
4
- line: number;
5
- column: number;
6
- message: string;
7
- rule: string;
8
- }
2
+ import type { Violation } from "./violation";
3
+ export type { Violation, ViolationContext } from "./violation";
4
+ export { createViolationId, enrichViolation, enrichViolations } from "./violation";
9
5
  export interface ImportGraph {
10
6
  [filePath: string]: string[];
11
7
  }
@@ -33,7 +29,20 @@ export interface Rule {
33
29
  description: string;
34
30
  execute(context: RuleContext): RuleResult;
35
31
  }
32
+ export interface LayerPatternConfig {
33
+ include?: string[];
34
+ exclude?: string[];
35
+ }
36
+ export interface PatternsConfig {
37
+ /** Base directory for pattern matching (relative to scan root). Default: "." */
38
+ root?: string;
39
+ /** Glob patterns skipped during scans (e.g. tests, mocks). */
40
+ ignore?: string[];
41
+ /** Per-layer include/exclude globs (relative to patterns.root). */
42
+ layers?: Record<string, LayerPatternConfig>;
43
+ }
36
44
  export interface ArchitectureConfig {
45
+ patterns?: PatternsConfig;
37
46
  layers: Record<string, LayerConfig>;
38
47
  /** Layer folder names that receive React UI behavior rules. Defaults to `ui` when present. */
39
48
  uiLayers?: string[];
@@ -45,10 +54,14 @@ export interface ArchitectureConfig {
45
54
  /** @deprecated Use `enforceLayerBoundaries`. Kept for backward compatibility. */
46
55
  enforceFeatureBoundaries?: boolean;
47
56
  noCircularLayerDeps?: boolean;
57
+ /** Detect illegal layer access through barrel re-exports. Default: on when boundaries are enforced. */
58
+ noBarrelBoundaryBypass?: boolean;
48
59
  };
49
60
  }
50
61
  export interface LayerConfig {
51
62
  allowedImports: string[];
63
+ /** Folder paths or globs relative to the scan root (e.g. src/components, src/db/**). */
64
+ paths?: string[];
52
65
  }
53
66
  export declare class RuleEngine {
54
67
  private rules;
@@ -56,10 +69,15 @@ export declare class RuleEngine {
56
69
  executeRules(context: RuleContext): RuleResult;
57
70
  getRegisteredRules(): Rule[];
58
71
  }
59
- export { parseTypeScript, extractImports } from "./parser";
72
+ export { parseTypeScript, extractImports, extractReexports } from "./parser";
73
+ export type { ReexportDeclaration } from "./parser";
60
74
  export { loadArchitectureConfig, validateArchitectureConfig, findArchitectureConfig } from "./config";
61
75
  export { createImportResolver } from "./resolver";
62
76
  export type { ImportResolver } from "./resolver";
63
- export { detectLayerFromPath, fileBelongsToUiLayer, isLayerBoundaryEnforcementEnabled, resolveUiLayers, } from "./layers";
77
+ export { countFilesMatchingLayers, detectLayerFromPath, fileBelongsToUiLayer, isLayerBoundaryEnforcementEnabled, resolveUiLayers, shouldIgnorePath, } from "./layers";
64
78
  export { discoverWorkspacePackages, resolveWorkspacePackageEntry } from "./workspace";
65
79
  export type { WorkspacePackage } from "./workspace";
80
+ export { expandAffectedFiles, filterViolationsForDiff, findGitRoot, getChangedFiles, resolveDiffAffectedFiles, resolveDiffBaseRef, } from "./diff";
81
+ export type { DiffScanOptions } from "./diff";
82
+ export { collectBarrelLeaks, formatBarrelChain, isBarrelBoundaryBypassEnabled, } from "./exports";
83
+ export type { BarrelLeak } from "./exports";
package/dist/index.js CHANGED
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
2
  /** Core rule engine types and abstractions. */
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.resolveWorkspacePackageEntry = exports.discoverWorkspacePackages = exports.resolveUiLayers = exports.isLayerBoundaryEnforcementEnabled = exports.fileBelongsToUiLayer = exports.detectLayerFromPath = exports.createImportResolver = exports.findArchitectureConfig = exports.validateArchitectureConfig = exports.loadArchitectureConfig = exports.extractImports = exports.parseTypeScript = exports.RuleEngine = void 0;
4
+ exports.isBarrelBoundaryBypassEnabled = exports.formatBarrelChain = exports.collectBarrelLeaks = exports.resolveDiffBaseRef = exports.resolveDiffAffectedFiles = exports.getChangedFiles = exports.findGitRoot = exports.filterViolationsForDiff = exports.expandAffectedFiles = exports.resolveWorkspacePackageEntry = exports.discoverWorkspacePackages = exports.shouldIgnorePath = exports.resolveUiLayers = exports.isLayerBoundaryEnforcementEnabled = exports.fileBelongsToUiLayer = exports.detectLayerFromPath = exports.countFilesMatchingLayers = exports.createImportResolver = exports.findArchitectureConfig = exports.validateArchitectureConfig = exports.loadArchitectureConfig = exports.extractReexports = exports.extractImports = exports.parseTypeScript = exports.RuleEngine = exports.enrichViolations = exports.enrichViolation = exports.createViolationId = void 0;
5
+ var violation_1 = require("./violation");
6
+ Object.defineProperty(exports, "createViolationId", { enumerable: true, get: function () { return violation_1.createViolationId; } });
7
+ Object.defineProperty(exports, "enrichViolation", { enumerable: true, get: function () { return violation_1.enrichViolation; } });
8
+ Object.defineProperty(exports, "enrichViolations", { enumerable: true, get: function () { return violation_1.enrichViolations; } });
5
9
  class RuleEngine {
6
10
  constructor() {
7
11
  this.rules = [];
@@ -28,6 +32,7 @@ exports.RuleEngine = RuleEngine;
28
32
  var parser_1 = require("./parser");
29
33
  Object.defineProperty(exports, "parseTypeScript", { enumerable: true, get: function () { return parser_1.parseTypeScript; } });
30
34
  Object.defineProperty(exports, "extractImports", { enumerable: true, get: function () { return parser_1.extractImports; } });
35
+ Object.defineProperty(exports, "extractReexports", { enumerable: true, get: function () { return parser_1.extractReexports; } });
31
36
  var config_1 = require("./config");
32
37
  Object.defineProperty(exports, "loadArchitectureConfig", { enumerable: true, get: function () { return config_1.loadArchitectureConfig; } });
33
38
  Object.defineProperty(exports, "validateArchitectureConfig", { enumerable: true, get: function () { return config_1.validateArchitectureConfig; } });
@@ -35,10 +40,23 @@ Object.defineProperty(exports, "findArchitectureConfig", { enumerable: true, get
35
40
  var resolver_1 = require("./resolver");
36
41
  Object.defineProperty(exports, "createImportResolver", { enumerable: true, get: function () { return resolver_1.createImportResolver; } });
37
42
  var layers_1 = require("./layers");
43
+ Object.defineProperty(exports, "countFilesMatchingLayers", { enumerable: true, get: function () { return layers_1.countFilesMatchingLayers; } });
38
44
  Object.defineProperty(exports, "detectLayerFromPath", { enumerable: true, get: function () { return layers_1.detectLayerFromPath; } });
39
45
  Object.defineProperty(exports, "fileBelongsToUiLayer", { enumerable: true, get: function () { return layers_1.fileBelongsToUiLayer; } });
40
46
  Object.defineProperty(exports, "isLayerBoundaryEnforcementEnabled", { enumerable: true, get: function () { return layers_1.isLayerBoundaryEnforcementEnabled; } });
41
47
  Object.defineProperty(exports, "resolveUiLayers", { enumerable: true, get: function () { return layers_1.resolveUiLayers; } });
48
+ Object.defineProperty(exports, "shouldIgnorePath", { enumerable: true, get: function () { return layers_1.shouldIgnorePath; } });
42
49
  var workspace_1 = require("./workspace");
43
50
  Object.defineProperty(exports, "discoverWorkspacePackages", { enumerable: true, get: function () { return workspace_1.discoverWorkspacePackages; } });
44
51
  Object.defineProperty(exports, "resolveWorkspacePackageEntry", { enumerable: true, get: function () { return workspace_1.resolveWorkspacePackageEntry; } });
52
+ var diff_1 = require("./diff");
53
+ Object.defineProperty(exports, "expandAffectedFiles", { enumerable: true, get: function () { return diff_1.expandAffectedFiles; } });
54
+ Object.defineProperty(exports, "filterViolationsForDiff", { enumerable: true, get: function () { return diff_1.filterViolationsForDiff; } });
55
+ Object.defineProperty(exports, "findGitRoot", { enumerable: true, get: function () { return diff_1.findGitRoot; } });
56
+ Object.defineProperty(exports, "getChangedFiles", { enumerable: true, get: function () { return diff_1.getChangedFiles; } });
57
+ Object.defineProperty(exports, "resolveDiffAffectedFiles", { enumerable: true, get: function () { return diff_1.resolveDiffAffectedFiles; } });
58
+ Object.defineProperty(exports, "resolveDiffBaseRef", { enumerable: true, get: function () { return diff_1.resolveDiffBaseRef; } });
59
+ var exports_1 = require("./exports");
60
+ Object.defineProperty(exports, "collectBarrelLeaks", { enumerable: true, get: function () { return exports_1.collectBarrelLeaks; } });
61
+ Object.defineProperty(exports, "formatBarrelChain", { enumerable: true, get: function () { return exports_1.formatBarrelChain; } });
62
+ Object.defineProperty(exports, "isBarrelBoundaryBypassEnabled", { enumerable: true, get: function () { return exports_1.isBarrelBoundaryBypassEnabled; } });
package/dist/layers.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  /** Layer detection and UI targeting helpers shared across rule packs. */
2
- import { ArchitectureConfig, LayerConfig } from "./index";
3
- export declare function detectLayerFromPath(filePath: string, layers: Record<string, LayerConfig>): string | null;
2
+ import { ArchitectureConfig } from "./index";
3
+ export declare function detectLayerFromPath(filePath: string, architecture: ArchitectureConfig, projectRoot?: string): string | null;
4
+ export declare function countFilesMatchingLayers(filePaths: string[], architecture: ArchitectureConfig, projectRoot: string): number;
5
+ export declare function shouldIgnorePath(filePath: string, architecture: ArchitectureConfig, projectRoot: string): boolean;
4
6
  export declare function resolveUiLayers(config: ArchitectureConfig): Set<string>;
5
- export declare function fileBelongsToUiLayer(filePath: string, config: ArchitectureConfig): boolean;
7
+ export declare function fileBelongsToUiLayer(filePath: string, config: ArchitectureConfig, projectRoot?: string): boolean;
6
8
  export declare function isLayerBoundaryEnforcementEnabled(rules: ArchitectureConfig["rules"]): boolean;
package/dist/layers.js CHANGED
@@ -1,22 +1,110 @@
1
1
  "use strict";
2
2
  /** Layer detection and UI targeting helpers shared across rule packs. */
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
3
36
  Object.defineProperty(exports, "__esModule", { value: true });
4
37
  exports.detectLayerFromPath = detectLayerFromPath;
38
+ exports.countFilesMatchingLayers = countFilesMatchingLayers;
39
+ exports.shouldIgnorePath = shouldIgnorePath;
5
40
  exports.resolveUiLayers = resolveUiLayers;
6
41
  exports.fileBelongsToUiLayer = fileBelongsToUiLayer;
7
42
  exports.isLayerBoundaryEnforcementEnabled = isLayerBoundaryEnforcementEnabled;
43
+ const path = __importStar(require("path"));
8
44
  const DEFAULT_UI_LAYER = "ui";
9
- function detectLayerFromPath(filePath, layers) {
10
- const normalizedPath = filePath.replace(/\\/g, "/");
11
- const segments = normalizedPath.split("/").filter(Boolean);
12
- const layerNames = new Set(Object.keys(layers));
45
+ function detectLayerFromPath(filePath, architecture, projectRoot) {
46
+ const patternsRoot = resolvePatternsRoot(projectRoot, architecture.patterns);
47
+ const relativePath = projectRoot
48
+ ? normalizeRelativePath(patternsRoot, filePath)
49
+ : filePath.replace(/\\/g, "/");
50
+ const absoluteNormalized = filePath.replace(/\\/g, "/");
51
+ const layerPatterns = resolveLayerPatterns(architecture);
52
+ let bestPathMatch = null;
53
+ for (const [layerName, patternConfig] of Object.entries(layerPatterns)) {
54
+ if (patternConfig.include.length === 0) {
55
+ continue;
56
+ }
57
+ if (patternConfig.exclude.some((pattern) => pathMatchesPattern(relativePath, absoluteNormalized, pattern))) {
58
+ continue;
59
+ }
60
+ for (const pattern of patternConfig.include) {
61
+ if (pathMatchesPattern(relativePath, absoluteNormalized, pattern)) {
62
+ const specificity = pattern.length;
63
+ if (!bestPathMatch || specificity > bestPathMatch.specificity) {
64
+ bestPathMatch = { layer: layerName, specificity };
65
+ }
66
+ }
67
+ }
68
+ }
69
+ if (bestPathMatch) {
70
+ return bestPathMatch.layer;
71
+ }
72
+ const layerNames = new Set(Object.keys(architecture.layers));
73
+ const segments = absoluteNormalized.split("/").filter(Boolean);
13
74
  for (const segment of segments) {
14
- if (layerNames.has(segment)) {
15
- return segment;
75
+ if (!layerNames.has(segment)) {
76
+ continue;
16
77
  }
78
+ const patternConfig = layerPatterns[segment];
79
+ if (patternConfig?.exclude.length) {
80
+ const excluded = patternConfig.exclude.some((pattern) => pathMatchesPattern(relativePath, absoluteNormalized, pattern));
81
+ if (excluded) {
82
+ continue;
83
+ }
84
+ }
85
+ return segment;
17
86
  }
18
87
  return null;
19
88
  }
89
+ function countFilesMatchingLayers(filePaths, architecture, projectRoot) {
90
+ let count = 0;
91
+ for (const filePath of filePaths) {
92
+ if (detectLayerFromPath(filePath, architecture, projectRoot)) {
93
+ count += 1;
94
+ }
95
+ }
96
+ return count;
97
+ }
98
+ function shouldIgnorePath(filePath, architecture, projectRoot) {
99
+ const patterns = architecture.patterns?.ignore;
100
+ if (!patterns?.length) {
101
+ return false;
102
+ }
103
+ const patternsRoot = resolvePatternsRoot(projectRoot, architecture.patterns);
104
+ const relativePath = normalizeRelativePath(patternsRoot, filePath);
105
+ const absoluteNormalized = filePath.replace(/\\/g, "/");
106
+ return patterns.some((pattern) => pathMatchesPattern(relativePath, absoluteNormalized, pattern));
107
+ }
20
108
  function resolveUiLayers(config) {
21
109
  if (config.uiLayers && config.uiLayers.length > 0) {
22
110
  return new Set(config.uiLayers);
@@ -26,14 +114,85 @@ function resolveUiLayers(config) {
26
114
  }
27
115
  return new Set();
28
116
  }
29
- function fileBelongsToUiLayer(filePath, config) {
117
+ function fileBelongsToUiLayer(filePath, config, projectRoot) {
30
118
  const uiLayers = resolveUiLayers(config);
31
119
  if (uiLayers.size === 0) {
32
120
  return false;
33
121
  }
34
- const layerName = detectLayerFromPath(filePath, config.layers);
122
+ const layerName = detectLayerFromPath(filePath, config, projectRoot);
35
123
  return layerName !== null && uiLayers.has(layerName);
36
124
  }
37
125
  function isLayerBoundaryEnforcementEnabled(rules) {
38
126
  return Boolean(rules.enforceLayerBoundaries || rules.enforceFeatureBoundaries);
39
127
  }
128
+ function resolvePatternsRoot(projectRoot, patterns) {
129
+ if (!projectRoot) {
130
+ return ".";
131
+ }
132
+ const root = patterns?.root?.trim();
133
+ if (!root || root === ".") {
134
+ return projectRoot;
135
+ }
136
+ return path.resolve(projectRoot, root);
137
+ }
138
+ function resolveLayerPatterns(architecture) {
139
+ const resolved = {};
140
+ for (const layerName of Object.keys(architecture.layers)) {
141
+ resolved[layerName] = { include: [], exclude: [] };
142
+ }
143
+ if (architecture.patterns?.layers) {
144
+ for (const [layerName, patternConfig] of Object.entries(architecture.patterns.layers)) {
145
+ if (!resolved[layerName]) {
146
+ continue;
147
+ }
148
+ resolved[layerName].include.push(...(patternConfig.include ?? []).map(normalizePattern));
149
+ resolved[layerName].exclude.push(...(patternConfig.exclude ?? []).map(normalizePattern));
150
+ }
151
+ }
152
+ for (const [layerName, layerConfig] of Object.entries(architecture.layers)) {
153
+ if (!layerConfig.paths?.length) {
154
+ continue;
155
+ }
156
+ resolved[layerName].include.push(...layerConfig.paths.map(normalizePattern));
157
+ }
158
+ return resolved;
159
+ }
160
+ function normalizePattern(pattern) {
161
+ const trimmed = pattern.replace(/\\/g, "/").replace(/\/+$/, "");
162
+ if (trimmed.includes("*")) {
163
+ return trimmed;
164
+ }
165
+ if (trimmed.includes("/")) {
166
+ return `${trimmed}/**`;
167
+ }
168
+ return trimmed;
169
+ }
170
+ function normalizeRelativePath(projectRoot, filePath) {
171
+ const relative = path.relative(projectRoot, filePath).replace(/\\/g, "/");
172
+ if (!relative || relative.startsWith("..")) {
173
+ return filePath.replace(/\\/g, "/");
174
+ }
175
+ return relative;
176
+ }
177
+ function pathMatchesPattern(relativePath, absoluteNormalized, pattern) {
178
+ const normalized = pattern.replace(/\\/g, "/").replace(/\/+$/, "");
179
+ if (normalized.includes("*")) {
180
+ const regexSource = "^" +
181
+ normalized
182
+ .split("/")
183
+ .map((segment) => {
184
+ if (segment === "**") {
185
+ return ".*";
186
+ }
187
+ return segment.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]*");
188
+ })
189
+ .join("/") +
190
+ "(/.*)?$";
191
+ return new RegExp(regexSource).test(relativePath);
192
+ }
193
+ if (normalized.includes("/")) {
194
+ return relativePath === normalized || relativePath.startsWith(`${normalized}/`);
195
+ }
196
+ const segments = absoluteNormalized.split("/").filter(Boolean);
197
+ return segments.includes(normalized);
198
+ }
package/dist/parser.d.ts CHANGED
@@ -3,3 +3,10 @@ import * as ts from "typescript";
3
3
  import { ImportInfo } from "./index";
4
4
  export declare function parseTypeScript(sourceCode: string, fileName: string): ts.SourceFile;
5
5
  export declare function extractImports(sourceFile: ts.SourceFile): ImportInfo[];
6
+ export interface ReexportDeclaration {
7
+ from: string;
8
+ exportAll: boolean;
9
+ namedSpecifiers: string[];
10
+ isTypeOnly: boolean;
11
+ }
12
+ export declare function extractReexports(sourceFile: ts.SourceFile): ReexportDeclaration[];
package/dist/parser.js CHANGED
@@ -36,6 +36,7 @@ var __importStar = (this && this.__importStar) || (function () {
36
36
  Object.defineProperty(exports, "__esModule", { value: true });
37
37
  exports.parseTypeScript = parseTypeScript;
38
38
  exports.extractImports = extractImports;
39
+ exports.extractReexports = extractReexports;
39
40
  const ts = __importStar(require("typescript"));
40
41
  function parseTypeScript(sourceCode, fileName) {
41
42
  return ts.createSourceFile(fileName, sourceCode, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
@@ -77,3 +78,39 @@ function extractImports(sourceFile) {
77
78
  visit(sourceFile);
78
79
  return imports;
79
80
  }
81
+ function extractReexports(sourceFile) {
82
+ const reexports = [];
83
+ function visit(node) {
84
+ if (!ts.isExportDeclaration(node) || !node.moduleSpecifier) {
85
+ ts.forEachChild(node, visit);
86
+ return;
87
+ }
88
+ if (!ts.isStringLiteral(node.moduleSpecifier)) {
89
+ ts.forEachChild(node, visit);
90
+ return;
91
+ }
92
+ const namedSpecifiers = [];
93
+ let exportAll = false;
94
+ if (!node.exportClause) {
95
+ exportAll = true;
96
+ }
97
+ else if (ts.isNamespaceExport(node.exportClause)) {
98
+ exportAll = true;
99
+ namedSpecifiers.push(node.exportClause.name.text);
100
+ }
101
+ else if (ts.isNamedExports(node.exportClause)) {
102
+ for (const element of node.exportClause.elements) {
103
+ namedSpecifiers.push(element.name.text);
104
+ }
105
+ }
106
+ reexports.push({
107
+ from: node.moduleSpecifier.text,
108
+ exportAll,
109
+ namedSpecifiers,
110
+ isTypeOnly: node.isTypeOnly ?? false,
111
+ });
112
+ ts.forEachChild(node, visit);
113
+ }
114
+ visit(sourceFile);
115
+ return reexports;
116
+ }
@@ -0,0 +1,23 @@
1
+ /** Violation helpers — stable IDs and enrichment for agent/CI output. */
2
+ export interface ViolationContext {
3
+ sourceLayer?: string;
4
+ targetLayer?: string;
5
+ importPath?: string;
6
+ reexportChain?: string;
7
+ }
8
+ export interface Violation {
9
+ /** Stable hash for baselines, SARIF, and agent tooling. */
10
+ id?: string;
11
+ file: string;
12
+ line: number;
13
+ column: number;
14
+ message: string;
15
+ rule: string;
16
+ severity?: "error" | "warning";
17
+ /** Actionable repair hint for humans and agents. */
18
+ fix?: string;
19
+ context?: ViolationContext;
20
+ }
21
+ export declare function createViolationId(violation: Pick<Violation, "rule" | "file" | "line" | "column" | "message">): string;
22
+ export declare function enrichViolation(violation: Violation): Violation;
23
+ export declare function enrichViolations(violations: Violation[]): Violation[];
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ /** Violation helpers — stable IDs and enrichment for agent/CI output. */
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.createViolationId = createViolationId;
38
+ exports.enrichViolation = enrichViolation;
39
+ exports.enrichViolations = enrichViolations;
40
+ const crypto = __importStar(require("crypto"));
41
+ function createViolationId(violation) {
42
+ const payload = [
43
+ violation.rule,
44
+ violation.file,
45
+ String(violation.line),
46
+ String(violation.column),
47
+ violation.message,
48
+ ].join("|");
49
+ return crypto.createHash("sha256").update(payload).digest("hex").slice(0, 16);
50
+ }
51
+ function enrichViolation(violation) {
52
+ return {
53
+ ...violation,
54
+ id: violation.id ?? createViolationId(violation),
55
+ severity: violation.severity ?? "error",
56
+ };
57
+ }
58
+ function enrichViolations(violations) {
59
+ return violations.map(enrichViolation);
60
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifesavertech/archguard-core",
3
- "version": "2.0.0",
3
+ "version": "2.2.0",
4
4
  "description": "Core rule engine for ArchGuard",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -22,7 +22,7 @@
22
22
  "build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc -b --force",
23
23
  "prepack": "npm run build",
24
24
  "build:test": "node -e \"require('fs').rmSync('dist-test',{recursive:true,force:true})\" && tsc -p tsconfig.test.json",
25
- "test": "npm run build:test && node --test dist-test/index.test.js"
25
+ "test": "npm run build:test && node --test dist-test/index.test.js dist-test/diff.test.js dist-test/exports.test.js"
26
26
  },
27
27
  "dependencies": {
28
28
  "typescript": "^5.0.0",