entkapp 4.2.0 → 4.3.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/bin/cli.js CHANGED
@@ -28,7 +28,7 @@ async function bootstrap() {
28
28
  program
29
29
  .name('entkapp')
30
30
  .description(ansis.cyan('Enterprise-Grade AST Syntax Refactoring & Self-Healing Engine'))
31
- .version(packageJsonContent.version || '4.2.0');
31
+ .version(packageJsonContent.version || '4.3.0');
32
32
 
33
33
  program
34
34
  .option('-c, --cwd <path>', 'Specify the execution context root directory', process.cwd())
@@ -132,7 +132,7 @@ async function bootstrap() {
132
132
  }, timeoutMs);
133
133
  timeoutTimer.unref(); // Allow process to exit if work finishes
134
134
 
135
- console.log(ansis.bold.green(`\nšŸ“¦ entkapp v${packageJsonContent.version || '4.2.0'} Engine Activation`));
135
+ console.log(ansis.bold.green(`\nšŸ“¦ entkapp v${packageJsonContent.version || '4.3.0'} Engine Activation`));
136
136
  console.log(ansis.dim('------------------------------------------------------------'));
137
137
  console.log(`${ansis.bold('Target Workspace Root :')} ${ansis.blue(targetCwd)}`);
138
138
  console.log(`${ansis.bold('Refactoring Mode :')} ${options.fix ? ansis.yellow('Active Fixing & Self-Healing Enabled') : ansis.gray('Dry-Run Reporting Only')}`);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
3
  "name": "entkapp",
4
- "version": "4.2.0",
4
+ "version": "4.3.0",
5
5
  "description": "The Ultimate Enterprise Codebase Janitor. Faster than Knip with OXC integration, type-aware analysis, and automated structural healing. Fully standalone - solving what Knip cannot.",
6
6
  "type": "module",
7
7
  "main": "index.js",
@@ -91,7 +91,6 @@
91
91
  },
92
92
  "devDependencies": {
93
93
  "@types/node": "^25.9.3",
94
- "knip": "^6.17.1",
95
94
  "vitepress": "^1.6.4",
96
95
  "vue": ">=3.5.0"
97
96
  },
@@ -31,8 +31,16 @@ export class GraphNode {
31
31
  this.localSuppressedRules = new Set();
32
32
  }
33
33
 
