optiprune 1.0.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 (57) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +49 -0
  3. package/dist/cache.d.ts +14 -0
  4. package/dist/cache.js +36 -0
  5. package/dist/cache.js.map +1 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +53 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/config-loader.d.ts +4 -0
  10. package/dist/config-loader.js +68 -0
  11. package/dist/config-loader.js.map +1 -0
  12. package/dist/coverage-tracker.d.ts +39 -0
  13. package/dist/coverage-tracker.js +61 -0
  14. package/dist/coverage-tracker.js.map +1 -0
  15. package/dist/fs-utils.d.ts +33 -0
  16. package/dist/fs-utils.js +334 -0
  17. package/dist/fs-utils.js.map +1 -0
  18. package/dist/graph.d.ts +28 -0
  19. package/dist/graph.js +331 -0
  20. package/dist/graph.js.map +1 -0
  21. package/dist/index.d.ts +3 -0
  22. package/dist/index.js +313 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/instrument.d.ts +1 -0
  25. package/dist/instrument.js +20 -0
  26. package/dist/instrument.js.map +1 -0
  27. package/dist/instrumentation-plugin.d.ts +6 -0
  28. package/dist/instrumentation-plugin.js +84 -0
  29. package/dist/instrumentation-plugin.js.map +1 -0
  30. package/dist/layer2.d.ts +6 -0
  31. package/dist/layer2.js +227 -0
  32. package/dist/layer2.js.map +1 -0
  33. package/dist/layer3.d.ts +7 -0
  34. package/dist/layer3.js +244 -0
  35. package/dist/layer3.js.map +1 -0
  36. package/dist/layer4.d.ts +7 -0
  37. package/dist/layer4.js +90 -0
  38. package/dist/layer4.js.map +1 -0
  39. package/dist/layer5.d.ts +6 -0
  40. package/dist/layer5.js +141 -0
  41. package/dist/layer5.js.map +1 -0
  42. package/dist/layer6.d.ts +25 -0
  43. package/dist/layer6.js +183 -0
  44. package/dist/layer6.js.map +1 -0
  45. package/dist/parser.d.ts +24 -0
  46. package/dist/parser.js +481 -0
  47. package/dist/parser.js.map +1 -0
  48. package/dist/reporters.d.ts +3 -0
  49. package/dist/reporters.js +75 -0
  50. package/dist/reporters.js.map +1 -0
  51. package/dist/types.d.ts +205 -0
  52. package/dist/types.js +11 -0
  53. package/dist/types.js.map +1 -0
  54. package/dist/workspace.d.ts +6 -0
  55. package/dist/workspace.js +114 -0
  56. package/dist/workspace.js.map +1 -0
  57. package/package.json +112 -0
