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 +2 -2
- package/package.json +1 -2
- package/src/EngineContext.js +12 -2
- package/src/ast/ASTAnalyzer.js +9 -5
- package/src/ast/DeadCodeDetector.js +2 -2
- package/src/ast/OxcAnalyzer.js +3 -4
- package/src/index.js +68 -1
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.
|
|
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.
|
|
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.
|
|
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
|
},
|
package/src/EngineContext.js
CHANGED
|
@@ -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,
|
package/src/ast/ASTAnalyzer.js
CHANGED
|
@@ -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
|
package/src/ast/OxcAnalyzer.js
CHANGED
|
@@ -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)
|
|
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
|
|