34
- isSymbolReferencedExternally(symbolName, projectGraph) {
34
+ isSymbolReferencedExternally(symbolName, projectGraph, context) {
35
35
  if (this.isLibraryEntry) return true;
36
+
37
+ // Check global registry if provided
38
+ if (context && context.importUsageRegistry) {
39
+ if (context.importUsageRegistry.has(`${this.filePath}:${symbolName}`) ||
40
+ context.importUsageRegistry.has(`${this.filePath}:*`)) {
41
+ return true;
42
+ }
43
+ }
36
44
 
37
45
  // --- NEW: Self-reference check (Fix for SecretSeverity bug) ---
38
46
  // If the symbol is used within the same file, it is NOT dead.
@@ -137,12 +145,14 @@ export class EngineContext {
137
145
  report.orphanedFiles.push(path.relative(this.cwd, filePath));
138
146
  }
139
147
 
148
+ if (node.isEntry || node.isLibraryEntry) continue;
149
+
140
150
  for (const [symbol, meta] of node.internalExports.entries()) {
141
151
  if (symbol === '*' || symbol === 'default' || node.localSuppressedRules.has('unused-export') || node.localSuppressedRules.has(`unused-export:${symbol}`)) {
142
152
  continue;
143
153
  }
144
154
 
145
- if (!node.isSymbolReferencedExternally(symbol, this.projectGraph)) {
155
+ if (!node.isSymbolReferencedExternally(symbol, this.projectGraph, this)) {
146
156
  const loc = node.symbolSourceLocations.get(symbol) || { line: 0, column: 0 };
147
157
  report.deadExports.push({
148
158
  symbol,
@@ -1,6 +1,7 @@
1
1
  import ts from 'typescript';
2
2
  import path from 'path';
3
3
  import fs from 'fs';
4
+ import ansis from 'ansis';
4
5
 
5
6
  export class ASTAnalyzer {
6
7
  constructor(context) {
@@ -398,6 +399,14 @@ export class ASTAnalyzer {
398
399
  if (node.expression.kind === ts.SyntaxKind.ImportKeyword) {
399
400
  const arg = node.arguments[0];
400
401
  if (arg) {
402
+ if (!fileNode.calculatedDynamicImports) fileNode.calculatedDynamicImports = [];
403
+ fileNode.calculatedDynamicImports.push({ kind: ts.SyntaxKind[arg.kind], start: arg.getStart(sourceFile) });
404
+
405
+ // Ensure the argument text is also in rawStringReferences if it's a literal
406
+ if (ts.isStringLiteral(arg) || ts.isNoSubstitutionTemplateLiteral(arg)) {
407
+ fileNode.rawStringReferences.add(arg.text);
408
+ }
409
+
401
410
  if (ts.isStringLiteral(arg)) {
402
411
  fileNode.explicitImports.add(arg.text);
403
412
  fileNode.dynamicImports.add(arg.text);
@@ -405,11 +414,6 @@ export class ASTAnalyzer {
405
414
  if (!arg.text.startsWith('.') && !arg.text.startsWith('/')) {
406
415
  fileNode.externalPackageUsage.add(this._extractPackageName(arg.text));
407
416
  }
408
- } else {
409
- // Dynamic import with a non-literal expression (e.g., variable or template literal).
410
- if (fileNode.calculatedDynamicImports) {
411
- fileNode.calculatedDynamicImports.push({ kind: ts.SyntaxKind[arg.kind], start: arg.getStart(sourceFile) });
412
- }
413
417
  }
414
418
  }
415
419
  } else if (ts.isIdentifier(node.expression) && node.expression.text === 'require') {
@@ -10,7 +10,7 @@ export class DeadCodeDetector {
10
10
  // Find all entry points
11
11
  const entryPoints = new Set();
12
12
  for (const [filePath, node] of graph.entries()) {
13
- if (node.isEntry || node.isNextJsRoute || node.isSvelteComponent || node.isAstroPage) {
13
+ if (node.isEntry || node.isLibraryEntry || node.isNextJsRoute || node.isSvelteComponent || node.isAstroPage) {
14
14
  entryPoints.add(filePath);
15
15
  }
16
16
  }
@@ -45,7 +45,7 @@ export class DeadCodeDetector {
45
45
  if (!node) continue;
46
46
 
47
47
  // If it's an entry point, we consider its exports used (unless strictly configured otherwise)
48
- if (node.isEntry) continue;
48
+ if (node.isEntry || node.isLibraryEntry) continue;
49
49
 
50
50
  for (const [exportName, exportInfo] of node.internalExports.entries()) {
51
51
  if (exportName === '*' || exportName === 'default') continue; // Skip wildcards for now
@@ -271,6 +271,9 @@ export class OxcAnalyzer {
271
271
  // Dynamic import(): import('./module')
272
272
  if (node.callee.type === "Import" && node.arguments.length > 0) {
273
273
  const arg = node.arguments[0];
274
+ if (!fileNode.calculatedDynamicImports) fileNode.calculatedDynamicImports = [];
275
+ fileNode.calculatedDynamicImports.push({ kind: arg.type, start: arg.start });
276
+
274
277
  if (arg.type === "StringLiteral") {
275
278
  const specifier = arg.value;
276
279
  fileNode.explicitImports.add(specifier);
@@ -279,10 +282,6 @@ export class OxcAnalyzer {
279
282
  if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
280
283
  fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
281
284
  }
282
- } else {
283
- if (fileNode.calculatedDynamicImports) {
284
- fileNode.calculatedDynamicImports.push({ kind: arg.type, start: arg.start });
285
- }
286
285
  }
287
286
  } else if (node.callee.type === "Identifier" && node.callee.name === "require" && node.arguments.length > 0 && node.arguments[0].type === "StringLiteral") {
288
287
  const specifier = node.arguments[0].value;
package/src/index.js CHANGED
@@ -87,6 +87,8 @@ export class RefactoringEngine {
87
87
  try {
88
88
  console.log(ansis.bold.green('šŸŽÆ Starting entkapp Operational Optimization Cycle...'));
89
89
 
90
+ if (!this.context.importUsageRegistry) this.context.importUsageRegistry = new Set();
91
+
90
92
  let rl;
91
93
  if (!this.context.skipConfirm) {
92
94
  rl = readline.createInterface({
@@ -136,6 +138,7 @@ export class RefactoringEngine {
136
138
 
137
139
  for (const filePath of sourceCodeFilesList) {
138
140
  const node = this.context.getOrCreateNode(filePath);
141
+ if (!this.context.importUsageRegistry) this.context.importUsageRegistry = new Set();
139
142
  const currentHash = await this.cacheManager.computeHash(filePath);
140
143
  node.contentHash = currentHash;
141
144
 
@@ -217,6 +220,21 @@ export class RefactoringEngine {
217
220
  // Pass 4: Evaluate graph edges and link connections across the codebase mesh
218
221
  console.log(ansis.dim('šŸ”— Linking graph edges and checking structural usage paths...'));
219
222
  await this.linkDependencyGraph();
223
+
224
+ // Update entry points and seeds based on link analysis
225
+ for (const [filePath, node] of this.context.projectGraph.entries()) {
226
+ if (node.isEntry || node.isLibraryEntry) {
227
+ if (!this.context.importUsageRegistry) this.context.importUsageRegistry = new Set();
228
+ this.context.importUsageRegistry.add(`${filePath}:*`);
229
+
230
+ // Also protect all symbols in library entries
231
+ if (node.internalExports) {
232
+ for (const symbol of node.internalExports.keys()) {
233
+ this.context.importUsageRegistry.add(`${filePath}:${symbol}`);
234
+ }
235
+ }
236
+ }
237
+ }
220
238
 
221
239
  // NEW: Circular Dependency Detection
222
240
  console.log(ansis.dim('šŸ”„ Detecting circular dependencies...'));
@@ -470,6 +488,8 @@ export class RefactoringEngine {
470
488
  if (isPackageEntryPoint) continue;
471
489
 
472
490
  const originalNode = this.context.projectGraph.get(cleanExportedFile);
491
+ if (originalNode && originalNode.isLibraryEntry) continue;
492
+
473
493
  const unusedExportsInThisFile = [];
474
494
 
475
495
  for (const symbol of exportsSet) {
@@ -600,6 +620,14 @@ export class RefactoringEngine {
600
620
  const ext = path.extname(entry.name);
601
621
  if (['.js', '.jsx', '.ts', '.tsx', '.vue', '.svelte'].includes(ext) || entry.name === 'package.json') {
602
622
  fileList.push(res);
623
+ // Auto-detect entry points: files named index.js/ts/jsx/tsx in the root are entry points
624
+ if (dir === this.context.cwd && /^index\.(js|ts|jsx|tsx)$/.test(entry.name)) {
625
+ if (!this.context.entryPoints.includes(res)) {
626
+ this.context.entryPoints.push(res);
627
+ }
628
+ const node = this.context.getOrCreateNode(res);
629
+ node.isEntry = true;
630
+ }
603
631
  }
604
632
  }
605
633
  }
@@ -626,7 +654,9 @@ export class RefactoringEngine {
626
654
 
627
655
  for (const seedPath of seeds) {
628
656
  if (this.context.projectGraph.has(seedPath)) {
629
- this.context.projectGraph.get(seedPath).isLibraryEntry = true;
657
+ const node = this.context.projectGraph.get(seedPath);
658
+ node.isLibraryEntry = true;
659
+ node.isEntry = true;
630
660
  }
631
661
  }
632
662
 
@@ -677,6 +707,43 @@ export class RefactoringEngine {
677
707
  }
678
708
  }
679
709
  }
710
+
711
+ // C. Dynamic Import Heuristics (Fix for non-literal dynamic imports)
712
+ // If a file has calculated dynamic imports (e.g. import(variable)),
713
+ // we check all raw strings in that file as potential targets.
714
+ if (node.calculatedDynamicImports && node.calculatedDynamicImports.length > 0) {
715
+ for (const candidate of node.rawStringReferences) {
716
+ // Rule 1: Try resolving it directly
717
+ const resolvedPath = this.resolver.resolveModulePath(filePath, candidate);
718
+ if (resolvedPath && this.context.projectGraph.has(resolvedPath)) {
719
+ const targetNode = this.context.projectGraph.get(resolvedPath);
720
+ targetNode.incomingEdges.add(filePath);
721
+ node.outgoingEdges.add(resolvedPath);
722
+ targetNode.isLibraryEntry = true;
723
+ if (this.context.verbose) console.log(ansis.dim(`šŸ”— Dynamic Heuristic (Resolved): Linked ${candidate} from ${filePath}`));
724
+ continue;
725
+ }
726
+
727
+ // Rule 2: Check all possible internal files for partial matches
728
+ if (candidate.length > 3) { // Avoid very short strings
729
+ for (const [targetPath, targetNode] of this.context.projectGraph.entries()) {
730
+ const relToCwd = path.relative(this.context.cwd, targetPath).replace(/\\/g, '/');
731
+ const relNoExt = relToCwd.replace(/\.[^/.]+$/, "");
732
+
733
+ // Check for exact relative path matches or partial matches that look intentional
734
+ if (candidate === relToCwd || candidate === relNoExt ||
735
+ candidate === `./${relToCwd}` || candidate === `./${relNoExt}` ||
736
+ (candidate.includes('/') && targetPath.endsWith(candidate.startsWith('/') ? candidate : `/${candidate}`))) {
737
+
738
+ targetNode.incomingEdges.add(filePath);
739
+ node.outgoingEdges.add(targetPath);
740
+ targetNode.isLibraryEntry = true;
741
+ if (this.context.verbose) console.log(ansis.dim(`šŸ”— Dynamic Heuristic (Partial): Linked ${candidate} to ${relToCwd} from ${filePath}`));
742
+ }
743
+ }
744
+ }
745
+ }
746
+ }
680
747
  }
681
748
  }
682
749