package/dist/graph.js ADDED
@@ -0,0 +1,331 @@
1
+ import path from "pathe";
2
+ import { resolveDynamicPattern, resolveLocalSpecifier } from "./fs-utils.js";
3
+ function dynamicParts(rawSpecifier) {
4
+ const marker = "${…}";
5
+ const index = rawSpecifier.indexOf(marker);
6
+ if (index < 0) {
7
+ return undefined;
8
+ }
9
+ return {
10
+ prefix: rawSpecifier.slice(0, index),
11
+ suffix: rawSpecifier.slice(index + marker.length),
12
+ };
13
+ }
14
+ function resolveEdge(edge, source, knownFiles, options) {
15
+ if (edge.kind === "unknown-dynamic") {
16
+ edge.resolution = "unknown";
17
+ return;
18
+ }
19
+ if (edge.kind === "dynamic-pattern") {
20
+ const parts = dynamicParts(edge.rawSpecifier);
21
+ if (!parts) {
22
+ edge.resolution = "unknown";
23
+ return;
24
+ }
25
+ const candidates = resolveDynamicPattern(source.id, parts.prefix, parts.suffix, knownFiles);
26
+ const baseDirectory = path.resolve(path.dirname(source.id), parts.prefix || ".");
27
+ edge.dynamicPattern = {
28
+ prefix: parts.prefix,
29
+ suffix: parts.suffix,
30
+ baseDirectory,
31
+ candidates,
32
+ };
33
+ edge.resolution = candidates.length > 0 ? "resolved" : "unresolved";
34
+ return;
35
+ }
36
+ // 1. Try local resolution
37
+ let target = resolveLocalSpecifier(source.id, edge.rawSpecifier, knownFiles, options.extensions);
38
+ if (edge.rawSpecifier.startsWith(".")) {
39
+ // console.log("Resolving " + edge.rawSpecifier + " from " + source.id + " -> " + target);
40
+ }
41
+ // 2. Try Monorepo Workspace resolution
42
+ if (!target && options.monorepo) {
43
+ // Check if the specifier starts with a workspace package name
44
+ for (const [pkgName, pkg] of options.monorepo.packageMap.entries()) {
45
+ if (edge.rawSpecifier === pkgName || edge.rawSpecifier.startsWith(pkgName + '/')) {
46
+ // Resolve to the package's entry point (main/exports)
47
+ // For simplicity, we assume index.ts/js in the package root or src
48
+ const subPath = edge.rawSpecifier.slice(pkgName.length);
49
+ const pkgRoot = pkg.location;
50
+ // Try common entry points if it's just the package name
51
+ if (!subPath || subPath === '/') {
52
+ const entries = ['src/index.ts', 'src/index.js', 'index.ts', 'index.js'];
53
+ for (const e of entries) {
54
+ const entryPath = path.join(pkgRoot, e);
55
+ if (knownFiles.has(entryPath)) {
56
+ target = entryPath;
57
+ break;
58
+ }
59
+ }
60
+ }
61
+ else {
62
+ // Try resolving the sub-path
63
+ target = resolveLocalSpecifier(path.join(pkgRoot, 'package.json'), '.' + subPath, knownFiles, options.extensions);
64
+ }
65
+ if (target)
66
+ break;
67
+ }
68
+ }
69
+ }
70
+ if (target) {
71
+ edge.target = target;
72
+ edge.resolution = "resolved";
73
+ }
74
+ else if (edge.rawSpecifier.startsWith(".") || edge.rawSpecifier.startsWith("/") || edge.rawSpecifier.startsWith("file:")) {
75
+ edge.resolution = "unresolved";
76
+ }
77
+ else {
78
+ edge.resolution = "external";
79
+ }
80
+ }
81
+ export function resolveDependencies(modules, options) {
82
+ const knownFiles = new Set(modules.keys());
83
+ for (const module of modules.values()) {
84
+ for (const edge of module.edges) {
85
+ resolveEdge(edge, module, knownFiles, options);
86
+ }
87
+ }
88
+ }
89
+ export function edgeTargets(edge) {
90
+ if (edge.target) {
91
+ return [edge.target];
92
+ }
93
+ return edge.dynamicPattern?.candidates ?? [];
94
+ }
95
+ function adjacencyFor(modules, moduleId) {
96
+ const module = modules.get(moduleId);
97
+ if (!module) {
98
+ return [];
99
+ }
100
+ return module.edges.flatMap(edgeTargets).filter((target) => modules.has(target));
101
+ }
102
+ function reverseAdjacency(modules) {
103
+ const reverse = new Map();
104
+ for (const moduleId of modules.keys()) {
105
+ reverse.set(moduleId, []);
106
+ }
107
+ for (const module of modules.values()) {
108
+ for (const target of module.edges.flatMap(edgeTargets)) {
109
+ const reverseTargets = reverse.get(target);
110
+ if (reverseTargets) {
111
+ reverseTargets.push(module.id);
112
+ }
113
+ }
114
+ }
115
+ return reverse;
116
+ }
117
+ /** Kosaraju with explicit stacks avoids stack overflows on deep graphs and cycles. */
118
+ export function stronglyConnectedComponents(modules) {
119
+ const visited = new Set();
120
+ const finishOrder = [];
121
+ for (const root of modules.keys()) {
122
+ if (visited.has(root)) {
123
+ continue;
124
+ }
125
+ const stack = [];
126
+ visited.add(root);
127
+ stack.push({ moduleId: root, nextIndex: 0, neighbors: adjacencyFor(modules, root) });
128
+ while (stack.length > 0) {
129
+ const frame = stack[stack.length - 1];
130
+ if (!frame) {
131
+ break;
132
+ }
133
+ if (frame.nextIndex < frame.neighbors.length) {
134
+ const neighbor = frame.neighbors[frame.nextIndex];
135
+ frame.nextIndex += 1;
136
+ if (neighbor && !visited.has(neighbor)) {
137
+ visited.add(neighbor);
138
+ stack.push({ moduleId: neighbor, nextIndex: 0, neighbors: adjacencyFor(modules, neighbor) });
139
+ }
140
+ }
141
+ else {
142
+ finishOrder.push(frame.moduleId);
143
+ stack.pop();
144
+ }
145
+ }
146
+ }
147
+ const reverse = reverseAdjacency(modules);
148
+ const assigned = new Set();
149
+ const components = [];
150
+ while (finishOrder.length > 0) {
151
+ const root = finishOrder.pop();
152
+ if (!root || assigned.has(root)) {
153
+ continue;
154
+ }
155
+ const members = [];
156
+ const stack = [root];
157
+ assigned.add(root);
158
+ while (stack.length > 0) {
159
+ const current = stack.pop();
160
+ if (!current) {
161
+ continue;
162
+ }
163
+ members.push(current);
164
+ for (const neighbor of reverse.get(current) ?? []) {
165
+ if (!assigned.has(neighbor)) {
166
+ assigned.add(neighbor);
167
+ stack.push(neighbor);
168
+ }
169
+ }
170
+ }
171
+ members.sort((left, right) => left.localeCompare(right));
172
+ const selfLoop = members.length === 1 && adjacencyFor(modules, members[0] ?? "").includes(members[0] ?? "");
173
+ components.push({
174
+ id: components.length,
175
+ modules: members,
176
+ isCycle: members.length > 1 || selfLoop,
177
+ });
178
+ }
179
+ return components;
180
+ }
181
+ /**
182
+ * Traverse the graph iteratively. Pattern imports and edges extracted from malformed files
183
+ * propagate `maybe` reachability. A reachable completely unknown dynamic boundary makes every
184
+ * project module only maybe-reachable, preventing unsafe unreachable-file claims.
185
+ */
186
+ export function calculateReachability(modules, entryPoints) {
187
+ const reachable = new Set();
188
+ const maybeReachable = new Set();
189
+ const queue = [...entryPoints].map((moduleId) => ({ moduleId, certainty: "exact" }));
190
+ let cursor = 0;
191
+ let hasReachableUnknownDynamicBoundary = false;
192
+ while (cursor < queue.length) {
193
+ const current = queue[cursor];
194
+ cursor += 1;
195
+ if (!current || !modules.has(current.moduleId)) {
196
+ continue;
197
+ }
198
+ if (current.certainty === "exact") {
199
+ if (reachable.has(current.moduleId)) {
200
+ continue;
201
+ }
202
+ reachable.add(current.moduleId);
203
+ maybeReachable.delete(current.moduleId);
204
+ }
205
+ else if (reachable.has(current.moduleId) || maybeReachable.has(current.moduleId)) {
206
+ continue;
207
+ }
208
+ else {
209
+ maybeReachable.add(current.moduleId);
210
+ }
211
+ const module = modules.get(current.moduleId);
212
+ if (!module) {
213
+ continue;
214
+ }
215
+ if (module.hasUnknownDynamicBoundary) {
216
+ hasReachableUnknownDynamicBoundary = true;
217
+ }
218
+ for (const edge of module.edges) {
219
+ // Type-only imports DO contribute to file reachability, but not necessarily runtime usage
220
+ // The `isTypeOnly` flag is preserved on the edge for later analysis.
221
+ const edgeIsMaybe = edge.kind === "dynamic-pattern" || module.parseStatus !== "parsed";
222
+ const childCertainty = current.certainty === "maybe" || edgeIsMaybe ? "maybe" : "exact";
223
+ for (const target of edgeTargets(edge)) {
224
+ queue.push({ moduleId: target, certainty: childCertainty });
225
+ }
226
+ }
227
+ }
228
+ if (hasReachableUnknownDynamicBoundary) {
229
+ for (const moduleId of modules.keys()) {
230
+ if (!reachable.has(moduleId)) {
231
+ maybeReachable.add(moduleId);
232
+ }
233
+ }
234
+ }
235
+ return { reachable, maybeReachable, hasReachableUnknownDynamicBoundary };
236
+ }
237
+ export function buildImportUsage(modules) {
238
+ const usage = new Map();
239
+ for (const module of modules.values()) {
240
+ for (const edge of module.edges) {
241
+ // We count all imports for usage, but we could later distinguish them if needed.
242
+ // For now, an import (even type-only) marks the export as used.
243
+ for (const target of edgeTargets(edge)) {
244
+ const current = usage.get(target) ?? {
245
+ consumers: new Set(),
246
+ names: new Set(),
247
+ wildcard: false,
248
+ reExportOnly: true
249
+ };
250
+ current.consumers.add(module.id);
251
+ const isReExport = edge.kind === "export-all" || edge.kind === "export-from";
252
+ if (!isReExport) {
253
+ current.reExportOnly = false;
254
+ }
255
+ for (const name of edge.importedNames) {
256
+ current.names.add(name);
257
+ if (name === "*") {
258
+ current.wildcard = true;
259
+ }
260
+ }
261
+ if (edge.kind === "dynamic-pattern" || module.parseStatus !== "parsed") {
262
+ current.wildcard = true;
263
+ }
264
+ if (edge.kind === "export-all") {
265
+ current.wildcard = true;
266
+ }
267
+ usage.set(target, current);
268
+ }
269
+ }
270
+ }
271
+ return usage;
272
+ }
273
+ export function buildUsedExports(modules) {
274
+ const usedExports = new Set();
275
+ const importUsage = buildImportUsage(modules);
276
+ if (modules.size < 10) { // Only log for small test cases
277
+ for (const [mid, usage] of importUsage.entries()) {
278
+ console.log(`[DEBUG] Usage for ${mid}: wildcard=${usage.wildcard}, reExportOnly=${usage.reExportOnly}, names=[${Array.from(usage.names).join(',')}]`);
279
+ }
280
+ }
281
+ for (const [moduleId, usage] of importUsage.entries()) {
282
+ const module = modules.get(moduleId);
283
+ if (!module) {
284
+ continue;
285
+ }
286
+ for (const exp of module.exports) {
287
+ // If an export is marked as an external contract, it's considered used.
288
+ if (exp.isExternalContract) {
289
+ usedExports.add(`${moduleId}:${exp.exportedAs}`);
290
+ continue; // Move to the next export
291
+ }
292
+ // Type-only exports are now tracked and can be marked as used by imports.
293
+ if (usage.wildcard) {
294
+ usedExports.add(`${moduleId}:${exp.exportedAs}`);
295
+ }
296
+ else {
297
+ const importedName = Array.from(usage.names).find((name) => {
298
+ if (name === "default" && exp.isDefault) {
299
+ return true;
300
+ }
301
+ return exp.exportedAs === name;
302
+ });
303
+ if (importedName) {
304
+ usedExports.add(`${moduleId}:${exp.exportedAs}`);
305
+ }
306
+ }
307
+ }
308
+ }
309
+ return usedExports;
310
+ }
311
+ export function buildGraph(modules, entryPoints, options) {
312
+ resolveDependencies(modules, options);
313
+ const components = stronglyConnectedComponents(modules);
314
+ const reachability = calculateReachability(modules, entryPoints);
315
+ const usedExports = buildUsedExports(modules);
316
+ return { components, ...reachability, usedExports };
317
+ }
318
+ export function contextWithGraph(modules, entryPoints, options) {
319
+ const graph = buildGraph(modules, entryPoints, options);
320
+ return {
321
+ options,
322
+ modules,
323
+ entryPoints,
324
+ reachable: graph.reachable,
325
+ maybeReachable: graph.maybeReachable,
326
+ components: graph.components,
327
+ usedExports: graph.usedExports,
328
+ candidateBranches: [],
329
+ };
330
+ }
331
+ //# sourceMappingURL=graph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph.js","sourceRoot":"","sources":["../src/graph.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { AnalyzerOptions, AnalysisReport, ResolvedOptions } from "./types.js";
2
+ export declare function analyze(options: AnalyzerOptions): Promise<AnalysisReport>;
3
+ export declare function shouldFail(report: AnalysisReport, failOn: ResolvedOptions["failOn"]): boolean;
package/dist/index.js ADDED
@@ -0,0 +1,313 @@
1
+ import { promises as fs } from "node:fs";
2
+ import path from "pathe";
3
+ import { parseModule, walkAst } from "./parser.js";
4
+ import { contextWithGraph, buildImportUsage } from "./graph.js";
5
+ import { analyzeLayer2 } from "./layer2.js";
6
+ import { analyzeLayer3 } from "./layer3.js";
7
+ import { analyzeLayer4 } from "./layer4.js";
8
+ import { analyzeLayer5 } from "./layer5.js";
9
+ import { analyzeLayer6 } from "./layer6.js";
10
+ import { buildMonorepoTopology } from "./workspace.js";
11
+ import { loadCache, saveCache, getFileHash, isCacheValid } from "./cache.js";
12
+ import { compileGlobs, conventionalEntryPatterns, DEFAULT_IGNORE, discoverPackageEntryPatterns, discoverSourceFiles, expandEntryPatterns, normalizeAbsolute, relativeDisplayPath, rootLooksValid, } from "./fs-utils.js";
13
+ import { CONFIDENCE_RANK } from "./types.js";
14
+ const VERSION = "1.0.0"; // Optiprune version
15
+ import { DEFAULT_CONFIG, loadConfig, mergeConfig } from "./config-loader.js";
16
+ async function resolveOptions(options) {
17
+ const rootDir = normalizeAbsolute(options.rootDir ?? process.cwd());
18
+ const userConfig = await loadConfig(rootDir);
19
+ const merged = mergeConfig(DEFAULT_CONFIG, {
20
+ ...userConfig,
21
+ ...options,
22
+ rootDir,
23
+ });
24
+ return {
25
+ ...merged,
26
+ entry: merged.entry?.map((entry) => normalizeAbsolute(path.resolve(rootDir, entry))) ?? [],
27
+ ignore: [...DEFAULT_IGNORE, ...(merged.ignore ?? [])],
28
+ };
29
+ }
30
+ export async function analyze(options) {
31
+ const resolvedOptions = await resolveOptions(options);
32
+ const cache = loadCache(resolvedOptions.rootDir);
33
+ const newCache = { version: "1.0", entries: {} };
34
+ // Phase 1: Core Graph & AST (Instant)
35
+ // Discover Monorepo Topology
36
+ let hasMonorepo = false;
37
+ try {
38
+ resolvedOptions.monorepo = await buildMonorepoTopology(resolvedOptions.rootDir);
39
+ hasMonorepo = !!resolvedOptions.monorepo;
40
+ }
41
+ catch (e) {
42
+ // console.error(`[Monorepo] Discovery failed: ${e}`);
43
+ }
44
+ const { rootDir, extensions, ignore, entry, includeConventionalEntries } = resolvedOptions;
45
+ const compiledIgnorePatterns = compileGlobs(ignore);
46
+ if (!(await rootLooksValid(rootDir))) {
47
+ throw new Error(`Root directory does not exist: ${rootDir}`);
48
+ }
49
+ const allSourceFiles = await discoverSourceFiles(rootDir, extensions, compiledIgnorePatterns);
50
+ const modules = new Map();
51
+ let filesParsed = 0;
52
+ let filesRecovered = 0;
53
+ let filesFallback = 0;
54
+ let hasFrameworkNodes = false;
55
+ for (const file of allSourceFiles) {
56
+ const sourceText = await fs.readFile(file, "utf8");
57
+ const currentHash = getFileHash(sourceText);
58
+ let moduleRecord;
59
+ const cached = cache.entries[file];
60
+ if (cached && isCacheValid(cached, sourceText)) {
61
+ moduleRecord = cached.moduleRecord;
62
+ newCache.entries[file] = cached;
63
+ }
64
+ else {
65
+ moduleRecord = parseModule(sourceText, file);
66
+ newCache.entries[file] = {
67
+ hash: currentHash,
68
+ moduleRecord,
69
+ timestamp: Date.now()
70
+ };
71
+ }
72
+ modules.set(file, moduleRecord);
73
+ if (moduleRecord.parseStatus === "parsed") {
74
+ filesParsed += 1;
75
+ // Quick framework detection for Layer 5 gating
76
+ // ✅ FIXED FRAMEWORK DETECTION
77
+ if (!hasFrameworkNodes && moduleRecord.ast) {
78
+ walkAst(moduleRecord.ast, (rawNode) => {
79
+ const node = rawNode;
80
+ const isDecorator = !!node.decorators || (Array.isArray(node.modifiers) && node.modifiers.some((m) => m.type === 'Decorator' || m.kind === 'Decorator'));
81
+ const isZodCall = node.type === "CallExpression" && ((node.callee?.type === "MemberExpression" && (node.callee.object?.name === "z" || node.callee.object?.name === "zod")) ||
82
+ (node.callee?.type === "Identifier" && (node.callee.name === "z" || node.callee.name.startsWith("zod"))));
83
+ if (isDecorator || isZodCall) {
84
+ hasFrameworkNodes = true;
85
+ return true;
86
+ }
87
+ });
88
+ }
89
+ }
90
+ else if (moduleRecord.parseStatus === "recovered") {
91
+ filesRecovered += 1;
92
+ }
93
+ else {
94
+ filesFallback += 1;
95
+ }
96
+ }
97
+ saveCache(resolvedOptions.rootDir, newCache);
98
+ let entryPoints = new Set();
99
+ if (entry.length > 0) {
100
+ for (const pattern of entry) {
101
+ const expanded = expandEntryPatterns(allSourceFiles, rootDir, [pattern]);
102
+ for (const e of expanded) {
103
+ entryPoints.add(path.normalize(e));
104
+ }
105
+ }
106
+ }
107
+ if (includeConventionalEntries) {
108
+ const rootPackageEntries = await discoverPackageEntryPatterns(rootDir);
109
+ for (const pattern of [...rootPackageEntries, ...conventionalEntryPatterns()]) {
110
+ for (const expanded of expandEntryPatterns(allSourceFiles, rootDir, [pattern])) {
111
+ entryPoints.add(path.normalize(expanded));
112
+ }
113
+ }
114
+ if (resolvedOptions.monorepo) {
115
+ for (const pkg of resolvedOptions.monorepo.packageMap.values()) {
116
+ const pkgEntries = await discoverPackageEntryPatterns(pkg.location);
117
+ for (const pattern of [...pkgEntries, ...conventionalEntryPatterns()]) {
118
+ // Use path.posix.relative and path.posix.join for internal pattern construction
119
+ const relativeToRoot = path.posix.relative(rootDir, pkg.location);
120
+ const adjustedPattern = pattern.startsWith('/') ? pattern : path.posix.join(relativeToRoot, pattern);
121
+ for (const expanded of expandEntryPatterns(allSourceFiles, rootDir, [adjustedPattern])) {
122
+ entryPoints.add(path.normalize(expanded));
123
+ }
124
+ }
125
+ }
126
+ }
127
+ }
128
+ const findings = [];
129
+ if (entryPoints.size === 0) {
130
+ findings.push({
131
+ rule: "no-entry-points",
132
+ severity: "warning",
133
+ confidence: "info",
134
+ message: "No entry points found or configured. All files will be considered unreachable.",
135
+ file: rootDir,
136
+ evidence: {},
137
+ });
138
+ }
139
+ const context = contextWithGraph(modules, entryPoints, resolvedOptions);
140
+ // Gated Layer 5: Schema Alignment
141
+ if (hasFrameworkNodes || resolvedOptions.externalContracts?.length) {
142
+ await analyzeLayer5(context);
143
+ }
144
+ // Gated Layer 6: Dependency & Boundary Engine
145
+ if (hasMonorepo || allSourceFiles.some(f => f.endsWith('.d.ts'))) {
146
+ const layer6Findings = await analyzeLayer6(context);
147
+ findings.push(...layer6Findings);
148
+ }
149
+ // Layer 1: Unused Exports & Unreachable Files
150
+ if (resolvedOptions.reportUnusedExports) {
151
+ const importUsage = buildImportUsage(modules);
152
+ for (const module of modules.values()) {
153
+ if (context.reachable.has(module.id)) {
154
+ for (const exp of module.exports) {
155
+ // If it's an external contract (Layer 5/6), it's NEVER unused.
156
+ if (exp.isExternalContract)
157
+ continue;
158
+ const isExportUsed = context.usedExports.has(`${module.id}:${exp.exportedAs}`);
159
+ // Refinement for Monorepo/Re-export chains:
160
+ // If an export is "used" but ONLY by modules that themselves are only re-exporting it
161
+ // (and those re-exporters have no "real" consumers), then it's effectively unused.
162
+ let isEffectivelyUsed = isExportUsed;
163
+ if (isExportUsed) {
164
+ const usage = importUsage.get(module.id);
165
+ if (usage && usage.reExportOnly) {
166
+ const hasRealConsumer = Array.from(usage.consumers).some(c => {
167
+ const cUsage = importUsage.get(c);
168
+ // A consumer is "real" if it's NOT a re-export-only module
169
+ // OR if it's an entry point (which is always real)
170
+ return cUsage && (!cUsage.reExportOnly || context.entryPoints.has(c));
171
+ });
172
+ if (!hasRealConsumer) {
173
+ isEffectivelyUsed = false;
174
+ }
175
+ }
176
+ }
177
+ if (!isEffectivelyUsed && exp.exportedAs !== "default") {
178
+ findings.push({
179
+ rule: "unused-export",
180
+ severity: "warning",
181
+ confidence: "high",
182
+ message: "Export '" + exp.exportedAs + "' is never imported or referenced.",
183
+ file: module.id,
184
+ ...(exp.location && { location: exp.location }),
185
+ evidence: { exportName: exp.exportedAs },
186
+ });
187
+ }
188
+ }
189
+ }
190
+ }
191
+ }
192
+ for (const module of modules.values()) {
193
+ if (!context.reachable.has(module.id) && !context.maybeReachable.has(module.id)) {
194
+ const fileComponent = context.components.find((c) => c.modules.includes(module.id));
195
+ findings.push({
196
+ rule: "unreachable-file",
197
+ severity: "warning",
198
+ confidence: module.hasUnknownDynamicBoundary ? "medium" : "high",
199
+ message: fileComponent?.isCycle
200
+ ? "File is part of an isolated circular dependency cycle (" + fileComponent.id + ") and is unreachable from entry points."
201
+ : "File is not reachable from any entry point.",
202
+ file: module.id,
203
+ evidence: {
204
+ entryPoints: [...context.entryPoints].map((p) => relativeDisplayPath(rootDir, p)),
205
+ componentId: fileComponent?.id,
206
+ cycleSize: fileComponent?.modules.length,
207
+ },
208
+ });
209
+ }
210
+ for (const diagnostic of module.parseDiagnostics) {
211
+ findings.push({
212
+ rule: "parse-recovery",
213
+ severity: diagnostic.recovered ? "info" : "error",
214
+ confidence: diagnostic.recovered ? "low" : "high",
215
+ message: "Parse " + (diagnostic.recovered ? "recovered with errors" : "failed") + ": " + diagnostic.message,
216
+ file: diagnostic.file,
217
+ ...(diagnostic.location && { location: diagnostic.location }),
218
+ evidence: {},
219
+ });
220
+ }
221
+ for (const edge of module.edges) {
222
+ if (edge.resolution === "unresolved") {
223
+ findings.push({
224
+ rule: "unresolved-import",
225
+ severity: "warning",
226
+ confidence: "high",
227
+ message: "Unresolved import specifier: '" + edge.rawSpecifier + "'",
228
+ file: edge.source,
229
+ ...(edge.location && { location: edge.location }),
230
+ evidence: {},
231
+ });
232
+ }
233
+ if (edge.kind === "unknown-dynamic") {
234
+ findings.push({
235
+ rule: "unknown-dynamic-import",
236
+ severity: "warning",
237
+ confidence: "medium",
238
+ message: "Unknown dynamic import pattern: '" + edge.rawSpecifier + "'. This may hide reachable code.",
239
+ file: edge.source,
240
+ ...(edge.location && { location: edge.location }),
241
+ evidence: {},
242
+ });
243
+ }
244
+ }
245
+ }
246
+ // Layer 2: Control Flow Graph (CFG)
247
+ const layer2Findings = analyzeLayer2(context);
248
+ findings.push(...layer2Findings);
249
+ // Phase 2: Layer 3 (Conditional Z3 SMT)
250
+ const layer3Findings = await analyzeLayer3(context);
251
+ findings.push(...layer3Findings);
252
+ // Phase 3: Layer 4 (node:vm sandbox)
253
+ const layer4Findings = await analyzeLayer4(context);
254
+ findings.push(...layer4Findings);
255
+ const summary = {
256
+ filesDiscovered: allSourceFiles.length,
257
+ filesParsed,
258
+ filesRecovered,
259
+ filesFallback,
260
+ edges: [...modules.values()].reduce((sum, module) => sum + module.edges.length, 0),
261
+ entryPoints: entryPoints.size,
262
+ stronglyConnectedComponents: context.components.length,
263
+ cycles: context.components.filter((c) => c.isCycle).length,
264
+ findings: findings.length,
265
+ errors: findings.filter((f) => f.severity === "error").length,
266
+ warnings: findings.filter((f) => f.severity === "warning").length,
267
+ };
268
+ return {
269
+ version: VERSION,
270
+ rootDir,
271
+ entryPoints: [...entryPoints].map((p) => relativeDisplayPath(rootDir, p)),
272
+ summary,
273
+ findings: findings.sort((a, b) => {
274
+ if (a.file !== b.file)
275
+ return a.file.localeCompare(b.file);
276
+ if (a.location && b.location) {
277
+ if (a.location.start.line !== b.location.start.line)
278
+ return a.location.start.line - b.location.start.line;
279
+ return a.location.start.column - b.location.start.column;
280
+ }
281
+ return 0;
282
+ }),
283
+ modules: [...modules.values()].map((module) => ({
284
+ path: relativeDisplayPath(rootDir, module.id),
285
+ parseStatus: module.parseStatus,
286
+ exports: module.exports.map((e) => ({
287
+ name: e.name,
288
+ exportedAs: e.exportedAs,
289
+ isDefault: e.isDefault,
290
+ isReExport: e.isReExport,
291
+ isWildcard: e.isWildcard,
292
+ isTypeOnly: e.isTypeOnly ?? false,
293
+ isExternalContract: e.isExternalContract ?? false,
294
+ })),
295
+ edges: module.edges.map((edge) => ({
296
+ kind: edge.kind,
297
+ specifier: edge.rawSpecifier,
298
+ ...(edge.target && { target: relativeDisplayPath(rootDir, edge.target) }),
299
+ resolution: edge.resolution,
300
+ })),
301
+ })),
302
+ components: context.components.map((c) => ({
303
+ id: c.id,
304
+ modules: c.modules.map((m) => relativeDisplayPath(rootDir, m)),
305
+ isCycle: c.isCycle,
306
+ })),
307
+ };
308
+ }
309
+ export function shouldFail(report, failOn) {
310
+ const failThreshold = CONFIDENCE_RANK[failOn];
311
+ return report.findings.some((f) => CONFIDENCE_RANK[f.confidence] >= failThreshold);
312
+ }
313
+ //# sourceMappingURL=index.js.map