entkapp 4.4.1 → 4.5.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.
@@ -62,6 +62,11 @@ export class DeadCodeDetector {
62
62
  }
63
63
  }
64
64
 
65
+ // Additional check for library entries or index files
66
+ if (!isUsed && (node.isLibraryEntry || filePath.toLowerCase().includes('index'))) {
67
+ isUsed = true;
68
+ }
69
+
65
70
  if (!isUsed) {
66
71
  deadExports.push({ file: filePath, symbol: exportName, line: exportInfo.start });
67
72
  }
@@ -1,3 +1,5 @@
1
+ import path from 'path';
2
+
1
3
  export class OxcAnalyzer {
2
4
  constructor(context) {
3
5
  this.context = context;
@@ -8,8 +10,7 @@ export class OxcAnalyzer {
8
10
  async init() {
9
11
  if (this.isAvailable) return true;
10
12
  try {
11
- const oxc = await import("oxc-parser");
12
- this.oxc = oxc;
13
+ this.oxc = await import("oxc-parser");
13
14
  this.isAvailable = true;
14
15
  return true;
15
16
  } catch (e) {
@@ -36,42 +37,55 @@ export class OxcAnalyzer {
36
37
  }
37
38
 
38
39
  try {
39
- // Fix: Ensure filePath is correctly handled for OXC (Windows path issue)
40
- const normalizedPath = filePath.replace(/\\/g, '/');
40
+ // Fix: Remove BOM if present (confuses some Rust parsers)
41
+ const cleanContent = content.startsWith('\uFEFF') ? content.slice(1) : content;
41
42
 
42
- // Try passing the path directly as the second argument if the object-based options fail
43
- // Some versions of oxc-parser expect a string as the second argument for the filename
43
+ // Fix: Use absolute path with forward slashes for OXC (most stable for Rust on Windows)
44
+ const normalizedPath = filePath.replace(/\\/g, '/');
45
+ const normalizedContent = cleanContent.replace(/\/([gimuy]*)([nh]+)([gimuy]*)/g, (match, p1, p2, p3) => {
46
+ if (this.context.verbose) console.log(`[OXC] Normalizing regex flag: ${p2} at ${filePath}`);
47
+ return `/${p1}${p3}`;
48
+ });
44
49
  let result;
45
50
  try {
46
- result = this.oxc.parseSync(content, {
47
- sourceType: "module",
48
- sourceFilename: normalizedPath,
49
- lang: "typescript"
50
- });
51
+ // ONLY USE FLAT SIGNATURE: parseSync(sourceText, sourceFilename)
52
+ // This avoids the "rust type String" conversion error seen with configuration objects.
53
+ result = this.oxc.parseSync(normalizedContent, normalizedPath);
51
54
  } catch (e) {
52
- // Fallback for versions that expect the path as second argument
53
- result = this.oxc.parseSync(content, normalizedPath);
55
+ // Silent fallback to TS Compiler if OXC fails
56
+ return false;
57
+ }
58
+
59
+ if (this.context.verbose) {
60
+ console.log(`[OXC] Parsed ${filePath} using flat signature`);
54
61
  }
55
62
 
56
63
  // Fix: Handle cases where OXC returns a JSON string instead of an object
57
64
  // Stabilize result through JSON round-trip to fix N-API conversion issues on Windows
58
- let parsedResult;
65
+ let parsedResult;
59
66
  try {
60
67
  parsedResult = typeof result === 'string' ? JSON.parse(result) : JSON.parse(JSON.stringify(result));
61
68
  } catch (err) {
62
- if (typeof result === 'object') {
63
- parsedResult = result; // Last resort
64
- } else {
65
- throw new Error("OXC returned an invalid format");
66
- }
69
+ parsedResult = result;
70
+ }
71
+ // REPORT OXC ERRORS: If the parser found syntax errors, show them in verbose mode
72
+ if (this.context.verbose && parsedResult.errors && parsedResult.errors.length > 0) {
73
+ console.log(`[OXC] ❌ Parser reported ${parsedResult.errors.length} errors for ${normalizedPath}:`);
74
+ parsedResult.errors.forEach(err => console.log(` - ${err.message || err}`));
67
75
  }
68
76
 
69
- let ast;
77
+ let ast = (parsedResult && parsedResult.program) ? parsedResult : { program: parsedResult };
78
+ fileNode.ast = ast.program;
79
+ fileNode.jsxComponents = new Set();
80
+ fileNode.jsxProps = new Set();
81
+ fileNode.decorators = new Set();
70
82
  if (parsedResult && typeof parsedResult === 'object') {
71
83
  if (parsedResult.program) {
72
84
  ast = parsedResult;
73
85
  } else if (parsedResult.ast) {
74
86
  ast = { program: parsedResult.ast };
87
+ } else if (parsedResult.body || parsedResult.type === 'Program') {
88
+ ast = { program: parsedResult };
75
89
  } else {
76
90
  ast = { program: parsedResult };
77
91
  }
@@ -84,8 +98,45 @@ export class OxcAnalyzer {
84
98
  fileNode.jsxProps = new Set();
85
99
  fileNode.decorators = new Set();
86
100
 
101
+ if (this.context.verbose) {
102
+ console.log(`[OXC] Analyzing ${filePath}`);
103
+ }
87
104
  this.walkOxcAst(ast.program, fileNode, content);
88
105
 
106
+ // 7. Success confirmation
107
+ if (this.context.verbose) {
108
+ console.log(`[OXC] Successfully parsed and analyzed ${filePath}`);
109
+ }
110
+ // Fallback: If OXC found 0 imports but the file has content that looks like it has imports,
111
+ // return false to trigger the TS Compiler API fallback. This is a safety anchor.
112
+ if (fileNode.explicitImports.size === 0 && (content.includes('import') || content.includes('export')) && content.length > 50) {
113
+ if (this.context.verbose) {
114
+ console.log(`[OXC] ⚠️ Parser yielded 0 imports/exports for ${filePath}. Engaging TS Compiler fallback for accuracy.`);
115
+
116
+ // DEEP DIAGNOSTICS: Inspect the first few nodes of the AST to see what OXC is actually producing
117
+ if (ast.program && ast.program.body && ast.program.body.length > 0) {
118
+ console.log(`[OXC-DEBUG] AST Program Body Length: ${ast.program.body.length}`);
119
+ const firstNodes = ast.program.body.slice(0, 3).map(n => ({
120
+ type: n.type,
121
+ hasSource: !!n.source,
122
+ hasDeclaration: !!n.declaration,
123
+ specifiersCount: n.specifiers ? n.specifiers.length : 0
124
+ }));
125
+ console.log(`[OXC-DEBUG] First 3 Nodes structure: ${JSON.stringify(firstNodes, null, 2)}`);
126
+ } else {
127
+ console.log(`[OXC-DEBUG] AST Program Body is EMPTY or UNDEFINED.`);
128
+ }
129
+ }
130
+ return false;
131
+ }
132
+
133
+ if (this.context.verbose) {
134
+ console.log(`[OXC] Found ${fileNode.explicitImports.size} imports in ${filePath}`);
135
+ if (fileNode.explicitImports.size > 0) {
136
+ console.log(`[OXC] Imports for ${filePath}: ${Array.from(fileNode.explicitImports).join(', ')}`);
137
+ }
138
+ }
139
+
89
140
  const lines = content.split('\n');
90
141
  const getLineCol = (pos) => {
91
142
  let count = 0;
@@ -154,6 +205,7 @@ export class OxcAnalyzer {
154
205
  }
155
206
 
156
207
  handleImportDeclaration(node, fileNode) {
208
+ if (!node.source || typeof node.source.value !== 'string') return;
157
209
  const specifier = node.source.value;
158
210
  fileNode.explicitImports.add(specifier);
159
211
 
@@ -164,8 +216,11 @@ export class OxcAnalyzer {
164
216
  if (node.specifiers) {
165
217
  node.specifiers.forEach((spec) => {
166
218
  if (spec.type === "ImportSpecifier") {
167
- const importedName = spec.imported.name || (spec.imported.type === "Identifier" ? spec.imported.name : spec.imported.value);
168
- fileNode.importedSymbols.add(`${specifier}:${importedName}`);
219
+ // In OXC, imported name can be in .imported.name or .imported.value
220
+ const importedName = spec.imported.name || spec.imported.value || (spec.imported.type === "Identifier" ? spec.imported.name : null);
221
+ if (importedName) {
222
+ fileNode.importedSymbols.add(`${specifier}:${importedName}`);
223
+ }
169
224
  } else if (spec.type === "ImportDefaultSpecifier") {
170
225
  fileNode.importedSymbols.add(`${specifier}:default`);
171
226
  } else if (spec.type === "ImportNamespaceSpecifier") {
package/src/index.js CHANGED
@@ -134,7 +134,7 @@ export class RefactoringEngine {
134
134
 
135
135
  // Pass 3: Process source file tokens using high-performance concurrent workers
136
136
  let parallelParseCompleted = false;
137
- if (sourceCodeFilesList.length > 10) {
137
+ if (sourceCodeFilesList.length > 1) {
138
138
  parallelParseCompleted = await this.workerPool.parallelAnalyzeCodebase(sourceCodeFilesList, this);
139
139
  }
140
140
 
@@ -176,9 +176,12 @@ export class RefactoringEngine {
176
176
  await this.magicDetector.injectVirtualConsumerEdges(filePath, node, activeFrameworkEcosystems);
177
177
 
178
178
  // Fix: Explicitly protect entry points defined in local configuration
179
+ const slashify = (p) => path.resolve(this.context.cwd, p).replace(/\\/g, '/');
179
180
  if (this.context.entryPoints && this.context.entryPoints.some(ep => {
180
- const absEp = path.resolve(this.context.cwd, ep);
181
- return absEp === filePath || absEp === filePath.replace(/\.[^/.]+$/, "");
181
+ const absEp = slashify(ep);
182
+ const cleanAbsEp = absEp.replace(/\.[^/.]+$/, "");
183
+ const cleanFilePath = slashify(filePath).replace(/\.[^/.]+$/, "");
184
+ return absEp === slashify(filePath) || cleanAbsEp === cleanFilePath;
182
185
  })) {
183
186
  node.isLibraryEntry = true;
184
187
  }
@@ -220,7 +223,18 @@ export class RefactoringEngine {
220
223
 
221
224
  // Pass 4: Evaluate graph edges and link connections across the codebase mesh
222
225
  console.log(ansis.dim('🔗 Linking graph edges and checking structural usage paths...'));
226
+ if (this.context.verbose) {
227
+ console.log(`[Linker] Starting dependency graph linkage for ${this.context.projectGraph.size} nodes.`);
228
+ }
223
229
  await this.linkDependencyGraph();
230
+ if (this.context.verbose) {
231
+ const totalEdges = Array.from(this.context.projectGraph.values()).reduce((sum, node) => sum + node.outgoingEdges.size, 0);
232
+ console.log(`[Linker] Completed linkage. Total edges created: ${totalEdges}`);
233
+ }
234
+
235
+ if (this.context.options.visualize) {
236
+ await this._generateVisualization(this.context.projectGraph);
237
+ }
224
238
 
225
239
  // NEW: Circular Dependency Detection
226
240
  console.log(ansis.dim('🔄 Detecting circular dependencies...'));
@@ -243,37 +257,37 @@ export class RefactoringEngine {
243
257
  // In a real scenario, we'd need the actual AST object here.
244
258
  const ast = fileNode.ast || {}; // Assuming AST is stored in fileNode
245
259
  this.advancedAnalysis.buildCFG(filePath, ast);
246
- this.advancedAnalysis.handleComputedExports(fileNode, ast);
247
- const unreachableCode = this.advancedAnalysis.analyzeReachability(filePath);
248
- if (unreachableCode.length > 0) {
249
- console.log(ansis.yellow(` ⚠️ Unreachable code detected in ${path.relative(this.context.cwd, filePath)}: ${unreachableCode.length} blocks`));
250
- }
251
- const taintFindings = this.advancedAnalysis.performTaintAnalysis(filePath, ast);
252
- if (taintFindings.length > 0) {
253
- console.log(ansis.red(` 🚨 Taint violations detected in ${path.relative(this.context.cwd, filePath)}: ${taintFindings.length} findings`));
254
- taintFindings.forEach(f => console.log(ansis.dim(` • ${f.type} at ${f.file}:${f.line} (Sink: ${f.sink})`)));
255
- this.context.allSecretFindings.push(...taintFindings); // Add taint findings to overall secrets
256
- }
260
+ // this.advancedAnalysis.handleComputedExports(fileNode, ast);
261
+ // const unreachableCode = this.advancedAnalysis.analyzeReachability(filePath);
262
+ // if (unreachableCode.length > 0) {
263
+ // console.log(ansis.yellow(` ⚠️ Unreachable code detected in ${path.relative(this.context.cwd, filePath)}: ${unreachableCode.length} blocks`));
264
+ // }
265
+ // const taintFindings = this.advancedAnalysis.performTaintAnalysis(filePath, ast);
266
+ // if (taintFindings.length > 0) {
267
+ // console.log(ansis.red(` 🚨 Taint violations detected in ${path.relative(this.context.cwd, filePath)}: ${taintFindings.length} findings`));
268
+ // taintFindings.forEach(f => console.log(ansis.dim(` • ${f.type} at ${f.file}:${f.line} (Sink: ${f.sink})`)));
269
+ // this.context.allSecretFindings.push(...taintFindings); // Add taint findings to overall secrets
270
+ // }
257
271
 
258
272
  // Detect unused members
259
- const unusedMembers = this.advancedAnalysis.detectUnusedMembers(fileNode);
260
- if (unusedMembers.length > 0) {
261
- console.log(ansis.yellow(` ⚠️ Unused members detected in ${path.relative(this.context.cwd, filePath)}:`));
262
- unusedMembers.forEach(m => console.log(ansis.dim(` • ${m.member}: ${m.message}`)));
263
- }
273
+ // const unusedMembers = this.advancedAnalysis.detectUnusedMembers(fileNode);
274
+ // if (unusedMembers.length > 0) {
275
+ // console.log(ansis.yellow(` ⚠️ Unused members detected in ${path.relative(this.context.cwd, filePath)}:`));
276
+ // unusedMembers.forEach(m => console.log(ansis.dim(` • ${m.member}: ${m.message}`)));
277
+ // }
264
278
 
265
279
  // NEW: Type-Jail Analysis
266
- const typeJailViolations = this.workspaceDiagnostic.analyzeTypeJail(fileNode);
267
- if (typeJailViolations.length > 0) {
268
- console.log(ansis.yellow(` ⚠️ Type-Jail violations in ${path.relative(this.context.cwd, filePath)}:`));
269
- typeJailViolations.forEach(v => console.log(ansis.dim(` • ${v.message}`)));
270
- }
280
+ // const typeJailViolations = this.workspaceDiagnostic.analyzeTypeJail(fileNode);
281
+ // if (typeJailViolations.length > 0) {
282
+ // console.log(ansis.yellow(` ⚠️ Type-Jail violations in ${path.relative(this.context.cwd, filePath)}:`));
283
+ // typeJailViolations.forEach(v => console.log(ansis.dim(` • ${v.message}`)));
284
+ // }
271
285
 
272
286
  // NEW: Ghost Code Detection
273
- const ghostExports = this.advancedAnalysis.detectGhostCode(fileNode, this.context.projectGraph);
274
- if (ghostExports.length > 0) {
275
- console.log(ansis.yellow(` 👻 Ghost exports detected in ${path.relative(this.context.cwd, filePath)}: [${ghostExports.join(', ')}]`));
276
- }
287
+ // const ghostExports = this.advancedAnalysis.detectGhostCode(fileNode, this.context.projectGraph);
288
+ // if (ghostExports.length > 0) {
289
+ // console.log(ansis.yellow(` 👻 Ghost exports detected in ${path.relative(this.context.cwd, filePath)}: [${ghostExports.join(', ')}]`));
290
+ // }
277
291
  }
278
292
 
279
293
  // NEW: Workspace Diagnostic & Architecture Enforcement
@@ -578,6 +592,60 @@ export class RefactoringEngine {
578
592
  }
579
593
  analysisSummary.unlistedDependencies = this.context.unlistedDependencies || [];
580
594
 
595
+ // NEW: Advanced Program Analysis v4.5.0 (JIT, Topology, SAST)
596
+ const advancedResults = this.advancedAnalysis.runAll(this.context.projectGraph, this.workspaceGraph.packageManifests);
597
+
598
+ if (this.context.options.verbose) {
599
+ if (advancedResults.jitWarnings.length > 0) {
600
+ console.log(ansis.bold.yellow(`\n⚡ JIT Optimization Warnings (${advancedResults.jitWarnings.length}):`));
601
+ advancedResults.jitWarnings.forEach(w => console.log(ansis.yellow(` • [${w.type}] ${w.message} (${w.file}:${w.line})`)));
602
+ }
603
+ if (advancedResults.securityFindings.length > 0) {
604
+ console.log(ansis.bold.red(`\n🛡️ Security Vulnerabilities (${advancedResults.securityFindings.length}):`));
605
+ advancedResults.securityFindings.forEach(w => console.log(ansis.red(` • [${w.type}] ${w.message} (${w.file}:${w.line})`)));
606
+ }
607
+ if (advancedResults.topologyWarnings.length > 0) {
608
+ console.log(ansis.bold.blue(`\n🌐 Topology & Reachability Warnings (${advancedResults.topologyWarnings.length}):`));
609
+ advancedResults.topologyWarnings.forEach(w => console.log(ansis.blue(` • [${w.type}] ${w.message} (${w.file})`)));
610
+ }
611
+ if (advancedResults.integrityWarnings.length > 0) {
612
+ console.log(ansis.bold.cyan(`\n📊 Config Integrity Checks (${advancedResults.integrityWarnings.length}):`));
613
+ advancedResults.integrityWarnings.forEach(w => console.log(ansis.cyan(` • [${w.type}] ${w.message} (${w.file})`)));
614
+ }
615
+ if (advancedResults.leakWarnings.length > 0) {
616
+ console.log(ansis.bold.magenta(`\n💧 Event Leak Risk Analysis (${advancedResults.leakWarnings.length}):`));
617
+ advancedResults.leakWarnings.forEach(w => console.log(ansis.magenta(` • [${w.type}] ${w.message} (${w.file}:${w.line})`)));
618
+ }
619
+ if (advancedResults.binaryWarnings.length > 0) {
620
+ console.log(ansis.bold.white(`\n🏗️ Binary Shaking Audit (${advancedResults.binaryWarnings.length}):`));
621
+ advancedResults.binaryWarnings.forEach(w => console.log(ansis.white(` • [${w.type}] ${w.message} (${w.file}:${w.line})`)));
622
+ }
623
+ if (advancedResults.sandboxViolations.length > 0) {
624
+ console.log(ansis.bold.bgRed.white(`\n🧱 Architectural Sandbox Violations (${advancedResults.sandboxViolations.length}):`));
625
+ advancedResults.sandboxViolations.forEach(w => console.log(ansis.red(` • [${w.type}] ${w.message} (${w.file})`)));
626
+ }
627
+ if (advancedResults.dereferenceWarnings.length > 0) {
628
+ console.log(ansis.bold.bgBlack.red(`\n💥 Guaranteed Runtime Exceptions (${advancedResults.dereferenceWarnings.length}):`));
629
+ advancedResults.dereferenceWarnings.forEach(w => console.log(ansis.red(` • [${w.type}] ${w.message} (${w.file}:${w.line})`)));
630
+ }
631
+ if (advancedResults.cloneFindings.length > 0) {
632
+ console.log(ansis.bold.bgCyan.black(`\n👯 Structural AST Clones Detected (${advancedResults.cloneFindings.length}):`));
633
+ advancedResults.cloneFindings.forEach(w => console.log(ansis.cyan(` • [${w.type}] ${w.message} (${w.file}:${w.line})`)));
634
+ }
635
+ if (advancedResults.scopeWarnings.length > 0) {
636
+ console.log(ansis.bold.bgGreen.black(`\n🔭 Scope Minimization Suggestions (${advancedResults.scopeWarnings.length}):`));
637
+ advancedResults.scopeWarnings.forEach(w => console.log(ansis.green(` • [${w.type}] ${w.message} (${w.file}:${w.line})`)));
638
+ }
639
+ if (advancedResults.loopWarnings.length > 0) {
640
+ console.log(ansis.bold.bgYellow.black(`\n🔄 Infinite Execution Proofs (${advancedResults.loopWarnings.length}):`));
641
+ advancedResults.loopWarnings.forEach(w => console.log(ansis.yellow(` • [${w.type}] ${w.message} (${w.file}:${w.line})`)));
642
+ }
643
+ if (advancedResults.configWarnings.length > 0) {
644
+ console.log(ansis.bold.bgBlue.white(`\n🧼 Configuration Sanitizer (${advancedResults.configWarnings.length}):`));
645
+ advancedResults.configWarnings.forEach(w => console.log(ansis.blue(` • [${w.type}] ${w.message} (${w.file}:${w.line})`)));
646
+ }
647
+ }
648
+
581
649
  const cycles = cyclesResult; // Use the already detected cycles
582
650
 
583
651
  const structuralModificationsStaged =
@@ -683,6 +751,104 @@ export class RefactoringEngine {
683
751
  }
684
752
  }
685
753
 
754
+ async _generateVisualization(graph) {
755
+ console.log(ansis.bold.green('\n🌐 [VISUALIZER] Generating Interactive Execution Graph...'));
756
+
757
+ const nodes = [];
758
+ const edges = [];
759
+ const fileToIndex = new Map();
760
+ let idCounter = 1;
761
+
762
+ for (const [file, node] of graph.entries()) {
763
+ const relPath = path.relative(this.context.cwd, file);
764
+ const id = idCounter++;
765
+ fileToIndex.set(file, id);
766
+ nodes.push({
767
+ id,
768
+ label: relPath,
769
+ color: node.isLibraryEntry ? '#ff7675' : '#74b9ff',
770
+ shape: node.isLibraryEntry ? 'diamond' : 'dot',
771
+ size: node.isLibraryEntry ? 25 : 15
772
+ });
773
+ }
774
+
775
+ for (const [file, node] of graph.entries()) {
776
+ const fromId = fileToIndex.get(file);
777
+ node.outgoingEdges.forEach(edgeFile => {
778
+ const toId = fileToIndex.get(edgeFile);
779
+ if (toId) {
780
+ edges.push({ from: fromId, to: toId, arrows: 'to' });
781
+ }
782
+ });
783
+ }
784
+
785
+ const html = `
786
+ <!DOCTYPE html>
787
+ <html>
788
+ <head>
789
+ <title>entkapp Execution Graph</title>
790
+ <script type="text/javascript" src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
791
+ <style type="text/css">
792
+ body, html { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background-color: #2d3436; color: #dfe6e9; font-family: sans-serif; }
793
+ #network { width: 100%; height: 100%; }
794
+ #header { position: absolute; top: 10px; left: 10px; z-index: 10; pointer-events: none; }
795
+ h1 { margin: 0; font-size: 1.5rem; color: #fab1a0; }
796
+ .legend { margin-top: 5px; font-size: 0.9rem; }
797
+ .legend-item { display: inline-block; margin-right: 15px; }
798
+ .dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 5px; }
799
+ </style>
800
+ </head>
801
+ <body>
802
+ <div id="header">
803
+ <h1>entkapp Execution Graph</h1>
804
+ <div class="legend">
805
+ <div class="legend-item"><span class="dot" style="background-color: #ff7675; border-radius: 0; transform: rotate(45deg);"></span> Entry Point</div>
806
+ <div class="legend-item"><span class="dot" style="background-color: #74b9ff;"></span> Module</div>
807
+ </div>
808
+ </div>
809
+ <div id="network"></div>
810
+ <script type="text/javascript">
811
+ const nodes = new vis.DataSet(${JSON.stringify(nodes)});
812
+ const edges = new vis.DataSet(${JSON.stringify(edges)});
813
+ const container = document.getElementById('network');
814
+ const data = { nodes, edges };
815
+ const options = {
816
+ nodes: { font: { color: '#dfe6e9', size: 14 } },
817
+ edges: { color: { color: '#636e72', highlight: '#fab1a0' }, width: 2 },
818
+ physics: {
819
+ forceAtlas2Based: { gravitationalConstant: -50, centralGravity: 0.01, springLength: 100, springConstant: 0.08 },
820
+ maxVelocity: 50,
821
+ solver: 'forceAtlas2Based',
822
+ timestep: 0.35,
823
+ stabilization: { iterations: 150 }
824
+ }
825
+ };
826
+ new vis.Network(container, data, options);
827
+ </script>
828
+ </body>
829
+ </html>`;
830
+
831
+ const http = await import('http');
832
+ const server = http.createServer((req, res) => {
833
+ res.writeHead(200, { 'Content-Type': 'text/html' });
834
+ res.end(html);
835
+ });
836
+
837
+ const port = 3000;
838
+ server.listen(port, () => {
839
+ console.log(ansis.bold.cyan(`\n🚀 Web Viewer active at: http://localhost:${port}`));
840
+ console.log(ansis.yellow('Press Ctrl+C to terminate the session and continue...'));
841
+ });
842
+
843
+ return new Promise((resolve) => {
844
+ process.on('SIGINT', () => {
845
+ server.close();
846
+ console.log(ansis.bold.red('\n🛑 Web Viewer stopped.'));
847
+ resolve();
848
+ });
849
+ });
850
+ }
851
+
686
852
  async discoverSourceFiles(dir, fileList) {
687
853
  const entries = await fs.readdir(dir, { withFileTypes: true });
688
854
  for (const entry of entries) {
@@ -702,10 +868,16 @@ export class RefactoringEngine {
702
868
 
703
869
  async linkDependencyGraph() {
704
870
  for (const [filePath, node] of this.context.projectGraph.entries()) {
871
+ if (this.context.verbose && node.explicitImports.size > 0) {
872
+ console.log(`[Linker] Linking ${node.explicitImports.size} imports from ${path.relative(this.context.cwd, filePath)}`);
873
+ }
705
874
  // Pass A: Link all explicit imports (static + dynamic + re-export sources)
706
875
  for (const specifier of node.explicitImports) {
707
876
  const resolvedPath = this.resolver.resolveModulePath(filePath, specifier);
708
877
  if (resolvedPath && this.context.projectGraph.has(resolvedPath)) {
878
+ if (this.context.verbose) {
879
+ console.log(` -> Resolved ${specifier} to ${path.relative(this.context.cwd, resolvedPath)}`);
880
+ }
709
881
  this.context.projectGraph.get(resolvedPath).incomingEdges.add(filePath);
710
882
  node.outgoingEdges.add(resolvedPath);
711
883
 
@@ -727,10 +899,20 @@ export class RefactoringEngine {
727
899
  }
728
900
  }
729
901
  }
902
+ // Auto-detect root index as entry point if no entry points are defined
903
+ const rootIndexFiles = ['src/index.ts', 'src/index.js', 'index.ts', 'index.js'];
904
+ const slashify = (p) => path.resolve(this.context.cwd, p).replace(/\\/g, '/');
905
+ for (const indexFile of rootIndexFiles) {
906
+ const absIndex = slashify(indexFile);
907
+ if (this.context.projectGraph.has(absIndex)) {
908
+ this.context.projectGraph.get(absIndex).isLibraryEntry = true;
909
+ }
910
+ }
730
911
 
731
912
  // Pass B: Link named-symbol imports through barrel/re-export chains
732
913
  for (const specToken of node.importedSymbols) {
733
- const delimiterIndex = specToken.indexOf(':');
914
+ // Fix: Use lastIndexOf for Windows paths (C:/path:symbol)
915
+ const delimiterIndex = specToken.lastIndexOf(':');
734
916
  if (delimiterIndex === -1) continue;
735
917
  const specifier = specToken.slice(0, delimiterIndex);
736
918
  const symbol = specToken.slice(delimiterIndex + 1);
@@ -822,5 +1004,7 @@ export class RefactoringEngine {
822
1004
  if (cachedRecord.localSuppressedRules) cachedRecord.localSuppressedRules.forEach(r => node.localSuppressedRules.add(r));
823
1005
  if (cachedRecord.calculatedDynamicImports) node.calculatedDynamicImports = cachedRecord.calculatedDynamicImports;
824
1006
  if (cachedRecord.isLibraryEntry !== undefined) node.isLibraryEntry = cachedRecord.isLibraryEntry;
1007
+ if (cachedRecord.isEntry !== undefined) node.isEntry = cachedRecord.isEntry;
1008
+ if (cachedRecord.isFrameworkComponent !== undefined) node.isFrameworkComponent = cachedRecord.isFrameworkComponent;
825
1009
  }
826
1010
  }
@@ -66,7 +66,9 @@ export class IncrementalCacheManager {
66
66
  securityThreats: node.securityThreats || [],
67
67
  localSuppressedRules: Array.from(node.localSuppressedRules),
68
68
  symbolSourceLocations: Object.fromEntries(node.symbolSourceLocations),
69
- externalPackageUsage: Array.from(node.externalPackageUsage)
69
+ externalPackageUsage: Array.from(node.externalPackageUsage),
70
+ isEntry: node.isEntry,
71
+ isFrameworkComponent: node.isFrameworkComponent
70
72
  };
71
73
  }
72
74
 
@@ -82,6 +82,9 @@ export class WorkerPool {
82
82
  if (result.jsxProps) result.jsxProps.forEach(p => node.jsxProps.add(p));
83
83
  if (result.decorators) result.decorators.forEach(d => node.decorators.add(d));
84
84
  if (result.isFrameworkContract) node.isFrameworkContract = true;
85
+ if (result.isEntry) node.isEntry = true;
86
+ if (result.isLibraryEntry) node.isLibraryEntry = true;
87
+ if (result.isFrameworkComponent) node.isFrameworkComponent = true;
85
88
  });
86
89
 
87
90
  return true;
@@ -96,7 +99,13 @@ export class WorkerPool {
96
99
  executeChunkInsideThread(fileChunkSubset) {
97
100
  return new Promise((resolve, reject) => {
98
101
  const workerInstance = new Worker(this.workerScriptPath, { type: 'module',
99
- workerData: { files: fileChunkSubset, contextOptions: { verbose: this.context.verbose } }
102
+ workerData: {
103
+ files: fileChunkSubset,
104
+ contextOptions: {
105
+ verbose: this.context.verbose,
106
+ cwd: this.context.cwd
107
+ }
108
+ }
100
109
  });
101
110
 
102
111
  workerInstance.on('message', (payload) => resolve(payload));
@@ -1,7 +1,7 @@
1
1
  import { parentPort, workerData } from 'worker_threads';
2
2
  import fs from 'fs/promises';
3
3
  import { ASTAnalyzer } from '../ast/ASTAnalyzer.js';
4
- import { OxcAnalyzer } from '../analyzers/OxcAnalyzer.js';
4
+ import { OxcAnalyzer } from '../ast/OxcAnalyzer.js';
5
5
 
6
6
  /**
7
7
  * Worker Thread Execution Script
@@ -14,6 +14,7 @@ async function runTask() {
14
14
  // Create a minimal context for analyzers
15
15
  const mockContext = {
16
16
  verbose: contextOptions.verbose,
17
+ cwd: contextOptions.cwd || process.cwd(),
17
18
  projectGraph: new Map(),
18
19
  getOrCreateNode: (path) => ({
19
20
  filePath: path,
@@ -31,7 +32,10 @@ async function runTask() {
31
32
  jsxComponents: new Set(),
32
33
  jsxProps: new Set(),
33
34
  decorators: new Set(),
34
- isFrameworkContract: false
35
+ isFrameworkContract: false,
36
+ isEntry: false,
37
+ isLibraryEntry: false,
38
+ isFrameworkComponent: false
35
39
  })
36
40
  };
37
41
 
@@ -71,7 +75,10 @@ async function runTask() {
71
75
  jsxComponents: Array.from(node.jsxComponents),
72
76
  jsxProps: Array.from(node.jsxProps),
73
77
  decorators: Array.from(node.decorators),
74
- isFrameworkContract: node.isFrameworkContract
78
+ isFrameworkContract: node.isFrameworkContract,
79
+ isEntry: node.isEntry,
80
+ isLibraryEntry: node.isLibraryEntry,
81
+ isFrameworkComponent: node.isFrameworkComponent
75
82
  });
76
83
  } catch (err) {
77
84
  if (contextOptions.verbose) {
@@ -45,14 +45,13 @@ export class DependencyResolver {
45
45
  // we should try to resolve the .ts version first.
46
46
  let effectiveSpecifier = importSpecifier;
47
47
  const isTsFile = /\.(ts|tsx|mts|cts)$/.test(containingFile);
48
- if (isTsFile && importSpecifier.endsWith('.js')) {
49
- // Try replacing .js with .ts
48
+ if (importSpecifier.endsWith('.js')) {
50
49
  const tsSpecifier = importSpecifier.replace(/\.js$/, '.ts');
51
50
  try {
52
51
  const resolvedTs = this.nativeResolver(containingDir, tsSpecifier);
53
52
  if (this.isAbsoluteInternalPath(resolvedTs)) return resolvedTs;
54
53
  } catch (e) {
55
- // Fall back to original specifier if .ts version doesn't exist
54
+ // Fall back to original specifier
56
55
  }
57
56
  }
58
57
  // -----------------------------------------
@@ -106,7 +105,7 @@ export class DependencyResolver {
106
105
  } catch {}
107
106
  }
108
107
  if (this.context.verbose) {
109
- console.debug(`[Resolution Trace Skip] Specifier unresolvable: ${effectiveSpecifier} inside ${containingFile}`);
108
+ console.log(`[Resolution Trace Skip] Specifier unresolvable: ${effectiveSpecifier} inside ${containingFile}`);
110
109
  }
111
110
  }
112
111