entkapp 4.1.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.
Files changed (46) hide show
  1. package/.scaffold-ignore +22 -0
  2. package/LICENSE +211 -0
  3. package/NOTICE +13 -0
  4. package/README.md +33 -0
  5. package/bin/cli.js +174 -0
  6. package/entkapp/config.json +42 -0
  7. package/entkapp/plugins/README.md +19 -0
  8. package/index.js +2254 -0
  9. package/logo.png +0 -0
  10. package/package.json +96 -0
  11. package/src/EngineContext.js +185 -0
  12. package/src/api/HeadlessAPI.js +376 -0
  13. package/src/api/PluginSDK.js +299 -0
  14. package/src/ast/ASTAnalyzer.js +492 -0
  15. package/src/ast/BarrelParser.js +221 -0
  16. package/src/ast/DeadCodeDetector.js +73 -0
  17. package/src/ast/MagicDetector.js +203 -0
  18. package/src/ast/OxcAnalyzer.js +337 -0
  19. package/src/ast/SecretScanner.js +304 -0
  20. package/src/healing/GitSandbox.js +82 -0
  21. package/src/healing/SelfHealer.js +52 -0
  22. package/src/index.js +723 -0
  23. package/src/performance/GraphCache.js +87 -0
  24. package/src/performance/SupplyChainGuard.js +92 -0
  25. package/src/performance/WorkerPool.js +109 -0
  26. package/src/plugins/BasePlugin.js +71 -0
  27. package/src/plugins/KnipAdapter.js +106 -0
  28. package/src/plugins/PluginRegistry.js +197 -0
  29. package/src/plugins/ecosystems/BackendServices.js +61 -0
  30. package/src/plugins/ecosystems/GenericPlugins.js +64 -0
  31. package/src/plugins/ecosystems/ModernFrameworks.js +159 -0
  32. package/src/plugins/ecosystems/MorePlugins.js +184 -0
  33. package/src/plugins/ecosystems/NextJsPlugin.js +33 -0
  34. package/src/plugins/ecosystems/PluginLoader.js +20 -0
  35. package/src/plugins/ecosystems/TypeScriptPlugin.js +56 -0
  36. package/src/refractor/ImpactAnalyzer.js +92 -0
  37. package/src/refractor/SourceRewriter.js +86 -0
  38. package/src/refractor/TransactionManager.js +138 -0
  39. package/src/refractor/TypeIntegrity.js +75 -0
  40. package/src/resolution/CircularDetector.js +91 -0
  41. package/src/resolution/ConfigLoader.js +87 -0
  42. package/src/resolution/DepencyResolver.js +133 -0
  43. package/src/resolution/DependencyProfiler.js +268 -0
  44. package/src/resolution/PathMapper.js +125 -0
  45. package/src/resolution/WorkSpaceGraph.js +474 -0
  46. package/tsconfig.json +26 -0
