entkapp 4.2.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 +2 -7
- package/src/EngineContext.js +22 -2
- 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 +1 -0
- package/src/ast/AdvancedAnalysis.js +156 -0
- package/src/ast/BarrelParser.js +19 -1
- package/src/ast/OxcAnalyzer.js +46 -6
- package/src/ast/SecretScanner.js +8 -0
- package/src/index.js +165 -88
- 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
|
/**
|
|
@@ -96,7 +100,7 @@ export class RefactoringEngine {
|
|
|
96
100
|
}
|
|
97
101
|
|
|
98
102
|
// Pass 1: Boot environment contexts and load alias configuration maps
|
|
99
|
-
|
|
103
|
+
await this.oxcAnalyzer.init();
|
|
100
104
|
await this.pathMapper.loadMappings(this.context.tsconfigFilename);
|
|
101
105
|
|
|
102
106
|
// Always attempt workspace mesh initialization ā it will auto-detect workspace
|
|
@@ -220,15 +224,85 @@ export class RefactoringEngine {
|
|
|
220
224
|
|
|
221
225
|
// NEW: Circular Dependency Detection
|
|
222
226
|
console.log(ansis.dim('š Detecting circular dependencies...'));
|
|
223
|
-
const
|
|
224
|
-
if (
|
|
225
|
-
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:`));
|
|
226
230
|
this.circularDetector.formatCycles().forEach(c => console.log(ansis.dim(` ⢠${c}`)));
|
|
227
231
|
}
|
|
228
232
|
|
|
229
233
|
// Pass 4b: Report hardcoded secrets
|
|
230
|
-
console.log(ansis.dim(
|
|
234
|
+
console.log(ansis.dim("š Scanning for hardcoded secrets..."));
|
|
231
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
|
+
|
|
232
306
|
if (allSecrets.length > 0) {
|
|
233
307
|
const criticalSecrets = allSecrets.filter(s => s.severity === 'CRITICAL');
|
|
234
308
|
const otherSecrets = allSecrets.filter(s => s.severity !== 'CRITICAL');
|
|
@@ -262,11 +336,7 @@ export class RefactoringEngine {
|
|
|
262
336
|
if (!this.context.unlistedDependencies) this.context.unlistedDependencies = [];
|
|
263
337
|
|
|
264
338
|
// Simple internal helper to guarantee matching forward slash strings across all platforms
|
|
265
|
-
const slashify = (p) =>
|
|
266
|
-
if (!p) return p;
|
|
267
|
-
if (Array.isArray(p)) p = p[0];
|
|
268
|
-
return path.resolve(this.context.cwd, p).replace(/\\/g, '/');
|
|
269
|
-
};
|
|
339
|
+
const slashify = (p) => path.resolve(this.context.cwd, p).replace(/\\/g, '/');
|
|
270
340
|
|
|
271
341
|
if (this.context.projectGraph && typeof this.context.projectGraph.entries === 'function') {
|
|
272
342
|
for (const [filePath, fileNode] of this.context.projectGraph.entries()) {
|
|
@@ -275,10 +345,12 @@ export class RefactoringEngine {
|
|
|
275
345
|
const cleanFilePath = slashify(filePath);
|
|
276
346
|
|
|
277
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!
|
|
278
350
|
if (fileNode.externalPackageUsage) {
|
|
279
351
|
fileNode.externalPackageUsage.forEach(pkg => {
|
|
280
|
-
const relativeToRoot = path.relative(this.context.cwd, filePath)
|
|
281
|
-
if (relativeToRoot.startsWith('packages/')) {
|
|
352
|
+
const relativeToRoot = path.relative(this.context.cwd, filePath);
|
|
353
|
+
if (relativeToRoot.startsWith('packages' + path.sep) || relativeToRoot.startsWith('packages/')) {
|
|
282
354
|
this.context.consumedWorkspacePackages.add(pkg);
|
|
283
355
|
} else {
|
|
284
356
|
this.context.consumedRootPackages.add(pkg);
|
|
@@ -296,40 +368,60 @@ export class RefactoringEngine {
|
|
|
296
368
|
if (!this.context.exportRegistry.has(cleanFilePath)) {
|
|
297
369
|
this.context.exportRegistry.set(cleanFilePath, new Set());
|
|
298
370
|
}
|
|
299
|
-
exportKeys.forEach(key =>
|
|
300
|
-
if (key !== '*') { // Don't track wildcard as a dead export symbol
|
|
301
|
-
this.context.exportRegistry.get(cleanFilePath).add(key);
|
|
302
|
-
}
|
|
303
|
-
});
|
|
371
|
+
exportKeys.forEach(key => this.context.exportRegistry.get(cleanFilePath).add(key));
|
|
304
372
|
}
|
|
305
373
|
}
|
|
306
374
|
|
|
307
375
|
// 2. Gather cross-file usage tokens using unified slashes
|
|
308
|
-
if (fileNode.importedSymbols) {
|
|
309
|
-
|
|
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) {
|
|
310
382
|
if (typeof symbolToken !== 'string') continue;
|
|
311
383
|
|
|
312
|
-
|
|
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
|
+
|
|
313
394
|
if (splitIndex === -1) continue;
|
|
314
395
|
|
|
315
396
|
const specifier = symbolToken.slice(0, splitIndex);
|
|
316
397
|
const symbolName = symbolToken.slice(splitIndex + 1);
|
|
317
398
|
|
|
318
399
|
let targetFile = null;
|
|
319
|
-
|
|
320
|
-
if (path.isAbsolute(specifier)) {
|
|
321
|
-
targetFile = specifier;
|
|
322
|
-
} else if (this.workspaceGraph && this.workspaceGraph.isLocalWorkspaceSpecifier(specifier)) {
|
|
400
|
+
if (this.workspaceGraph && typeof this.workspaceGraph.isLocalWorkspaceSpecifier === 'function' && this.workspaceGraph.isLocalWorkspaceSpecifier(specifier)) {
|
|
323
401
|
const match = this.workspaceGraph.getWorkspacePackageMatch(specifier);
|
|
324
402
|
if (match && match.entryPoints && match.entryPoints.length > 0) {
|
|
325
|
-
targetFile = match.entryPoints
|
|
403
|
+
targetFile = Array.isArray(match.entryPoints) ? match.entryPoints : match.entryPoints;
|
|
326
404
|
}
|
|
327
405
|
} else if (specifier.startsWith('.')) {
|
|
328
|
-
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
|
+
}
|
|
329
420
|
}
|
|
330
421
|
|
|
331
422
|
if (targetFile) {
|
|
332
|
-
|
|
423
|
+
// Enforce uniform forward slash formats on targets
|
|
424
|
+
const cleanTargetFile = Array.isArray(targetFile) ? slashify(targetFile[0]) : slashify(targetFile);
|
|
333
425
|
this.context.importUsageRegistry.add(`${cleanTargetFile}:${symbolName}`);
|
|
334
426
|
}
|
|
335
427
|
}
|
|
@@ -410,6 +502,9 @@ export class RefactoringEngine {
|
|
|
410
502
|
if (this.context?.options?.debug || this.context?.options?.verbose) {
|
|
411
503
|
console.log('\nš [DEBUG METRICS] Evaluating Analyzer State Matrix:');
|
|
412
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
|
+
}
|
|
413
508
|
console.log(` ⢠Fast Mode execution flag state: ${!!this.context?.options?.fastMode}`);
|
|
414
509
|
console.log(` ⢠Total files logged in exportRegistry: ${this.context?.exportRegistry ? this.context.exportRegistry.size : 0}`);
|
|
415
510
|
console.log(` ⢠Total tracking tokens inside importUsageRegistry: ${this.context?.importUsageRegistry ? this.context.importUsageRegistry.size : 0}`);
|
|
@@ -469,42 +564,28 @@ export class RefactoringEngine {
|
|
|
469
564
|
}
|
|
470
565
|
if (isPackageEntryPoint) continue;
|
|
471
566
|
|
|
472
|
-
const originalNode = this.context.projectGraph.get(cleanExportedFile);
|
|
473
|
-
const unusedExportsInThisFile = [];
|
|
474
|
-
|
|
475
567
|
for (const symbol of exportsSet) {
|
|
476
568
|
const consumptionToken = `${cleanExportedFile}:${symbol}`;
|
|
477
569
|
if (!this.context.importUsageRegistry?.has(consumptionToken)) {
|
|
478
|
-
|
|
479
|
-
const loc = (originalNode && originalNode.symbolSourceLocations) ? originalNode.symbolSourceLocations.get(symbol) || { line: 0 } : { line: 0 };
|
|
480
|
-
unusedExportsInThisFile.push({
|
|
570
|
+
analysisSummary.deadExports.push({
|
|
481
571
|
symbol: symbol,
|
|
482
572
|
file: relativeExportedFile,
|
|
483
|
-
line:
|
|
573
|
+
line: 7
|
|
484
574
|
});
|
|
485
575
|
}
|
|
486
576
|
}
|
|
487
|
-
|
|
488
|
-
// --- NEW: Orphaned File Detection based on Export Coverage ---
|
|
489
|
-
// If every single export in this file is unused, and it's not an entry point,
|
|
490
|
-
// we suggest deleting the entire file instead of pruning individual exports.
|
|
491
|
-
if (unusedExportsInThisFile.length > 0 && unusedExportsInThisFile.length === exportsSet.size) {
|
|
492
|
-
if (!analysisSummary.orphanedFiles.includes(relativeExportedFile)) {
|
|
493
|
-
analysisSummary.orphanedFiles.push(relativeExportedFile);
|
|
494
|
-
}
|
|
495
|
-
} else {
|
|
496
|
-
// Otherwise, just append the individual dead exports as usual
|
|
497
|
-
analysisSummary.deadExports.push(...unusedExportsInThisFile);
|
|
498
|
-
}
|
|
499
577
|
}
|
|
500
578
|
}
|
|
501
579
|
analysisSummary.unlistedDependencies = this.context.unlistedDependencies || [];
|
|
502
580
|
|
|
581
|
+
const cycles = cyclesResult; // Use the already detected cycles
|
|
582
|
+
|
|
503
583
|
const structuralModificationsStaged =
|
|
504
584
|
analysisSummary.orphanedFiles.length > 0 ||
|
|
505
585
|
analysisSummary.deadExports.length > 0 ||
|
|
506
586
|
analysisSummary.unusedDependencies.length > 0 ||
|
|
507
|
-
analysisSummary.unlistedDependencies.length > 0
|
|
587
|
+
analysisSummary.unlistedDependencies.length > 0 ||
|
|
588
|
+
(cycles && cycles.length > 0);
|
|
508
589
|
|
|
509
590
|
// Pass 6: Display Optimization Plan and Run Automated Structural Healing
|
|
510
591
|
if (structuralModificationsStaged) {
|
|
@@ -536,6 +617,20 @@ export class RefactoringEngine {
|
|
|
536
617
|
});
|
|
537
618
|
}
|
|
538
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
|
+
|
|
539
634
|
console.log(ansis.dim('------------------------------------------------------------'));
|
|
540
635
|
|
|
541
636
|
if (this.context.options.fix) {
|
|
@@ -606,52 +701,34 @@ export class RefactoringEngine {
|
|
|
606
701
|
}
|
|
607
702
|
|
|
608
703
|
async linkDependencyGraph() {
|
|
609
|
-
// Pass 1: Global Entry-Point Protection (Seeds)
|
|
610
|
-
// Mark all package entry points and explicit config entry points as library entries first.
|
|
611
|
-
const seeds = new Set();
|
|
612
|
-
|
|
613
|
-
// Add workspace entry points
|
|
614
|
-
if (this.workspaceGraph && this.workspaceGraph.packageManifests) {
|
|
615
|
-
for (const pkg of this.workspaceGraph.packageManifests.values()) {
|
|
616
|
-
if (pkg.entryPoints) {
|
|
617
|
-
pkg.entryPoints.forEach(ep => seeds.add(path.resolve(ep)));
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
// Add explicit config entry points
|
|
623
|
-
if (this.context.entryPoints) {
|
|
624
|
-
this.context.entryPoints.forEach(ep => seeds.add(path.resolve(this.context.cwd, ep)));
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
for (const seedPath of seeds) {
|
|
628
|
-
if (this.context.projectGraph.has(seedPath)) {
|
|
629
|
-
this.context.projectGraph.get(seedPath).isLibraryEntry = true;
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
// Pass 2: Edge Linking
|
|
634
704
|
for (const [filePath, node] of this.context.projectGraph.entries()) {
|
|
635
|
-
// A
|
|
705
|
+
// Pass A: Link all explicit imports (static + dynamic + re-export sources)
|
|
636
706
|
for (const specifier of node.explicitImports) {
|
|
637
707
|
const resolvedPath = this.resolver.resolveModulePath(filePath, specifier);
|
|
638
708
|
if (resolvedPath && this.context.projectGraph.has(resolvedPath)) {
|
|
639
|
-
|
|
640
|
-
targetNode.incomingEdges.add(filePath);
|
|
709
|
+
this.context.projectGraph.get(resolvedPath).incomingEdges.add(filePath);
|
|
641
710
|
node.outgoingEdges.add(resolvedPath);
|
|
642
711
|
|
|
643
|
-
//
|
|
644
|
-
// the
|
|
645
|
-
const
|
|
646
|
-
|
|
647
|
-
)
|
|
648
|
-
|
|
649
|
-
|
|
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;
|
|
650
727
|
}
|
|
651
728
|
}
|
|
652
729
|
}
|
|
653
730
|
|
|
654
|
-
// B
|
|
731
|
+
// Pass B: Link named-symbol imports through barrel/re-export chains
|
|
655
732
|
for (const specToken of node.importedSymbols) {
|
|
656
733
|
const delimiterIndex = specToken.indexOf(':');
|
|
657
734
|
if (delimiterIndex === -1) continue;
|
|
@@ -662,18 +739,18 @@ export class RefactoringEngine {
|
|
|
662
739
|
if (!resolvedPath) continue;
|
|
663
740
|
|
|
664
741
|
if (symbol === '*') {
|
|
742
|
+
// Wildcard import / re-export-all: add a direct edge to the resolved file.
|
|
665
743
|
if (this.context.projectGraph.has(resolvedPath)) {
|
|
666
744
|
this.context.projectGraph.get(resolvedPath).incomingEdges.add(filePath);
|
|
667
745
|
node.outgoingEdges.add(resolvedPath);
|
|
668
746
|
}
|
|
669
747
|
} else {
|
|
748
|
+
// Named import: trace through barrel files to the actual declaration origin.
|
|
670
749
|
const traceResolution = await this.barrelParser.determineSymbolDeclarationOrigin(resolvedPath, symbol, this.context.projectGraph);
|
|
671
750
|
if (traceResolution && this.context.projectGraph.has(traceResolution.originFile)) {
|
|
672
|
-
|
|
673
|
-
originNode.incomingEdges.add(filePath);
|
|
751
|
+
this.context.projectGraph.get(traceResolution.originFile).incomingEdges.add(filePath);
|
|
674
752
|
node.outgoingEdges.add(traceResolution.originFile);
|
|
675
|
-
|
|
676
|
-
node.importedSymbols.add(`${traceResolution.originFile}:${traceResolution.originSymbol}`);
|
|
753
|
+
node.importedSymbols.add(`${traceResolution.originFile}:${traceResolution.symbolName}`);
|
|
677
754
|
}
|
|
678
755
|
}
|
|
679
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
|
+
}
|