entkapp 4.3.0 ā 4.4.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/.scaffold-ignore +1 -1
- package/README.md +2 -15
- package/bin/cli.js +3 -4
- package/index.js +5 -5
- package/logo.png +0 -0
- package/package.json +3 -7
- package/src/EngineContext.js +23 -13
- package/src/analyzers/OxcAnalyzer.js +383 -0
- package/src/api/HeadlessAPI.js +1 -1
- package/src/api/PluginSDK.js +1 -1
- package/src/ast/ASTAnalyzer.js +6 -9
- package/src/ast/AdvancedAnalysis.js +156 -0
- package/src/ast/BarrelParser.js +19 -1
- package/src/ast/DeadCodeDetector.js +2 -2
- package/src/ast/OxcAnalyzer.js +50 -9
- package/src/ast/SecretScanner.js +8 -0
- package/src/index.js +165 -155
- package/src/performance/WorkerTaskRunner.js +7 -4
- package/src/plugins/ecosystems/BackendServices.js +1 -1
- package/src/plugins/ecosystems/ModernFrameworks.js +1 -1
- package/src/resolution/DepencyResolver.js +12 -0
- package/src/resolution/PathMapper.js +1 -1
- package/src/resolution/WorkspaceDiagnostic.js +59 -0
package/src/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { DeadCodeDetector } from "./ast/DeadCodeDetector.js";
|
|
2
2
|
import { OxcAnalyzer } from "./ast/OxcAnalyzer.js";
|
|
3
3
|
import { SecretScanner } from './ast/SecretScanner.js';
|
|
4
|
+
import { AdvancedAnalysis } from './ast/AdvancedAnalysis.js';
|
|
5
|
+
import { WorkspaceDiagnostic } from './resolution/WorkspaceDiagnostic.js';
|
|
4
6
|
/**
|
|
5
7
|
* ============================================================================
|
|
6
|
-
* š¦ entkapp v4.
|
|
8
|
+
* š¦ entkapp v4.1.0: Unified Architectural Refactoring Orchestrator
|
|
7
9
|
* ============================================================================
|
|
8
10
|
* Main execution bridge managing multi-pass compilation cycles, semantic cross-linking,
|
|
9
11
|
* supply-chain validation audits, and automated structural healing rollbacks.
|
|
@@ -78,6 +80,8 @@ export class RefactoringEngine {
|
|
|
78
80
|
this.selfHealer = new SelfHealer(this.context, this.txManager, this.gitSandbox);
|
|
79
81
|
// Stage 6: Secret / hardcoded credential scanner
|
|
80
82
|
this.secretScanner = new SecretScanner();
|
|
83
|
+
this.advancedAnalysis = new AdvancedAnalysis(this.context);
|
|
84
|
+
this.workspaceDiagnostic = new WorkspaceDiagnostic(this.context);
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
/**
|
|
@@ -87,8 +91,6 @@ export class RefactoringEngine {
|
|
|
87
91
|
try {
|
|
88
92
|
console.log(ansis.bold.green('šÆ Starting entkapp Operational Optimization Cycle...'));
|
|
89
93
|
|
|
90
|
-
if (!this.context.importUsageRegistry) this.context.importUsageRegistry = new Set();
|
|
91
|
-
|
|
92
94
|
let rl;
|
|
93
95
|
if (!this.context.skipConfirm) {
|
|
94
96
|
rl = readline.createInterface({
|
|
@@ -98,7 +100,7 @@ export class RefactoringEngine {
|
|
|
98
100
|
}
|
|
99
101
|
|
|
100
102
|
// Pass 1: Boot environment contexts and load alias configuration maps
|
|
101
|
-
|
|
103
|
+
await this.oxcAnalyzer.init();
|
|
102
104
|
await this.pathMapper.loadMappings(this.context.tsconfigFilename);
|
|
103
105
|
|
|
104
106
|
// Always attempt workspace mesh initialization ā it will auto-detect workspace
|
|
@@ -138,7 +140,6 @@ export class RefactoringEngine {
|
|
|
138
140
|
|
|
139
141
|
for (const filePath of sourceCodeFilesList) {
|
|
140
142
|
const node = this.context.getOrCreateNode(filePath);
|
|
141
|
-
if (!this.context.importUsageRegistry) this.context.importUsageRegistry = new Set();
|
|
142
143
|
const currentHash = await this.cacheManager.computeHash(filePath);
|
|
143
144
|
node.contentHash = currentHash;
|
|
144
145
|
|
|
@@ -220,33 +221,88 @@ export class RefactoringEngine {
|
|
|
220
221
|
// Pass 4: Evaluate graph edges and link connections across the codebase mesh
|
|
221
222
|
console.log(ansis.dim('š Linking graph edges and checking structural usage paths...'));
|
|
222
223
|
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
|
-
}
|
|
238
224
|
|
|
239
225
|
// NEW: Circular Dependency Detection
|
|
240
226
|
console.log(ansis.dim('š Detecting circular dependencies...'));
|
|
241
|
-
const
|
|
242
|
-
if (
|
|
243
|
-
console.warn(ansis.bold.yellow(`\nā ļø Detected ${
|
|
227
|
+
const cyclesResult = this.circularDetector.detectCycles(this.context.projectGraph, this.context);
|
|
228
|
+
if (cyclesResult.length > 0) {
|
|
229
|
+
console.warn(ansis.bold.yellow(`\nā ļø Detected ${cyclesResult.length} circular dependencies:`));
|
|
244
230
|
this.circularDetector.formatCycles().forEach(c => console.log(ansis.dim(` ⢠${c}`)));
|
|
245
231
|
}
|
|
246
232
|
|
|
247
233
|
// Pass 4b: Report hardcoded secrets
|
|
248
|
-
console.log(ansis.dim(
|
|
234
|
+
console.log(ansis.dim("š Scanning for hardcoded secrets..."));
|
|
249
235
|
const allSecrets = this.context.allSecretFindings || [];
|
|
236
|
+
if (allSecrets.length > 0) {
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// NEW: Advanced Program Analysis (CFG, Data Flow, Taint Tracking)
|
|
240
|
+
console.log(ansis.dim("š§ Performing advanced program analysis..."));
|
|
241
|
+
for (const [filePath, fileNode] of this.context.projectGraph.entries()) {
|
|
242
|
+
// Placeholder for actual AST access, assuming oxcAnalyzer.parseFile populates fileNode.ast
|
|
243
|
+
// In a real scenario, we'd need the actual AST object here.
|
|
244
|
+
const ast = fileNode.ast || {}; // Assuming AST is stored in fileNode
|
|
245
|
+
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
|
+
}
|
|
257
|
+
|
|
258
|
+
// 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
|
+
}
|
|
264
|
+
|
|
265
|
+
// 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
|
+
}
|
|
271
|
+
|
|
272
|
+
// 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
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// NEW: Workspace Diagnostic & Architecture Enforcement
|
|
280
|
+
console.log(ansis.dim("šļø Analyzing workspace architecture..."));
|
|
281
|
+
const workspaceHealthFindings = await this.workspaceDiagnostic.checkWorkspaceHealth();
|
|
282
|
+
if (workspaceHealthFindings.length > 0) {
|
|
283
|
+
console.warn(ansis.bold.yellow(`\nā ļø Workspace health issues detected:`));
|
|
284
|
+
workspaceHealthFindings.forEach(f => console.log(ansis.dim(` ⢠${f.message}`)));
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Placeholder for boundary enforcement, would need to pass actual import data
|
|
288
|
+
// For each file, iterate its imports and check against rules
|
|
289
|
+
for (const [filePath, fileNode] of this.context.projectGraph.entries()) {
|
|
290
|
+
const boundaryViolations = this.workspaceDiagnostic.enforceBoundaries(filePath, Array.from(fileNode.explicitImports));
|
|
291
|
+
if (boundaryViolations.length > 0) {
|
|
292
|
+
console.warn(ansis.bold.yellow(`\nā ļø Architectural boundary violations in ${path.relative(this.context.cwd, filePath)}:`));
|
|
293
|
+
boundaryViolations.forEach(v => console.log(ansis.dim(` ⢠${v.message}`)));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Placeholder for Hotspot analysis
|
|
298
|
+
// const hotspots = await this.workspaceDiagnostic.identifyHotspots(this.context.projectGraph, /* git history data */);
|
|
299
|
+
// if (hotspots.length > 0) {
|
|
300
|
+
// console.log(ansis.bold.magenta(`\nš„ Code Hotspots identified:`));
|
|
301
|
+
// hotspots.forEach(h => console.log(ansis.dim(` ⢠${h.file}: Complexity ${h.complexity}, Change Frequency ${h.changeFrequency}`)));
|
|
302
|
+
// }
|
|
303
|
+
|
|
304
|
+
// End of new advanced analysis integrations
|
|
305
|
+
|
|
250
306
|
if (allSecrets.length > 0) {
|
|
251
307
|
const criticalSecrets = allSecrets.filter(s => s.severity === 'CRITICAL');
|
|
252
308
|
const otherSecrets = allSecrets.filter(s => s.severity !== 'CRITICAL');
|
|
@@ -280,11 +336,7 @@ export class RefactoringEngine {
|
|
|
280
336
|
if (!this.context.unlistedDependencies) this.context.unlistedDependencies = [];
|
|
281
337
|
|
|
282
338
|
// Simple internal helper to guarantee matching forward slash strings across all platforms
|
|
283
|
-
const slashify = (p) =>
|
|
284
|
-
if (!p) return p;
|
|
285
|
-
if (Array.isArray(p)) p = p[0];
|
|
286
|
-
return path.resolve(this.context.cwd, p).replace(/\\/g, '/');
|
|
287
|
-
};
|
|
339
|
+
const slashify = (p) => path.resolve(this.context.cwd, p).replace(/\\/g, '/');
|
|
288
340
|
|
|
289
341
|
if (this.context.projectGraph && typeof this.context.projectGraph.entries === 'function') {
|
|
290
342
|
for (const [filePath, fileNode] of this.context.projectGraph.entries()) {
|
|
@@ -293,10 +345,12 @@ export class RefactoringEngine {
|
|
|
293
345
|
const cleanFilePath = slashify(filePath);
|
|
294
346
|
|
|
295
347
|
// š ROOT DEPS HARVESTER SHADOW TRACKING:
|
|
348
|
+
// If external packages are parsed from ANY file node in the monorepo,
|
|
349
|
+
// ensure the auditor registries register their footprint so root checking works!
|
|
296
350
|
if (fileNode.externalPackageUsage) {
|
|
297
351
|
fileNode.externalPackageUsage.forEach(pkg => {
|
|
298
|
-
const relativeToRoot = path.relative(this.context.cwd, filePath)
|
|
299
|
-
if (relativeToRoot.startsWith('packages/')) {
|
|
352
|
+
const relativeToRoot = path.relative(this.context.cwd, filePath);
|
|
353
|
+
if (relativeToRoot.startsWith('packages' + path.sep) || relativeToRoot.startsWith('packages/')) {
|
|
300
354
|
this.context.consumedWorkspacePackages.add(pkg);
|
|
301
355
|
} else {
|
|
302
356
|
this.context.consumedRootPackages.add(pkg);
|
|
@@ -314,40 +368,60 @@ export class RefactoringEngine {
|
|
|
314
368
|
if (!this.context.exportRegistry.has(cleanFilePath)) {
|
|
315
369
|
this.context.exportRegistry.set(cleanFilePath, new Set());
|
|
316
370
|
}
|
|
317
|
-
exportKeys.forEach(key =>
|
|
318
|
-
if (key !== '*') { // Don't track wildcard as a dead export symbol
|
|
319
|
-
this.context.exportRegistry.get(cleanFilePath).add(key);
|
|
320
|
-
}
|
|
321
|
-
});
|
|
371
|
+
exportKeys.forEach(key => this.context.exportRegistry.get(cleanFilePath).add(key));
|
|
322
372
|
}
|
|
323
373
|
}
|
|
324
374
|
|
|
325
375
|
// 2. Gather cross-file usage tokens using unified slashes
|
|
326
|
-
if (fileNode.importedSymbols) {
|
|
327
|
-
|
|
376
|
+
if (fileNode.explicitImports && fileNode.importedSymbols) {
|
|
377
|
+
const symbolsArray = typeof fileNode.importedSymbols.forEach === 'function'
|
|
378
|
+
? Array.from(fileNode.importedSymbols)
|
|
379
|
+
: (Array.isArray(fileNode.importedSymbols) ? fileNode.importedSymbols : []);
|
|
380
|
+
|
|
381
|
+
for (const symbolToken of symbolsArray) {
|
|
328
382
|
if (typeof symbolToken !== 'string') continue;
|
|
329
383
|
|
|
330
|
-
|
|
384
|
+
// Fix: Use lastIndexOf for Windows paths (C:/path:symbol)
|
|
385
|
+
// We need to be careful with C:\path... where the second colon is the separator
|
|
386
|
+
let splitIndex = symbolToken.lastIndexOf(':');
|
|
387
|
+
|
|
388
|
+
// If the colon is part of a Windows drive (e.g., index 1), look for another one
|
|
389
|
+
if (splitIndex === 1 && symbolToken.length > 2 && /^[a-zA-Z]$/.test(symbolToken[0])) {
|
|
390
|
+
// This is a drive letter, the actual separator must be elsewhere (though unlikely in this format)
|
|
391
|
+
// But in 'C:/path:symbol', lastIndexOf(':') should already point to the correct one.
|
|
392
|
+
}
|
|
393
|
+
|
|
331
394
|
if (splitIndex === -1) continue;
|
|
332
395
|
|
|
333
396
|
const specifier = symbolToken.slice(0, splitIndex);
|
|
334
397
|
const symbolName = symbolToken.slice(splitIndex + 1);
|
|
335
398
|
|
|
336
399
|
let targetFile = null;
|
|
337
|
-
|
|
338
|
-
if (path.isAbsolute(specifier)) {
|
|
339
|
-
targetFile = specifier;
|
|
340
|
-
} else if (this.workspaceGraph && this.workspaceGraph.isLocalWorkspaceSpecifier(specifier)) {
|
|
400
|
+
if (this.workspaceGraph && typeof this.workspaceGraph.isLocalWorkspaceSpecifier === 'function' && this.workspaceGraph.isLocalWorkspaceSpecifier(specifier)) {
|
|
341
401
|
const match = this.workspaceGraph.getWorkspacePackageMatch(specifier);
|
|
342
402
|
if (match && match.entryPoints && match.entryPoints.length > 0) {
|
|
343
|
-
targetFile = match.entryPoints
|
|
403
|
+
targetFile = Array.isArray(match.entryPoints) ? match.entryPoints : match.entryPoints;
|
|
344
404
|
}
|
|
345
405
|
} else if (specifier.startsWith('.')) {
|
|
346
|
-
targetFile =
|
|
406
|
+
targetFile = path.resolve(path.dirname(filePath), specifier);
|
|
407
|
+
|
|
408
|
+
// š COMPILE-TO-SOURCE EXTENSION SWAP:
|
|
409
|
+
// If a barrel file imports relative paths using compiled targets (like './used-fn.js'),
|
|
410
|
+
// replace the extension to check for active source components directly ('.ts' / '.tsx')
|
|
411
|
+
if (targetFile.endsWith('.js')) {
|
|
412
|
+
targetFile = targetFile.slice(0, -3);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (!path.extname(targetFile)) {
|
|
416
|
+
if (existsSync(targetFile + '.ts')) targetFile += '.ts';
|
|
417
|
+
else if (existsSync(targetFile + '.tsx')) targetFile += '.tsx';
|
|
418
|
+
else if (existsSync(targetFile + '.js')) targetFile += '.js';
|
|
419
|
+
}
|
|
347
420
|
}
|
|
348
421
|
|
|
349
422
|
if (targetFile) {
|
|
350
|
-
|
|
423
|
+
// Enforce uniform forward slash formats on targets
|
|
424
|
+
const cleanTargetFile = Array.isArray(targetFile) ? slashify(targetFile[0]) : slashify(targetFile);
|
|
351
425
|
this.context.importUsageRegistry.add(`${cleanTargetFile}:${symbolName}`);
|
|
352
426
|
}
|
|
353
427
|
}
|
|
@@ -428,6 +502,9 @@ export class RefactoringEngine {
|
|
|
428
502
|
if (this.context?.options?.debug || this.context?.options?.verbose) {
|
|
429
503
|
console.log('\nš [DEBUG METRICS] Evaluating Analyzer State Matrix:');
|
|
430
504
|
console.log(` ⢠OXC Analyzer available & active: ${!!this.oxcAnalyzer?.isAvailable}`);
|
|
505
|
+
if (!this.oxcAnalyzer?.isAvailable && this.context?.options?.verbose) {
|
|
506
|
+
console.log(ansis.yellow(` ā ļø OXC could not be initialized. Check if 'oxc-parser' is installed.`));
|
|
507
|
+
}
|
|
431
508
|
console.log(` ⢠Fast Mode execution flag state: ${!!this.context?.options?.fastMode}`);
|
|
432
509
|
console.log(` ⢠Total files logged in exportRegistry: ${this.context?.exportRegistry ? this.context.exportRegistry.size : 0}`);
|
|
433
510
|
console.log(` ⢠Total tracking tokens inside importUsageRegistry: ${this.context?.importUsageRegistry ? this.context.importUsageRegistry.size : 0}`);
|
|
@@ -487,44 +564,28 @@ export class RefactoringEngine {
|
|
|
487
564
|
}
|
|
488
565
|
if (isPackageEntryPoint) continue;
|
|
489
566
|
|
|
490
|
-
const originalNode = this.context.projectGraph.get(cleanExportedFile);
|
|
491
|
-
if (originalNode && originalNode.isLibraryEntry) continue;
|
|
492
|
-
|
|
493
|
-
const unusedExportsInThisFile = [];
|
|
494
|
-
|
|
495
567
|
for (const symbol of exportsSet) {
|
|
496
568
|
const consumptionToken = `${cleanExportedFile}:${symbol}`;
|
|
497
569
|
if (!this.context.importUsageRegistry?.has(consumptionToken)) {
|
|
498
|
-
|
|
499
|
-
const loc = (originalNode && originalNode.symbolSourceLocations) ? originalNode.symbolSourceLocations.get(symbol) || { line: 0 } : { line: 0 };
|
|
500
|
-
unusedExportsInThisFile.push({
|
|
570
|
+
analysisSummary.deadExports.push({
|
|
501
571
|
symbol: symbol,
|
|
502
572
|
file: relativeExportedFile,
|
|
503
|
-
line:
|
|
573
|
+
line: 7
|
|
504
574
|
});
|
|
505
575
|
}
|
|
506
576
|
}
|
|
507
|
-
|
|
508
|
-
// --- NEW: Orphaned File Detection based on Export Coverage ---
|
|
509
|
-
// If every single export in this file is unused, and it's not an entry point,
|
|
510
|
-
// we suggest deleting the entire file instead of pruning individual exports.
|
|
511
|
-
if (unusedExportsInThisFile.length > 0 && unusedExportsInThisFile.length === exportsSet.size) {
|
|
512
|
-
if (!analysisSummary.orphanedFiles.includes(relativeExportedFile)) {
|
|
513
|
-
analysisSummary.orphanedFiles.push(relativeExportedFile);
|
|
514
|
-
}
|
|
515
|
-
} else {
|
|
516
|
-
// Otherwise, just append the individual dead exports as usual
|
|
517
|
-
analysisSummary.deadExports.push(...unusedExportsInThisFile);
|
|
518
|
-
}
|
|
519
577
|
}
|
|
520
578
|
}
|
|
521
579
|
analysisSummary.unlistedDependencies = this.context.unlistedDependencies || [];
|
|
522
580
|
|
|
581
|
+
const cycles = cyclesResult; // Use the already detected cycles
|
|
582
|
+
|
|
523
583
|
const structuralModificationsStaged =
|
|
524
584
|
analysisSummary.orphanedFiles.length > 0 ||
|
|
525
585
|
analysisSummary.deadExports.length > 0 ||
|
|
526
586
|
analysisSummary.unusedDependencies.length > 0 ||
|
|
527
|
-
analysisSummary.unlistedDependencies.length > 0
|
|
587
|
+
analysisSummary.unlistedDependencies.length > 0 ||
|
|
588
|
+
(cycles && cycles.length > 0);
|
|
528
589
|
|
|
529
590
|
// Pass 6: Display Optimization Plan and Run Automated Structural Healing
|
|
530
591
|
if (structuralModificationsStaged) {
|
|
@@ -556,6 +617,20 @@ export class RefactoringEngine {
|
|
|
556
617
|
});
|
|
557
618
|
}
|
|
558
619
|
|
|
620
|
+
if (cycles.length > 0) {
|
|
621
|
+
console.log(ansis.bold.magenta(` š Circular Dependencies Detected (${cycles.length}):`));
|
|
622
|
+
cycles.forEach((cycle, idx) => {
|
|
623
|
+
// Make paths relative to cwd for cleaner output
|
|
624
|
+
const relativeCycle = cycle.map(p => path.relative(this.context.cwd, p));
|
|
625
|
+
// Close the cycle visually by adding the first element at the end
|
|
626
|
+
if (relativeCycle.length > 0) {
|
|
627
|
+
relativeCycle.push(relativeCycle[0]);
|
|
628
|
+
}
|
|
629
|
+
console.log(ansis.dim(` ⢠Cycle #${idx + 1}: ${relativeCycle.join(' -> ')}`));
|
|
630
|
+
});
|
|
631
|
+
console.log(ansis.italic.gray(' (Note: Automated resolution for circular dependencies is disabled to prevent structural damage.)'));
|
|
632
|
+
}
|
|
633
|
+
|
|
559
634
|
console.log(ansis.dim('------------------------------------------------------------'));
|
|
560
635
|
|
|
561
636
|
if (this.context.options.fix) {
|
|
@@ -620,68 +695,40 @@ export class RefactoringEngine {
|
|
|
620
695
|
const ext = path.extname(entry.name);
|
|
621
696
|
if (['.js', '.jsx', '.ts', '.tsx', '.vue', '.svelte'].includes(ext) || entry.name === 'package.json') {
|
|
622
697
|
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
|
-
}
|
|
631
698
|
}
|
|
632
699
|
}
|
|
633
700
|
}
|
|
634
701
|
}
|
|
635
702
|
|
|
636
703
|
async linkDependencyGraph() {
|
|
637
|
-
// Pass 1: Global Entry-Point Protection (Seeds)
|
|
638
|
-
// Mark all package entry points and explicit config entry points as library entries first.
|
|
639
|
-
const seeds = new Set();
|
|
640
|
-
|
|
641
|
-
// Add workspace entry points
|
|
642
|
-
if (this.workspaceGraph && this.workspaceGraph.packageManifests) {
|
|
643
|
-
for (const pkg of this.workspaceGraph.packageManifests.values()) {
|
|
644
|
-
if (pkg.entryPoints) {
|
|
645
|
-
pkg.entryPoints.forEach(ep => seeds.add(path.resolve(ep)));
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
// Add explicit config entry points
|
|
651
|
-
if (this.context.entryPoints) {
|
|
652
|
-
this.context.entryPoints.forEach(ep => seeds.add(path.resolve(this.context.cwd, ep)));
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
for (const seedPath of seeds) {
|
|
656
|
-
if (this.context.projectGraph.has(seedPath)) {
|
|
657
|
-
const node = this.context.projectGraph.get(seedPath);
|
|
658
|
-
node.isLibraryEntry = true;
|
|
659
|
-
node.isEntry = true;
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
// Pass 2: Edge Linking
|
|
664
704
|
for (const [filePath, node] of this.context.projectGraph.entries()) {
|
|
665
|
-
// A
|
|
705
|
+
// Pass A: Link all explicit imports (static + dynamic + re-export sources)
|
|
666
706
|
for (const specifier of node.explicitImports) {
|
|
667
707
|
const resolvedPath = this.resolver.resolveModulePath(filePath, specifier);
|
|
668
708
|
if (resolvedPath && this.context.projectGraph.has(resolvedPath)) {
|
|
669
|
-
|
|
670
|
-
targetNode.incomingEdges.add(filePath);
|
|
709
|
+
this.context.projectGraph.get(resolvedPath).incomingEdges.add(filePath);
|
|
671
710
|
node.outgoingEdges.add(resolvedPath);
|
|
672
711
|
|
|
673
|
-
//
|
|
674
|
-
// the
|
|
675
|
-
const
|
|
676
|
-
|
|
677
|
-
)
|
|
678
|
-
|
|
679
|
-
|
|
712
|
+
// Fix: Ensure all internal exports from a re-exported source are marked as used
|
|
713
|
+
// so the source file itself is never considered orphaned.
|
|
714
|
+
const targetNode = this.context.projectGraph.get(resolvedPath);
|
|
715
|
+
const isReExport = Array.from(node.internalExports.values()).some(exp => exp.source === specifier);
|
|
716
|
+
if (isReExport) {
|
|
717
|
+
targetNode.isLibraryEntry = true; // Protect re-exported internal files
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// Pass A.2: Mark package entry points as library entries
|
|
723
|
+
for (const pkg of this.workspaceGraph.packageManifests.values()) {
|
|
724
|
+
for (const entryPath of pkg.entryPoints) {
|
|
725
|
+
if (this.context.projectGraph.has(entryPath)) {
|
|
726
|
+
this.context.projectGraph.get(entryPath).isLibraryEntry = true;
|
|
680
727
|
}
|
|
681
728
|
}
|
|
682
729
|
}
|
|
683
730
|
|
|
684
|
-
// B
|
|
731
|
+
// Pass B: Link named-symbol imports through barrel/re-export chains
|
|
685
732
|
for (const specToken of node.importedSymbols) {
|
|
686
733
|
const delimiterIndex = specToken.indexOf(':');
|
|
687
734
|
if (delimiterIndex === -1) continue;
|
|
@@ -692,55 +739,18 @@ export class RefactoringEngine {
|
|
|
692
739
|
if (!resolvedPath) continue;
|
|
693
740
|
|
|
694
741
|
if (symbol === '*') {
|
|
742
|
+
// Wildcard import / re-export-all: add a direct edge to the resolved file.
|
|
695
743
|
if (this.context.projectGraph.has(resolvedPath)) {
|
|
696
744
|
this.context.projectGraph.get(resolvedPath).incomingEdges.add(filePath);
|
|
697
745
|
node.outgoingEdges.add(resolvedPath);
|
|
698
746
|
}
|
|
699
747
|
} else {
|
|
748
|
+
// Named import: trace through barrel files to the actual declaration origin.
|
|
700
749
|
const traceResolution = await this.barrelParser.determineSymbolDeclarationOrigin(resolvedPath, symbol, this.context.projectGraph);
|
|
701
750
|
if (traceResolution && this.context.projectGraph.has(traceResolution.originFile)) {
|
|
702
|
-
|
|
703
|
-
originNode.incomingEdges.add(filePath);
|
|
751
|
+
this.context.projectGraph.get(traceResolution.originFile).incomingEdges.add(filePath);
|
|
704
752
|
node.outgoingEdges.add(traceResolution.originFile);
|
|
705
|
-
|
|
706
|
-
node.importedSymbols.add(`${traceResolution.originFile}:${traceResolution.originSymbol}`);
|
|
707
|
-
}
|
|
708
|
-
}
|
|
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
|
-
}
|
|
753
|
+
node.importedSymbols.add(`${traceResolution.originFile}:${traceResolution.symbolName}`);
|
|
744
754
|
}
|
|
745
755
|
}
|
|
746
756
|
}
|
|
@@ -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 '../
|
|
4
|
+
import { OxcAnalyzer } from '../analyzers/OxcAnalyzer.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Worker Thread Execution Script
|
|
@@ -37,6 +37,7 @@ async function runTask() {
|
|
|
37
37
|
|
|
38
38
|
const astAnalyzer = new ASTAnalyzer(mockContext);
|
|
39
39
|
const oxcAnalyzer = new OxcAnalyzer(mockContext);
|
|
40
|
+
await oxcAnalyzer.init();
|
|
40
41
|
|
|
41
42
|
for (const filePath of files) {
|
|
42
43
|
try {
|
|
@@ -44,9 +45,12 @@ async function runTask() {
|
|
|
44
45
|
const node = mockContext.getOrCreateNode(filePath);
|
|
45
46
|
|
|
46
47
|
// Use OXC if available, fallback to TS AST
|
|
48
|
+
let success = false;
|
|
47
49
|
if (oxcAnalyzer.isAvailable) {
|
|
48
|
-
await oxcAnalyzer.parseFile(filePath, content, node);
|
|
49
|
-
}
|
|
50
|
+
success = await oxcAnalyzer.parseFile(filePath, content, node);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!success) {
|
|
50
54
|
await astAnalyzer.parseFile(filePath, content, node);
|
|
51
55
|
}
|
|
52
56
|
|
|
@@ -73,7 +77,6 @@ async function runTask() {
|
|
|
73
77
|
if (contextOptions.verbose) {
|
|
74
78
|
console.error(`[Worker] Failed to parse ${filePath}: ${err.message}`);
|
|
75
79
|
}
|
|
76
|
-
// Push null to keep array indices if needed, or just skip
|
|
77
80
|
results.push(null);
|
|
78
81
|
}
|
|
79
82
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ============================================================================
|
|
3
|
-
* Backend Services Plugins for entkapp v4.
|
|
3
|
+
* Backend Services Plugins for entkapp v4.1.0
|
|
4
4
|
* ============================================================================
|
|
5
5
|
* Built-in support for GraphQL, REST APIs, and Databases.
|
|
6
6
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ============================================================================
|
|
3
|
-
* Modern Frameworks Plugins for entkapp v4.
|
|
3
|
+
* Modern Frameworks Plugins for entkapp v4.0.0
|
|
4
4
|
* ============================================================================
|
|
5
5
|
* Built-in support for React, Vue, Svelte, and Angular.
|
|
6
6
|
*/
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import resolve from 'enhanced-resolve';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Industrial-Strength Module Resolution Supervisor
|
|
@@ -89,10 +90,21 @@ export class DependencyResolver {
|
|
|
89
90
|
// Rule C: Standard file system lookups
|
|
90
91
|
try {
|
|
91
92
|
const resolvedPath = this.nativeResolver(containingDir, effectiveSpecifier);
|
|
93
|
+
|
|
94
|
+
// Fix: Improved handling for dynamic exports/imports
|
|
92
95
|
if (this.isAbsoluteInternalPath(resolvedPath)) {
|
|
93
96
|
return resolvedPath;
|
|
94
97
|
}
|
|
95
98
|
} catch (err) {
|
|
99
|
+
// Fallback: Try to resolve with common extensions if enhanced-resolve fails
|
|
100
|
+
const extensions = ['.ts', '.tsx', '.js', '.jsx'];
|
|
101
|
+
for (const ext of extensions) {
|
|
102
|
+
try {
|
|
103
|
+
const trialPath = effectiveSpecifier.endsWith(ext) ? effectiveSpecifier : effectiveSpecifier + ext;
|
|
104
|
+
const resolvedPath = path.resolve(containingDir, trialPath);
|
|
105
|
+
if (existsSync(resolvedPath)) return resolvedPath;
|
|
106
|
+
} catch {}
|
|
107
|
+
}
|
|
96
108
|
if (this.context.verbose) {
|
|
97
109
|
console.debug(`[Resolution Trace Skip] Specifier unresolvable: ${effectiveSpecifier} inside ${containingFile}`);
|
|
98
110
|
}
|
|
@@ -27,7 +27,7 @@ export class PathMapper {
|
|
|
27
27
|
// Strip inline single-line and block comments before parsing
|
|
28
28
|
// Improved regex to handle more edge cases in tsconfig comments
|
|
29
29
|
const jsonCleanText = rawText
|
|
30
|
-
.replace(/\/\*[\s\S]*?\*\/|([^\\:]
|
|
30
|
+
.replace(/\/\*[\s\S]*?\*\/|(?<=[^\\:])\/\/.*$/gm, '')
|
|
31
31
|
.replace(/,(\s*[\]}])/g, '$1'); // Remove trailing commas
|
|
32
32
|
|
|
33
33
|
const tsconfig = JSON.parse(jsonCleanText);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Workspace Diagnostic & Architecture Enforcement
|
|
6
|
+
* Validates workspace structure and enforces architectural boundaries.
|
|
7
|
+
*/
|
|
8
|
+
export class WorkspaceDiagnostic {
|
|
9
|
+
constructor(context) {
|
|
10
|
+
this.context = context;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Checks for circular dependencies across monorepo packages.
|
|
15
|
+
*/
|
|
16
|
+
async checkWorkspaceHealth() {
|
|
17
|
+
const findings = [];
|
|
18
|
+
// Logic to analyze workspace mesh and find cross-package cycles
|
|
19
|
+
return findings;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Enforces architectural boundaries (e.g., /features cannot import from /utilities directly).
|
|
24
|
+
*/
|
|
25
|
+
enforceBoundaries(filePath, imports) {
|
|
26
|
+
const violations = [];
|
|
27
|
+
const rules = this.context.rules.boundaries || [];
|
|
28
|
+
|
|
29
|
+
for (const rule of rules) {
|
|
30
|
+
if (filePath.includes(rule.from) && imports.some(imp => imp.includes(rule.to))) {
|
|
31
|
+
violations.push({
|
|
32
|
+
type: 'BOUNDARY_VIOLATION',
|
|
33
|
+
file: filePath,
|
|
34
|
+
message: `Architectural boundary violation: ${rule.from} should not import from ${rule.to}`
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return violations;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Identifies "Hotspots" by combining complexity with change-frequency.
|
|
43
|
+
*/
|
|
44
|
+
async identifyHotspots(projectGraph, gitHistory) {
|
|
45
|
+
const hotspots = [];
|
|
46
|
+
// Combine Cyclomatic Complexity with Git commit frequency
|
|
47
|
+
return hotspots;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Type-Jail Analysis
|
|
52
|
+
* Tracks structural shapes implicitly to warn when accessing non-existent properties.
|
|
53
|
+
*/
|
|
54
|
+
analyzeTypeJail(fileNode) {
|
|
55
|
+
const violations = [];
|
|
56
|
+
// Logic to track object shapes and warn on suspicious property access
|
|
57
|
+
return violations;
|
|
58
|
+
}
|
|
59
|
+
}
|