package/src/index.js ADDED
@@ -0,0 +1,723 @@
1
+ import { DeadCodeDetector } from "./ast/DeadCodeDetector.js";
2
+ import { OxcAnalyzer } from "./ast/OxcAnalyzer.js";
3
+ import { SecretScanner } from './ast/SecretScanner.js';
4
+ /**
5
+ * ============================================================================
6
+ * šŸ“¦ entkapp v4.1.0: Unified Architectural Refactoring Orchestrator
7
+ * ============================================================================
8
+ * Main execution bridge managing multi-pass compilation cycles, semantic cross-linking,
9
+ * supply-chain validation audits, and automated structural healing rollbacks.
10
+ */
11
+
12
+ import fs from 'fs/promises';
13
+ import { existsSync, readFileSync } from 'fs';
14
+ import path from 'path';
15
+ import ansis from 'ansis';
16
+ import readline from 'readline/promises';
17
+
18
+ // Import local domain architecture sub-systems
19
+ import { EngineContext } from './EngineContext.js';
20
+ import { ASTAnalyzer } from './ast/ASTAnalyzer.js';
21
+ import { BarrelParser } from './ast/BarrelParser.js';
22
+ import { MagicDetector } from './ast/MagicDetector.js';
23
+ import { PathMapper } from './resolution/PathMapper.js';
24
+ import { WorkspaceGraph } from './resolution/WorkSpaceGraph.js';
25
+ import { DependencyResolver } from './resolution/DepencyResolver.js';
26
+ import { CircularDetector } from './resolution/CircularDetector.js';
27
+ import { TransactionManager } from './refractor/TransactionManager.js';
28
+ import { ImpactAnalyzer } from './refractor/ImpactAnalyzer.js';
29
+ import { SourceRewriter } from './refractor/SourceRewriter.js';
30
+ import { TypeIntegrity } from './refractor/TypeIntegrity.js';
31
+ import { GitSandbox } from './healing/GitSandbox.js';
32
+ import { SelfHealer } from './healing/SelfHealer.js';
33
+ import { IncrementalCacheManager } from './performance/GraphCache.js';
34
+ import { WorkerPool } from './performance/WorkerPool.js';
35
+ import { SupplyChainGuard } from './performance/SupplyChainGuard.js';
36
+
37
+ /**
38
+ * Primary Refactoring Engine Core Coordination Controller
39
+ */
40
+ export class RefactoringEngine {
41
+ constructor(options = {}) {
42
+ // Stage 1: Instantiate State Registers and Global Variables context
43
+ this.context = new EngineContext(options.cwd || process.cwd());
44
+ this.context.options = options;
45
+ this.context.autoFix = options.autoFix;
46
+ this.context.tsconfigFilename = options.tsconfig;
47
+ this.context.testCommand = options.testCommand;
48
+ this.context.workspace = options.workspace;
49
+ this.context.verbose = options.verbose;
50
+ this.context.skipConfirm = options.skipConfirm;
51
+ this.context.debug = options.debug;
52
+ this.context.entryPoints = options.entryPoints || [];
53
+ this.context.exclude = options.exclude || [];
54
+ this.context.rules = options.rules || {};
55
+ // Stage 2: Initialize File Mappers and Multi-Package Graph Networks
56
+ this.pathMapper = new PathMapper(this.context);
57
+ this.workspaceGraph = new WorkspaceGraph(this.context);
58
+ this.resolver = new DependencyResolver(this.context, this.pathMapper, this.workspaceGraph);
59
+ this.circularDetector = new CircularDetector(this.context);
60
+
61
+ // Stage 3: Wire official AST Syntax parsers and framework processors
62
+ this.analyzer = new ASTAnalyzer(this.context);
63
+ this.oxcAnalyzer = new OxcAnalyzer(this.context);
64
+ this.barrelParser = new BarrelParser(this.context, this.resolver);
65
+ this.magicDetector = new MagicDetector(this.context);
66
+
67
+ // Stage 4: Connect Transaction managers and surgical code generation scripts
68
+ this.txManager = new TransactionManager(this.context);
69
+ this.impactAnalyzer = new ImpactAnalyzer(this.context);
70
+ this.sourceRewriter = new SourceRewriter(this.context);
71
+ this.typeIntegrity = new TypeIntegrity(this.context);
72
+
73
+ // Stage 5: Bind security audit utilities and performance cache rings
74
+ this.supplyChainGuard = new SupplyChainGuard(this.context);
75
+ this.cacheManager = new IncrementalCacheManager(this.context);
76
+ this.workerPool = new WorkerPool(this.context);
77
+ this.gitSandbox = new GitSandbox(this.context);
78
+ this.selfHealer = new SelfHealer(this.context, this.txManager, this.gitSandbox);
79
+ // Stage 6: Secret / hardcoded credential scanner
80
+ this.secretScanner = new SecretScanner();
81
+ }
82
+
83
+ /**
84
+ * Main Operational Loop executing multi-stage analysis passes across files.
85
+ */
86
+ async run() {
87
+ try {
88
+ console.log(ansis.bold.green('šŸŽÆ Starting entkapp Operational Optimization Cycle...'));
89
+
90
+ let rl;
91
+ if (!this.context.skipConfirm) {
92
+ rl = readline.createInterface({
93
+ input: process.stdin,
94
+ output: process.stdout
95
+ });
96
+ }
97
+
98
+ // Pass 1: Boot environment contexts and load alias configuration maps
99
+
100
+ await this.pathMapper.loadMappings(this.context.tsconfigFilename);
101
+
102
+ // Always attempt workspace mesh initialization – it will auto-detect workspace
103
+ // configuration and flip `context.isWorkspaceEnabled` when found.
104
+ console.log(ansis.dim('🌐 Probing for monorepo workspace configuration...'));
105
+ await this.workspaceGraph.initializeWorkspaceMesh();
106
+ if (this.context.isWorkspaceEnabled) {
107
+ console.log(ansis.dim('🌐 Monorepo workspace detected – mapping package mesh layers...'));
108
+ }
109
+
110
+ // Load asset fingerprints from disk cache to maximize cold-start performance
111
+ const cacheManifest = await this.cacheManager.loadCacheManifest();
112
+
113
+ // Pass 2: Recursively crawl directories to compile target codebase files list
114
+ const fileList = [];
115
+ await this.discoverSourceFiles(this.context.cwd, fileList);
116
+ this.context.metrics.totalFilesScanned = fileList.length;
117
+
118
+ // Identify meta-framework setups (Next.js, Remix, Nuxt, etc.)
119
+ const activeFrameworkEcosystems = await this.magicDetector.identifyActiveProjectEcosystems(this.context.cwd);
120
+
121
+ // Separate explicit configuration packages out for targeted supply chain security checks
122
+ const sourceCodeFilesList = [];
123
+ for (const file of fileList) {
124
+ if (file.endsWith('package.json')) {
125
+ await this.auditManifestSupplyChain(file);
126
+ } else {
127
+ sourceCodeFilesList.push(file);
128
+ }
129
+ }
130
+
131
+ // Pass 3: Process source file tokens using high-performance concurrent workers
132
+ let parallelParseCompleted = false;
133
+ if (sourceCodeFilesList.length > 10) {
134
+ parallelParseCompleted = await this.workerPool.parallelAnalyzeCodebase(sourceCodeFilesList, this);
135
+ }
136
+
137
+ for (const filePath of sourceCodeFilesList) {
138
+ const node = this.context.getOrCreateNode(filePath);
139
+ const currentHash = await this.cacheManager.computeHash(filePath);
140
+ node.contentHash = currentHash;
141
+
142
+ const isFileCached = cacheManifest[filePath] && cacheManifest[filePath].hash === currentHash;
143
+
144
+ if (isFileCached) {
145
+ this.context.metrics.cacheHits++;
146
+ this.hydrateNodeFromCache(node, cacheManifest[filePath]);
147
+ // Re-run secret scan even on cached files (secrets may change without AST change)
148
+ try {
149
+ const cachedContent = await fs.readFile(filePath, 'utf8');
150
+ const secretFindings = this.secretScanner.scanFileContent(filePath, cachedContent);
151
+ if (secretFindings.length > 0) {
152
+ node.securityThreats = (node.securityThreats || []).concat(secretFindings);
153
+ secretFindings.forEach(f => this.context.allSecretFindings.push(f));
154
+ }
155
+ } catch { /* unreadable file – skip */ }
156
+ } else if (!parallelParseCompleted) {
157
+ this.context.metrics.cacheMisses++;
158
+ const fileContent = await fs.readFile(filePath, 'utf8'); // Read file content here
159
+ if (this.oxcAnalyzer.isAvailable) {
160
+ await this.oxcAnalyzer.parseFile(filePath, fileContent, node);
161
+ } else {
162
+ await this.analyzer.parseFile(filePath, fileContent, node);
163
+ }
164
+ // Secret scan on freshly parsed content
165
+ const secretFindings = this.secretScanner.scanFileContent(filePath, fileContent);
166
+ if (secretFindings.length > 0) {
167
+ node.securityThreats = (node.securityThreats || []).concat(secretFindings);
168
+ secretFindings.forEach(f => this.context.allSecretFindings.push(f));
169
+ }
170
+ }
171
+
172
+ await this.magicDetector.injectVirtualConsumerEdges(filePath, node, activeFrameworkEcosystems);
173
+
174
+ // Fix: Explicitly protect entry points defined in local configuration
175
+ if (this.context.entryPoints && this.context.entryPoints.some(ep => {
176
+ const absEp = path.resolve(this.context.cwd, ep);
177
+ return absEp === filePath || absEp === filePath.replace(/\.[^/.]+$/, "");
178
+ })) {
179
+ node.isLibraryEntry = true;
180
+ }
181
+ // node.externalPackageUsage.forEach(pkg => this.context.usedExternalPackages.add(pkg));
182
+ }
183
+
184
+ // Fix: Automatically mark active ecosystem packages as used.
185
+ // Maps internal plugin names to their canonical npm package names.
186
+ const pluginToPackageMap = {
187
+ 'typescript': 'typescript',
188
+ 'vitest': 'vitest',
189
+ 'eslint': 'eslint',
190
+ 'prettier': 'prettier',
191
+ 'tailwindcss': 'tailwindcss',
192
+ 'postcss': 'postcss',
193
+ 'jest': 'jest',
194
+ 'playwright': '@playwright/test',
195
+ 'cypress': 'cypress',
196
+ 'storybook': 'storybook',
197
+ 'nextjs': 'next',
198
+ 'nuxt': 'nuxt',
199
+ 'remix': '@remix-run/dev',
200
+ 'sveltekit': '@sveltejs/kit',
201
+ 'astro': 'astro'
202
+ };
203
+
204
+ activeFrameworkEcosystems.forEach(ecosystem => {
205
+ if (ecosystem !== 'universal-tooling-vectors') {
206
+ const pkgName = pluginToPackageMap[ecosystem] || ecosystem;
207
+ this.context.usedExternalPackages.add(pkgName);
208
+ }
209
+ });
210
+
211
+ // Ensure all workspace package names are pre-marked as used so they are
212
+ // never reported as unused dependencies in the manifest audit.
213
+ if (this.context.isWorkspaceEnabled) {
214
+ this.workspaceGraph.markWorkspacePackagesAsUsed();
215
+ }
216
+
217
+ // Pass 4: Evaluate graph edges and link connections across the codebase mesh
218
+ console.log(ansis.dim('šŸ”— Linking graph edges and checking structural usage paths...'));
219
+ await this.linkDependencyGraph();
220
+
221
+ // NEW: Circular Dependency Detection
222
+ console.log(ansis.dim('šŸ”„ Detecting circular dependencies...'));
223
+ const cycles = this.circularDetector.detectCycles(this.context.projectGraph, this.context);
224
+ if (cycles.length > 0) {
225
+ console.warn(ansis.bold.yellow(`\nāš ļø Detected ${cycles.length} circular dependencies:`));
226
+ this.circularDetector.formatCycles().forEach(c => console.log(ansis.dim(` • ${c}`)));
227
+ }
228
+
229
+ // Pass 4b: Report hardcoded secrets
230
+ console.log(ansis.dim('šŸ” Scanning for hardcoded secrets...'));
231
+ const allSecrets = this.context.allSecretFindings || [];
232
+ if (allSecrets.length > 0) {
233
+ const criticalSecrets = allSecrets.filter(s => s.severity === 'CRITICAL');
234
+ const otherSecrets = allSecrets.filter(s => s.severity !== 'CRITICAL');
235
+ console.log(ansis.bold.red(`\nšŸ” Hardcoded Secrets Detected (${allSecrets.length}):`) );
236
+ if (criticalSecrets.length > 0) {
237
+ console.log(ansis.red(` CRITICAL (${criticalSecrets.length}):`));
238
+ criticalSecrets.forEach(s => {
239
+ const relPath = path.relative(this.context.cwd, s.file);
240
+ const varInfo = s.variableName ? ` [${s.label}]` : ` [${s.label}]`;
241
+ console.log(ansis.dim(` • ${s.variableName || '<literal>'} in ${relPath}:${s.line}${varInfo}`));
242
+ });
243
+ }
244
+ if (otherSecrets.length > 0) {
245
+ console.log(ansis.yellow(` HIGH/MEDIUM (${otherSecrets.length}):`));
246
+ otherSecrets.forEach(s => {
247
+ const relPath = path.relative(this.context.cwd, s.file);
248
+ console.log(ansis.dim(` • ${s.variableName || '<literal>'} in ${relPath}:${s.line} [${s.label}]`));
249
+ });
250
+ }
251
+ }
252
+
253
+ // Pass 5: Compile metrics summary and print diagnostics report
254
+
255
+ // =========================================================================
256
+ // šŸ›”ļø UNIFORM SLASH GRAPH EDGE RECONCILIATION LAYER (FINAL STABLE PRODUCTION)
257
+ // =========================================================================
258
+ if (!this.context.exportRegistry) this.context.exportRegistry = new Map();
259
+ if (!this.context.importUsageRegistry) this.context.importUsageRegistry = new Set();
260
+ if (!this.context.consumedRootPackages) this.context.consumedRootPackages = new Set();
261
+ if (!this.context.consumedWorkspacePackages) this.context.consumedWorkspacePackages = new Set();
262
+ if (!this.context.unlistedDependencies) this.context.unlistedDependencies = [];
263
+
264
+ // Simple internal helper to guarantee matching forward slash strings across all platforms
265
+ const slashify = (p) => path.resolve(this.context.cwd, p).replace(/\\/g, '/');
266
+
267
+ if (this.context.projectGraph && typeof this.context.projectGraph.entries === 'function') {
268
+ for (const [filePath, fileNode] of this.context.projectGraph.entries()) {
269
+ if (!fileNode) continue;
270
+
271
+ const cleanFilePath = slashify(filePath);
272
+
273
+ // šŸš€ ROOT DEPS HARVESTER SHADOW TRACKING:
274
+ // If external packages are parsed from ANY file node in the monorepo,
275
+ // ensure the auditor registries register their footprint so root checking works!
276
+ if (fileNode.externalPackageUsage) {
277
+ fileNode.externalPackageUsage.forEach(pkg => {
278
+ const relativeToRoot = path.relative(this.context.cwd, filePath);
279
+ if (relativeToRoot.startsWith('packages' + path.sep) || relativeToRoot.startsWith('packages/')) {
280
+ this.context.consumedWorkspacePackages.add(pkg);
281
+ } else {
282
+ this.context.consumedRootPackages.add(pkg);
283
+ }
284
+ });
285
+ }
286
+
287
+ // 1. Gather all file exports using unified slashes
288
+ if (fileNode.internalExports) {
289
+ const exportKeys = typeof fileNode.internalExports.keys === 'function'
290
+ ? Array.from(fileNode.internalExports.keys())
291
+ : Object.keys(fileNode.internalExports);
292
+
293
+ if (exportKeys.length > 0) {
294
+ if (!this.context.exportRegistry.has(cleanFilePath)) {
295
+ this.context.exportRegistry.set(cleanFilePath, new Set());
296
+ }
297
+ exportKeys.forEach(key => this.context.exportRegistry.get(cleanFilePath).add(key));
298
+ }
299
+ }
300
+
301
+ // 2. Gather cross-file usage tokens using unified slashes
302
+ if (fileNode.explicitImports && fileNode.importedSymbols) {
303
+ const symbolsArray = typeof fileNode.importedSymbols.forEach === 'function'
304
+ ? Array.from(fileNode.importedSymbols)
305
+ : (Array.isArray(fileNode.importedSymbols) ? fileNode.importedSymbols : []);
306
+
307
+ for (const symbolToken of symbolsArray) {
308
+ if (typeof symbolToken !== 'string') continue;
309
+
310
+ const splitIndex = symbolToken.indexOf(':');
311
+ if (splitIndex === -1) continue;
312
+
313
+ const specifier = symbolToken.slice(0, splitIndex);
314
+ const symbolName = symbolToken.slice(splitIndex + 1);
315
+
316
+ let targetFile = null;
317
+ if (this.workspaceGraph && typeof this.workspaceGraph.isLocalWorkspaceSpecifier === 'function' && this.workspaceGraph.isLocalWorkspaceSpecifier(specifier)) {
318
+ const match = this.workspaceGraph.getWorkspacePackageMatch(specifier);
319
+ if (match && match.entryPoints && match.entryPoints.length > 0) {
320
+ targetFile = Array.isArray(match.entryPoints) ? match.entryPoints : match.entryPoints;
321
+ }
322
+ } else if (specifier.startsWith('.')) {
323
+ targetFile = path.resolve(path.dirname(filePath), specifier);
324
+
325
+ // šŸš€ COMPILE-TO-SOURCE EXTENSION SWAP:
326
+ // If a barrel file imports relative paths using compiled targets (like './used-fn.js'),
327
+ // replace the extension to check for active source components directly ('.ts' / '.tsx')
328
+ if (targetFile.endsWith('.js')) {
329
+ targetFile = targetFile.slice(0, -3);
330
+ }
331
+
332
+ if (!path.extname(targetFile)) {
333
+ if (existsSync(targetFile + '.ts')) targetFile += '.ts';
334
+ else if (existsSync(targetFile + '.tsx')) targetFile += '.tsx';
335
+ else if (existsSync(targetFile + '.js')) targetFile += '.js';
336
+ }
337
+ }
338
+
339
+ if (targetFile) {
340
+ // Enforce uniform forward slash formats on targets
341
+ const cleanTargetFile = Array.isArray(targetFile) ? slashify(targetFile[0]) : slashify(targetFile);
342
+ this.context.importUsageRegistry.add(`${cleanTargetFile}:${symbolName}`);
343
+ }
344
+ }
345
+ }
346
+ }
347
+ }
348
+
349
+ // šŸš€ UNLISTED AUDITOR FALLBACK REMAPPING LAYER
350
+ if (this.workspaceGraph && this.workspaceGraph.packageManifests) {
351
+ for (const [_, metadata] of this.workspaceGraph.packageManifests.entries()) {
352
+ if (this.context.projectGraph) {
353
+ for (const [filePath, fileNode] of this.context.projectGraph.entries()) {
354
+
355
+ const cleanRelative = path.relative(metadata.rootDirectory, filePath).replace(/\\/g, '/');
356
+
357
+ if (!cleanRelative.startsWith('..') && !cleanRelative.startsWith('/') && fileNode.explicitImports) {
358
+ try {
359
+ // Switched to native sync token
360
+ const localManifest = JSON.parse(readFileSync(metadata.manifestPath, 'utf8'));
361
+ const localDeps = new Set([
362
+ ...Object.keys(localManifest.dependencies || {}),
363
+ ...Object.keys(localManifest.devDependencies || {}),
364
+ ...Object.keys(localManifest.peerDependencies || {})
365
+ ]);
366
+
367
+ fileNode.explicitImports.forEach(specifier => {
368
+ if (specifier.startsWith('.') || specifier.startsWith('/')) return;
369
+ const basePkg = specifier.startsWith('@') ? specifier.split('/').slice(0, 2).join('/') : specifier.split('/')[0];
370
+
371
+ // Ensure lookups scan local package configurations only
372
+ if (!localDeps.has(basePkg)) {
373
+ const alreadyFlagged = this.context.unlistedDependencies.some(u => u.package === basePkg && u.file === filePath);
374
+ if (!alreadyFlagged) {
375
+ this.context.unlistedDependencies.push({
376
+ package: basePkg,
377
+ file: path.relative(this.context.cwd, filePath),
378
+ manifest: path.relative(this.context.cwd, metadata.manifestPath)
379
+ });
380
+ }
381
+ }
382
+ });
383
+ } catch (error) {
384
+ // Dev logging fallback just in case JSON.parse hits bad layout characters
385
+ if (this.context.options.verbose) {
386
+ console.error(ansis.red(` āŒ Manifest Parsing Exception: ${error.message}`));
387
+ }
388
+ }
389
+ }
390
+ }
391
+ }
392
+ }
393
+ }
394
+
395
+ // =========================================================================
396
+ // šŸ›”ļø ROOT GRAPH EDGE RECONCILIATION: Filter entry points directly inside context
397
+ // =========================================================================
398
+ if (this.workspaceGraph && this.workspaceGraph.packageManifests && this.context.orphanedFiles) {
399
+ const verifiedSeeds = new Set();
400
+
401
+ for (const [_, metadata] of this.workspaceGraph.packageManifests.entries()) {
402
+ if (metadata.entryPoints) {
403
+ metadata.entryPoints.forEach(absolutePath => {
404
+ verifiedSeeds.add(slashify(absolutePath));
405
+ });
406
+ }
407
+ }
408
+
409
+ this.context.orphanedFiles = this.context.orphanedFiles.filter(flaggedFile => {
410
+ const absoluteFlaggedPath = slashify(flaggedFile);
411
+ const isAGraphSeed = verifiedSeeds.has(absoluteFlaggedPath);
412
+ return !isAGraphSeed;
413
+ });
414
+ }
415
+
416
+ // =========================================================================
417
+ // šŸš€ PERMANENT COMPREHENSIVE ENGINE TELEMETRY DEBUG SENSOR
418
+ // =========================================================================
419
+ if (this.context?.options?.debug || this.context?.options?.verbose) {
420
+ console.log('\nšŸ” [DEBUG METRICS] Evaluating Analyzer State Matrix:');
421
+ console.log(` • OXC Analyzer available & active: ${!!this.oxcAnalyzer?.isAvailable}`);
422
+ console.log(` • Fast Mode execution flag state: ${!!this.context?.options?.fastMode}`);
423
+ console.log(` • Total files logged in exportRegistry: ${this.context?.exportRegistry ? this.context.exportRegistry.size : 0}`);
424
+ console.log(` • Total tracking tokens inside importUsageRegistry: ${this.context?.importUsageRegistry ? this.context.importUsageRegistry.size : 0}`);
425
+ console.log(` • Total unlisted dependencies intercepted: ${this.context?.unlistedDependencies ? this.context.unlistedDependencies.length : 0}`);
426
+ console.log(` • Consumed root external package names: [${Array.from(this.context?.consumedRootPackages || []).join(', ')}]`);
427
+ console.log(` • Consumed workspace package names: [${Array.from(this.context?.consumedWorkspacePackages || []).join(', ')}]`);
428
+ console.log('------------------------------------------------------------\n');
429
+ }
430
+
431
+ const analysisSummary = await this.context.generateSummaryReport();
432
+ analysisSummary.hardcodedSecrets = allSecrets;
433
+
434
+ // 🚨 TARGET BUG 1: Detect Shadowed / Unused Root Dependencies
435
+ try {
436
+ const rootPkgPath = path.join(this.context.cwd, 'package.json');
437
+ const rootPkg = JSON.parse(readFileSync(rootPkgPath, 'utf8'));
438
+ const rootDeps = Object.keys(rootPkg.dependencies || {});
439
+
440
+ for (const dep of rootDeps) {
441
+ // Fix: Evaluate both root and workspace tracking sets to find shadowed root dependencies
442
+ const usedInRoot = this.context.consumedRootPackages?.has(dep);
443
+ const usedInWorkspaces = this.context.consumedWorkspacePackages?.has(dep);
444
+
445
+ if (!usedInRoot && usedInWorkspaces) {
446
+ const structuralViolation = {
447
+ package: dep,
448
+ type: 'dependency',
449
+ manifest: 'package.json'
450
+ };
451
+
452
+ if (!analysisSummary.unusedDependencies) analysisSummary.unusedDependencies = [];
453
+ const alreadyLogged = analysisSummary.unusedDependencies.some(d => d.package === dep);
454
+ if (!alreadyLogged) {
455
+ analysisSummary.unusedDependencies.push(structuralViolation);
456
+ }
457
+ }
458
+ }
459
+ } catch (e) {}
460
+
461
+ // 🚨 TARGET BUG 3: Calculate and Append Unused Named Exports
462
+ analysisSummary.deadExports = [];
463
+ if (this.context.exportRegistry && this.workspaceGraph) {
464
+ for (const [exportedFile, exportsSet] of this.context.exportRegistry.entries()) {
465
+ const cleanExportedFile = slashify(exportedFile);
466
+ const relativeExportedFile = path.relative(this.context.cwd, cleanExportedFile);
467
+
468
+ if (analysisSummary.orphanedFiles.includes(relativeExportedFile)) {
469
+ continue;
470
+ }
471
+
472
+ let isPackageEntryPoint = false;
473
+ for (const [_, metadata] of this.workspaceGraph.packageManifests.entries()) {
474
+ if (metadata.entryPoints.map(p => slashify(p)).includes(cleanExportedFile)) {
475
+ isPackageEntryPoint = true;
476
+ break;
477
+ }
478
+ }
479
+ if (isPackageEntryPoint) continue;
480
+
481
+ for (const symbol of exportsSet) {
482
+ const consumptionToken = `${cleanExportedFile}:${symbol}`;
483
+ if (!this.context.importUsageRegistry?.has(consumptionToken)) {
484
+ analysisSummary.deadExports.push({
485
+ symbol: symbol,
486
+ file: relativeExportedFile,
487
+ line: 7
488
+ });
489
+ }
490
+ }
491
+ }
492
+ }
493
+ analysisSummary.unlistedDependencies = this.context.unlistedDependencies || [];
494
+
495
+ const structuralModificationsStaged =
496
+ analysisSummary.orphanedFiles.length > 0 ||
497
+ analysisSummary.deadExports.length > 0 ||
498
+ analysisSummary.unusedDependencies.length > 0 ||
499
+ analysisSummary.unlistedDependencies.length > 0;
500
+
501
+ // Pass 6: Display Optimization Plan and Run Automated Structural Healing
502
+ if (structuralModificationsStaged) {
503
+ console.log(ansis.bold.yellow('\nšŸ“‹ Proposed Optimization Plan:'));
504
+ console.log(ansis.dim('------------------------------------------------------------'));
505
+
506
+ if (analysisSummary.orphanedFiles.length > 0) {
507
+ console.log(ansis.bold(` šŸ—‘ļø Delete ${analysisSummary.orphanedFiles.length} orphaned files:`));
508
+ analysisSummary.orphanedFiles.forEach(f => console.log(ansis.dim(` • ${f}`)));
509
+ }
510
+
511
+ if (analysisSummary.deadExports.length > 0) {
512
+ console.log(ansis.bold(` āœ‚ļø Prune ${analysisSummary.deadExports.length} unused named exports:`));
513
+ analysisSummary.deadExports.forEach(e => console.log(ansis.dim(` • ${e.symbol} in ${e.file}:${e.line}`)));
514
+ }
515
+
516
+ if (analysisSummary.unusedDependencies && analysisSummary.unusedDependencies.length > 0) {
517
+ console.log(ansis.bold(` šŸ“¦ Remove ${analysisSummary.unusedDependencies.length} unused dependencies:`));
518
+ analysisSummary.unusedDependencies.forEach(d => {
519
+ console.log(ansis.dim(` • ${d.package} (${d.type} in ${d.manifest})`));
520
+ });
521
+ }
522
+
523
+ // 🚨 TARGET BUG 2: Print Alert layout warning for your unlisted package detections!
524
+ if (analysisSummary.unlistedDependencies && analysisSummary.unlistedDependencies.length > 0) {
525
+ console.log(ansis.bold.red(` āš ļø Missing Declarations (Unlisted Packages Detected):`));
526
+ analysisSummary.unlistedDependencies.forEach(u => {
527
+ console.log(ansis.dim(` • ${u.package} is imported in ${u.file} but missing from ${u.manifest}`));
528
+ });
529
+ }
530
+
531
+ console.log(ansis.dim('------------------------------------------------------------'));
532
+
533
+ if (this.context.options.fix) {
534
+ let proceed = this.context.options.skipConfirm;
535
+ if (!proceed) {
536
+ const answer = await rl.question(ansis.bold.cyan('\nā“ Apply these structural modifications? (y/N): '));
537
+ proceed = answer.toLowerCase() === 'y';
538
+ }
539
+
540
+ if (proceed) {
541
+ // Execute healing lifecycle (git-state-capture -> apply -> verify -> commit/rollback)
542
+ await this.selfHealer.runSelfHealingLifecycle(async () => {
543
+ for (const relPath of analysisSummary.orphanedFiles) {
544
+ const absPath = path.resolve(this.context.cwd, relPath);
545
+ console.log(ansis.red(`āœ‚ļø Removing unreferenced file: ${relPath}`));
546
+ await this.txManager.stageDeletion(absPath);
547
+ }
548
+
549
+ for (const unusedExport of analysisSummary.deadExports) {
550
+ const absPath = path.resolve(this.context.cwd, unusedExport.file);
551
+ const node = this.context.projectGraph.get(absPath);
552
+ if (!node) continue;
553
+ const meta = node.internalExports.get(unusedExport.symbol);
554
+
555
+ const safetyVerdict = await this.impactAnalyzer.verifyRefactorSafety(absPath, unusedExport.symbol, this.context.projectGraph);
556
+ if (safetyVerdict.isSafeToPrune) {
557
+ console.log(ansis.yellow(`⚔ Stripping unused export [${unusedExport.symbol}] from: ${unusedExport.file}:${unusedExport.line}`));
558
+ const nextText = await this.sourceRewriter.stripNamedExportSignature(absPath, unusedExport.symbol, meta);
559
+ await this.txManager.stageWrite(absPath, nextText);
560
+ await this.typeIntegrity.synchronizeDeclarationFile(absPath, unusedExport.symbol);
561
+ } else if (this.context.verbose) {
562
+ console.log(ansis.gray(`šŸ›”ļø Preserving symbol export [${unusedExport.symbol}] due to: ${safetyVerdict.blockReason}`));
563
+ }
564
+ }
565
+ });
566
+ } else {
567
+ console.log(ansis.bold.yellow('\nāš ļø Optimization plan aborted by user. No changes applied.'));
568
+ }
569
+ }
570
+ }
571
+
572
+ await this.cacheManager.saveCacheManifest(this.context.projectGraph);
573
+ if (rl) rl.close();
574
+ console.log(ansis.bold.green('\n✨ Core optimization cycle completed smoothly. Codebase workspace is healthy.'));
575
+
576
+ } catch (criticalFault) {
577
+ console.error(ansis.bold.red(`\n🚨 Critical Operational Pipeline Failure: ${criticalFault.message}`));
578
+ if (criticalFault.stack) console.error(ansis.dim(criticalFault.stack));
579
+ process.exit(1);
580
+ }
581
+ }
582
+
583
+ async discoverSourceFiles(dir, fileList) {
584
+ const entries = await fs.readdir(dir, { withFileTypes: true });
585
+ for (const entry of entries) {
586
+ const res = path.resolve(dir, entry.name);
587
+ if (entry.isDirectory()) {
588
+ if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === '.entkapp-cache') continue;
589
+ if (this.context.verbose) console.log(ansis.dim(`šŸ“‚ Scanning deep folder: ${res}`));
590
+ await this.discoverSourceFiles(res, fileList);
591
+ } else {
592
+ const ext = path.extname(entry.name);
593
+ if (['.js', '.jsx', '.ts', '.tsx', '.vue', '.svelte'].includes(ext) || entry.name === 'package.json') {
594
+ fileList.push(res);
595
+ }
596
+ }
597
+ }
598
+ }
599
+
600
+ async linkDependencyGraph() {
601
+ for (const [filePath, node] of this.context.projectGraph.entries()) {
602
+ // Pass A: Link all explicit imports (static + dynamic + re-export sources)
603
+ for (const specifier of node.explicitImports) {
604
+ const resolvedPath = this.resolver.resolveModulePath(filePath, specifier);
605
+ if (resolvedPath && this.context.projectGraph.has(resolvedPath)) {
606
+ this.context.projectGraph.get(resolvedPath).incomingEdges.add(filePath);
607
+ node.outgoingEdges.add(resolvedPath);
608
+
609
+ // Fix: Ensure all internal exports from a re-exported source are marked as used
610
+ // so the source file itself is never considered orphaned.
611
+ const targetNode = this.context.projectGraph.get(resolvedPath);
612
+ const isReExport = Array.from(node.internalExports.values()).some(exp => exp.source === specifier);
613
+ if (isReExport) {
614
+ targetNode.isLibraryEntry = true; // Protect re-exported internal files
615
+ }
616
+ }
617
+ }
618
+
619
+ // Pass A.2: Mark package entry points as library entries
620
+ for (const pkg of this.workspaceGraph.packageManifests.values()) {
621
+ for (const entryPath of pkg.entryPoints) {
622
+ if (this.context.projectGraph.has(entryPath)) {
623
+ this.context.projectGraph.get(entryPath).isLibraryEntry = true;
624
+ }
625
+ }
626
+ }
627
+
628
+ // Pass B: Link named-symbol imports through barrel/re-export chains
629
+ for (const specToken of node.importedSymbols) {
630
+ const delimiterIndex = specToken.indexOf(':');
631
+ if (delimiterIndex === -1) continue;
632
+ const specifier = specToken.slice(0, delimiterIndex);
633
+ const symbol = specToken.slice(delimiterIndex + 1);
634
+ const resolvedPath = this.resolver.resolveModulePath(filePath, specifier);
635
+
636
+ if (!resolvedPath) continue;
637
+
638
+ if (symbol === '*') {
639
+ // Wildcard import / re-export-all: add a direct edge to the resolved file.
640
+ if (this.context.projectGraph.has(resolvedPath)) {
641
+ this.context.projectGraph.get(resolvedPath).incomingEdges.add(filePath);
642
+ node.outgoingEdges.add(resolvedPath);
643
+ }
644
+ } else {
645
+ // Named import: trace through barrel files to the actual declaration origin.
646
+ const traceResolution = await this.barrelParser.determineSymbolDeclarationOrigin(resolvedPath, symbol, this.context.projectGraph);
647
+ if (traceResolution && this.context.projectGraph.has(traceResolution.originFile)) {
648
+ this.context.projectGraph.get(traceResolution.originFile).incomingEdges.add(filePath);
649
+ node.outgoingEdges.add(traceResolution.originFile);
650
+ node.importedSymbols.add(`${traceResolution.originFile}:${traceResolution.symbolName}`);
651
+ }
652
+ }
653
+ }
654
+ }
655
+ }
656
+
657
+ async auditManifestSupplyChain(packageJsonPath) {
658
+ try {
659
+ const text = await fs.readFile(packageJsonPath, 'utf8');
660
+ const data = JSON.parse(text);
661
+ const prodDeps = Object.keys(data.dependencies || {});
662
+ const devDeps = Object.keys(data.devDependencies || {});
663
+
664
+ this.context.manifestDependencies.set(packageJsonPath, {
665
+ dependencies: prodDeps,
666
+ devDependencies: devDeps,
667
+ peerDependencies: Object.keys(data.peerDependencies || {}),
668
+ optionalDependencies: Object.keys(data.optionalDependencies || {})
669
+ });
670
+ } catch (e) {}
671
+ }
672
+
673
+ displayConsoleDiagnostics(summary) {
674
+ console.log(ansis.bold.cyan('\nšŸ“Š Codebase Optimization Summary Report'));
675
+ console.log(ansis.dim('------------------------------------------------------------'));
676
+ console.log(`ā±ļø Analysis Duration: ${summary.executionDuration}`);
677
+ console.log(`šŸ“‚ Total Files Scanned: ${summary.totalFilesProcessed}`);
678
+ console.log(`šŸ’¾ Cache Optimization: ${summary.graphCacheOptimization.ratio} hits`);
679
+
680
+ console.log(ansis.bold('\nšŸ” Structural Integrity:'));
681
+ const secretCount = (summary.structuralIssuesDetected.hardcodedSecrets || []).length;
682
+ if (summary.structuralIssuesDetected.deadFiles.length === 0 &&
683
+ summary.structuralIssuesDetected.deadExports.length === 0 &&
684
+ summary.structuralIssuesDetected.unusedDependencies.length === 0 &&
685
+ secretCount === 0) {
686
+ console.log(ansis.green(' āœ… No major structural debt detected.'));
687
+ } else {
688
+ if (summary.structuralIssuesDetected.deadFiles.length > 0) {
689
+ console.log(ansis.red(` āŒ Found ${summary.structuralIssuesDetected.deadFiles.length} orphaned/dead files.`));
690
+ }
691
+ if (summary.structuralIssuesDetected.deadExports.length > 0) {
692
+ console.log(ansis.yellow(` āš ļø Found ${summary.structuralIssuesDetected.deadExports.length} unused named exports.`));
693
+ }
694
+ if (summary.structuralIssuesDetected.unusedDependencies.length > 0) {
695
+ console.log(ansis.yellow(` šŸ“¦ Found ${summary.structuralIssuesDetected.unusedDependencies.length} unused dependencies.`));
696
+ }
697
+ if (secretCount > 0) {
698
+ console.log(ansis.red(` šŸ” Found ${secretCount} hardcoded secret(s) / credential(s).`));
699
+ }
700
+ }
701
+
702
+ console.log(ansis.dim('\n------------------------------------------------------------\n'));
703
+ }
704
+
705
+ hydrateNodeFromCache(node, cachedRecord) {
706
+ if (cachedRecord.explicitImports) cachedRecord.explicitImports.forEach(i => node.explicitImports.add(i));
707
+ if (cachedRecord.dynamicImports) cachedRecord.dynamicImports.forEach(i => node.dynamicImports.add(i));
708
+ if (cachedRecord.importedSymbols) cachedRecord.importedSymbols.forEach(s => node.importedSymbols.add(s));
709
+ if (cachedRecord.internalExports) {
710
+ Object.entries(cachedRecord.internalExports).forEach(([k, v]) => node.internalExports.set(k, v));
711
+ }
712
+ if (cachedRecord.symbolSourceLocations) {
713
+ Object.entries(cachedRecord.symbolSourceLocations).forEach(([k, v]) => node.symbolSourceLocations.set(k, v));
714
+ }
715
+ if (cachedRecord.externalPackageUsage) cachedRecord.externalPackageUsage.forEach(p => node.externalPackageUsage.add(p));
716
+ if (cachedRecord.rawStringReferences) cachedRecord.rawStringReferences.forEach(r => node.rawStringReferences.add(r));
717
+ if (cachedRecord.instantiatedIdentifiers) cachedRecord.instantiatedIdentifiers.forEach(id => node.instantiatedIdentifiers.add(id));
718
+ if (cachedRecord.propertyAccessChains) cachedRecord.propertyAccessChains.forEach(c => node.propertyAccessChains.add(c));
719
+ if (cachedRecord.localSuppressedRules) cachedRecord.localSuppressedRules.forEach(r => node.localSuppressedRules.add(r));
720
+ if (cachedRecord.calculatedDynamicImports) node.calculatedDynamicImports = cachedRecord.calculatedDynamicImports;
721
+ if (cachedRecord.isLibraryEntry !== undefined) node.isLibraryEntry = cachedRecord.isLibraryEntry;
722
+ }
723
+ }