entkapp 5.5.0 → 5.6.1

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 (62) hide show
  1. package/README.md +1 -1
  2. package/bin/cli.js +2 -2
  3. package/bin/cli.mjs +175 -0
  4. package/index.cjs +18 -0
  5. package/index.mjs +51 -0
  6. package/package.json +7 -6
  7. package/src/EngineContext.mjs +428 -0
  8. package/src/Initializer.mjs +82 -0
  9. package/src/analyzers/CodeSmellAnalyzer.mjs +106 -0
  10. package/src/analyzers/OxcAnalyzer.mjs +11 -0
  11. package/src/api/HeadlessAPI.js +31 -16
  12. package/src/api/HeadlessAPI.mjs +369 -0
  13. package/src/api/PluginSDK.mjs +135 -0
  14. package/src/ast/ASTAnalyzer.js +17 -3
  15. package/src/ast/ASTAnalyzer.mjs +742 -0
  16. package/src/ast/AdvancedAnalysis.mjs +586 -0
  17. package/src/ast/BarrelParser.mjs +230 -0
  18. package/src/ast/DeadCodeDetector.js +7 -5
  19. package/src/ast/DeadCodeDetector.mjs +92 -0
  20. package/src/ast/MagicDetector.mjs +203 -0
  21. package/src/ast/OxcAnalyzer.mjs +188 -0
  22. package/src/ast/SecretScanner.mjs +374 -0
  23. package/src/healing/GitSandbox.mjs +82 -0
  24. package/src/healing/SelfHealer.mjs +48 -0
  25. package/src/index.js +37 -1
  26. package/src/index.mjs +1180 -0
  27. package/src/performance/GraphCache.mjs +108 -0
  28. package/src/performance/SupplyChainGuard.mjs +92 -0
  29. package/src/performance/WorkerPool.mjs +132 -0
  30. package/src/performance/WorkerTaskRunner.mjs +144 -0
  31. package/src/plugins/BasePlugin.mjs +240 -0
  32. package/src/plugins/PluginRegistry.mjs +203 -0
  33. package/src/plugins/ecosystems/BackendServices.mjs +197 -0
  34. package/src/plugins/ecosystems/GenericPlugins.mjs +142 -0
  35. package/src/plugins/ecosystems/ModernFrameworks.mjs +162 -0
  36. package/src/plugins/ecosystems/MorePlugins.mjs +562 -0
  37. package/src/plugins/ecosystems/NewPlugins.mjs +526 -0
  38. package/src/plugins/ecosystems/NextJsPlugin.mjs +45 -0
  39. package/src/plugins/ecosystems/PluginLoader.mjs +193 -0
  40. package/src/plugins/ecosystems/TypeScriptPlugin.mjs +56 -0
  41. package/src/plugins/ecosystems/UltimateBundle.mjs +1182 -0
  42. package/src/refractor/ImpactAnalyzer.mjs +92 -0
  43. package/src/refractor/SourceRewriter.mjs +86 -0
  44. package/src/refractor/TransactionManager.mjs +5 -0
  45. package/src/refractor/TypeIntegrity.mjs +4 -0
  46. package/src/resolution/BuildOrchestrator.mjs +46 -0
  47. package/src/resolution/CircularDetector.mjs +91 -0
  48. package/src/resolution/ConfigGenerator.mjs +83 -0
  49. package/src/resolution/ConfigLoader.mjs +94 -0
  50. package/src/resolution/DepencyResolver.mjs +66 -0
  51. package/src/resolution/DependencyFixer.mjs +88 -0
  52. package/src/resolution/DependencyProfiler.mjs +286 -0
  53. package/src/resolution/EntryPointDetector.mjs +134 -0
  54. package/src/resolution/FrameworkConfigParser.mjs +119 -0
  55. package/src/resolution/GraphAnalyzer.mjs +80 -0
  56. package/src/resolution/MigrationAnalyzer.mjs +60 -0
  57. package/src/resolution/PathMapper.mjs +82 -0
  58. package/src/resolution/TSConfigLoader.mjs +162 -0
  59. package/src/resolution/WorkSpaceGraph.mjs +183 -0
  60. package/src/resolution/WorkspaceDiagnostic.mjs +139 -0
  61. package/test.js +76 -0
  62. package/wrangler.toml +6 -0
@@ -14,7 +14,8 @@
14
14
 
15
15
  import EventEmitter from 'events';
16
16
  import path from 'path';
17
- // import { RefactoringEngine } from '../index.js';
17
+ import fs from 'fs/promises';
18
+ import { RefactoringEngine } from '../index.js';
18
19
 
19
20
  export class HeadlessAPI extends EventEmitter {
20
21
  constructor(options = {}) {
@@ -105,16 +106,7 @@ export class HeadlessAPI extends EventEmitter {
105
106
  }
106
107
  }
107
108
 
108
- // Initialize TypeScript program for AST analysis (required before processFile)
109
- if (sourceCodeFilesList.length > 0) {
110
- try {
111
- this.engine.analyzer.initProgram(sourceCodeFilesList);
112
- } catch (e) {
113
- if (this.engine.context.verbose) {
114
- console.warn('Warning: Failed to initialize TypeScript program:', e.message);
115
- }
116
- }
117
- }
109
+ // ASTAnalyzer doesn't need initProgram in this version
118
110
 
119
111
  // Parallel processing
120
112
  let parallelParseCompleted = false;
@@ -139,7 +131,9 @@ export class HeadlessAPI extends EventEmitter {
139
131
  this.engine.hydrateNodeFromCache(node, cacheManifest[filePath]);
140
132
  } else if (!parallelParseCompleted) {
141
133
  this.engine.context.metrics.cacheMisses++;
142
- await this.engine.analyzer.parseFile(filePath, "", node);
134
+ const fs = await import('fs/promises');
135
+ const fileContent = await fs.readFile(filePath, 'utf8');
136
+ await this.engine.analyzer.parseFile(filePath, fileContent, node);
143
137
  }
144
138
 
145
139
  this.engine.magicDetector.injectVirtualConsumerEdges(filePath, node, activeFrameworkEcosystems);
@@ -161,7 +155,7 @@ export class HeadlessAPI extends EventEmitter {
161
155
  this.emit('analysis:graph-linking-complete');
162
156
 
163
157
  // Generate summary
164
- this.analysisResults = this.engine.context.generateSummaryReport();
158
+ this.analysisResults = await this.engine.context.generateSummaryReport();
165
159
  this.emit('analysis:complete', this.analysisResults);
166
160
 
167
161
  this.isRunning = false;
@@ -310,9 +304,30 @@ export class HeadlessAPI extends EventEmitter {
310
304
  for (const dep of depsToRemove) {
311
305
  try {
312
306
  const absPath = path.resolve(this.engine.context.cwd, dep.manifest);
313
- // TODO: Implement package.json dependency removal
314
- refactoringResults.dependenciesRemoved.push(dep);
315
- this.emit('refactoring:dependency-removed', dep);
307
+
308
+ const pkgContent = await fs.readFile(absPath, 'utf8');
309
+ const pkg = JSON.parse(pkgContent);
310
+
311
+ let removed = false;
312
+ if (pkg.dependencies && pkg.dependencies[dep.package]) {
313
+ delete pkg.dependencies[dep.package];
314
+ removed = true;
315
+ } else if (pkg.devDependencies && pkg.devDependencies[dep.package]) {
316
+ delete pkg.devDependencies[dep.package];
317
+ removed = true;
318
+ } else if (pkg.peerDependencies && pkg.peerDependencies[dep.package]) {
319
+ delete pkg.peerDependencies[dep.package];
320
+ removed = true;
321
+ } else if (pkg.optionalDependencies && pkg.optionalDependencies[dep.package]) {
322
+ delete pkg.optionalDependencies[dep.package];
323
+ removed = true;
324
+ }
325
+
326
+ if (removed) {
327
+ await this.engine.txManager.stageWrite(absPath, JSON.stringify(pkg, null, 2) + '\n');
328
+ refactoringResults.dependenciesRemoved.push(dep);
329
+ this.emit('refactoring:dependency-removed', dep);
330
+ }
316
331
  } catch (error) {
317
332
  refactoringResults.errors.push({
318
333
  type: 'dependency-removal',
@@ -0,0 +1,369 @@
1
+ /**
2
+ * ============================================================================
3
+ * Headless API for entkapp v4.1.0
4
+ * ============================================================================
5
+ * Provides a programmatic interface for integrating entkapp into
6
+ * custom workflows, CI/CD pipelines, and third-party tools.
7
+ *
8
+ * Features:
9
+ * - Full control over analysis and refactoring operations
10
+ * - Event-driven architecture for real-time feedback
11
+ * - Streaming results for large codebases
12
+ * - Plugin SDK integration
13
+ */
14
+
15
+ import EventEmitter from 'events';
16
+ import path from 'path';
17
+ import { RefactoringEngine } from '../index.mjs';
18
+
19
+ export class HeadlessAPI extends EventEmitter {
20
+ constructor(options = {}) {
21
+ super();
22
+ this.options = options;
23
+ this.engine = null;
24
+ this.analysisResults = null;
25
+ this.isRunning = false;
26
+ }
27
+
28
+ /**
29
+ * Initialize the API with a project context
30
+ * @param {string} projectRoot - Root directory of the project
31
+ * @param {Object} config - Configuration options
32
+ * @returns {Promise<void>}
33
+ */
34
+ async initialize(projectRoot, config = {}) {
35
+ try {
36
+ this.emit('initialize:start', { projectRoot });
37
+
38
+ const engineOptions = {
39
+ cwd: projectRoot,
40
+ autoFix: config.autoFix !== false,
41
+ skipConfirm: config.skipConfirm || false,
42
+ verbose: config.verbose || false,
43
+ ...config
44
+ };
45
+
46
+ this.engine = new RefactoringEngine(engineOptions);
47
+
48
+ this.emit('initialize:complete', {
49
+ projectRoot,
50
+ config: this.engine.context
51
+ });
52
+ } catch (error) {
53
+ this.emit('initialize:error', { error });
54
+ throw error;
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Analyze the codebase without making changes
60
+ * @returns {Promise<Object>} Analysis results
61
+ */
62
+ async analyze() {
63
+ if (!this.engine) {
64
+ throw new Error('API not initialized. Call initialize() first.');
65
+ }
66
+
67
+ try {
68
+ this.isRunning = true;
69
+ this.emit('analysis:start');
70
+
71
+ // Initialize path mappings and workspace graph
72
+ await this.engine.pathMapper.loadMappings(this.engine.context.tsconfigFilename);
73
+
74
+ if (this.engine.context.isWorkspaceEnabled) {
75
+ this.emit('analysis:workspace-mapping-start');
76
+ await this.engine.workspaceGraph.initializeWorkspaceMesh();
77
+ this.emit('analysis:workspace-mapping-complete');
78
+ }
79
+
80
+ // Load cache manifest
81
+ const cacheManifest = await this.engine.cacheManager.loadCacheManifest();
82
+
83
+ // Discover source files
84
+ this.emit('analysis:file-discovery-start');
85
+ const fileList = [];
86
+ await this.engine.discoverSourceFiles(this.engine.context.cwd, fileList);
87
+ this.engine.context.metrics.totalFilesScanned = fileList.length;
88
+ this.emit('analysis:file-discovery-complete', { fileCount: fileList.length });
89
+
90
+ // Identify framework ecosystems
91
+ this.emit('analysis:framework-detection-start');
92
+ const activeFrameworkEcosystems = await this.engine.magicDetector.identifyActiveProjectEcosystems(
93
+ this.engine.context.cwd
94
+ );
95
+ this.emit('analysis:framework-detection-complete', { ecosystems: activeFrameworkEcosystems });
96
+
97
+ // Process files
98
+ this.emit('analysis:file-processing-start');
99
+ const sourceCodeFilesList = [];
100
+ for (const file of fileList) {
101
+ if (file.endsWith('package.json')) {
102
+ await this.engine.auditManifestSupplyChain(file);
103
+ } else {
104
+ sourceCodeFilesList.push(file);
105
+ }
106
+ }
107
+
108
+ // ASTAnalyzer doesn't need initProgram in this version
109
+
110
+ // Parallel processing
111
+ let parallelParseCompleted = false;
112
+ if (sourceCodeFilesList.length > 10) {
113
+ parallelParseCompleted = await this.engine.workerPool.parallelAnalyzeCodebase(
114
+ sourceCodeFilesList,
115
+ this.engine
116
+ );
117
+ }
118
+
119
+ // Sequential processing
120
+ for (let i = 0; i < sourceCodeFilesList.length; i++) {
121
+ const filePath = sourceCodeFilesList[i];
122
+ const node = this.engine.context.getOrCreateNode(filePath);
123
+ const currentHash = await this.engine.cacheManager.computeHash(filePath);
124
+ node.contentHash = currentHash;
125
+
126
+ const isFileCached = cacheManifest[filePath] && cacheManifest[filePath].hash === currentHash;
127
+
128
+ if (isFileCached) {
129
+ this.engine.context.metrics.cacheHits++;
130
+ this.engine.hydrateNodeFromCache(node, cacheManifest[filePath]);
131
+ } else if (!parallelParseCompleted) {
132
+ this.engine.context.metrics.cacheMisses++;
133
+ const fs = await import('fs/promises');
134
+ const fileContent = await fs.readFile(filePath, 'utf8');
135
+ await this.engine.analyzer.parseFile(filePath, fileContent, node);
136
+ }
137
+
138
+ this.engine.magicDetector.injectVirtualConsumerEdges(filePath, node, activeFrameworkEcosystems);
139
+ node.externalPackageUsage.forEach(pkg => this.engine.context.usedExternalPackages.add(pkg));
140
+
141
+ // Emit progress
142
+ if ((i + 1) % Math.ceil(sourceCodeFilesList.length / 10) === 0) {
143
+ this.emit('analysis:file-processing-progress', {
144
+ processed: i + 1,
145
+ total: sourceCodeFilesList.length,
146
+ percentage: Math.round(((i + 1) / sourceCodeFilesList.length) * 100)
147
+ });
148
+ }
149
+ }
150
+
151
+ // Link dependency graph
152
+ this.emit('analysis:graph-linking-start');
153
+ await this.engine.linkDependencyGraph();
154
+ this.emit('analysis:graph-linking-complete');
155
+
156
+ // Generate summary
157
+ this.analysisResults = await this.engine.context.generateSummaryReport();
158
+ this.emit('analysis:complete', this.analysisResults);
159
+
160
+ this.isRunning = false;
161
+ return this.analysisResults;
162
+ } catch (error) {
163
+ this.isRunning = false;
164
+ this.emit('analysis:error', { error });
165
+ throw error;
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Get detailed impact analysis for a specific file or export
171
+ * @param {string} filePath - Path to the file
172
+ * @param {string} symbol - Optional: specific export symbol to analyze
173
+ * @returns {Promise<Object>} Impact analysis results
174
+ */
175
+ async getImpactAnalysis(filePath, symbol = null) {
176
+ if (!this.engine) {
177
+ throw new Error('API not initialized. Call initialize() first.');
178
+ }
179
+
180
+ try {
181
+ const node = this.engine.context.projectGraph.get(filePath);
182
+ if (!node) {
183
+ throw new Error(`File not found in analysis graph: ${filePath}`);
184
+ }
185
+
186
+ const impact = {
187
+ file: filePath,
188
+ symbol,
189
+ directDependents: Array.from(node.incomingEdges),
190
+ dependencies: Array.from(node.outgoingEdges),
191
+ internalExports: symbol
192
+ ? [symbol]
193
+ : Array.from(node.internalExports.keys()),
194
+ externalPackages: Array.from(node.externalPackageUsage)
195
+ };
196
+
197
+ if (symbol) {
198
+ const safety = await this.engine.impactAnalyzer.verifyRefactorSafety(
199
+ filePath,
200
+ symbol,
201
+ this.engine.context.projectGraph
202
+ );
203
+ impact.refactorSafety = safety;
204
+ }
205
+
206
+ return impact;
207
+ } catch (error) {
208
+ this.emit('impact-analysis:error', { error, filePath, symbol });
209
+ throw error;
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Apply refactoring changes with automatic rollback on test failure
215
+ * @param {Object} changes - Changes to apply
216
+ * @returns {Promise<Object>} Refactoring results
217
+ */
218
+ async applyRefactoring(changes = {}) {
219
+ if (!this.engine) {
220
+ throw new Error('API not initialized. Call initialize() first.');
221
+ }
222
+
223
+ if (!this.analysisResults) {
224
+ throw new Error('No analysis results available. Call analyze() first.');
225
+ }
226
+
227
+ try {
228
+ this.isRunning = true;
229
+ this.emit('refactoring:start', changes);
230
+
231
+ const refactoringResults = {
232
+ filesDeleted: [],
233
+ exportsRemoved: [],
234
+ dependenciesRemoved: [],
235
+ errors: []
236
+ };
237
+
238
+ await this.engine.selfHealer.runSelfHealingLifecycle(async () => {
239
+ // Delete dead files
240
+ const filesToDelete = changes.deleteDeadFiles !== false
241
+ ? this.analysisResults.orphanedFiles
242
+ : [];
243
+
244
+ for (const relPath of filesToDelete) {
245
+ try {
246
+ const absPath = path.resolve(this.engine.context.cwd, relPath);
247
+ await this.engine.txManager.stageDeletion(absPath);
248
+ refactoringResults.filesDeleted.push(relPath);
249
+ this.emit('refactoring:file-deleted', { file: relPath });
250
+ } catch (error) {
251
+ refactoringResults.errors.push({ type: 'file-deletion', file: relPath, error: error.message });
252
+ this.emit('refactoring:error', { type: 'file-deletion', file: relPath, error });
253
+ }
254
+ }
255
+
256
+ // Remove unused exports
257
+ const exportsToRemove = changes.removeUnusedExports !== false
258
+ ? this.analysisResults.deadExports
259
+ : [];
260
+
261
+ for (const unusedExport of exportsToRemove) {
262
+ try {
263
+ const absPath = path.resolve(this.engine.context.cwd, unusedExport.file);
264
+ const node = this.engine.context.projectGraph.get(absPath);
265
+
266
+ if (node) {
267
+ const safety = await this.engine.impactAnalyzer.verifyRefactorSafety(
268
+ absPath,
269
+ unusedExport.symbol,
270
+ this.engine.context.projectGraph
271
+ );
272
+
273
+ if (safety.isSafeToPrune) {
274
+ const nextText = await this.engine.sourceRewriter.stripNamedExportSignature(
275
+ absPath,
276
+ unusedExport.symbol,
277
+ node.internalExports.get(unusedExport.symbol)
278
+ );
279
+
280
+ await this.engine.txManager.stageWrite(absPath, nextText);
281
+ await this.engine.typeIntegrity.synchronizeDeclarationFile(absPath, unusedExport.symbol);
282
+
283
+ refactoringResults.exportsRemoved.push(unusedExport);
284
+ this.emit('refactoring:export-removed', unusedExport);
285
+ }
286
+ }
287
+ } catch (error) {
288
+ refactoringResults.errors.push({
289
+ type: 'export-removal',
290
+ export: unusedExport.symbol,
291
+ file: unusedExport.file,
292
+ error: error.message
293
+ });
294
+ this.emit('refactoring:error', { type: 'export-removal', export: unusedExport, error });
295
+ }
296
+ }
297
+
298
+ // Remove unused dependencies
299
+ const depsToRemove = changes.removeUnusedDependencies !== false
300
+ ? this.analysisResults.unusedDependencies
301
+ : [];
302
+
303
+ for (const dep of depsToRemove) {
304
+ try {
305
+ const absPath = path.resolve(this.engine.context.cwd, dep.manifest);
306
+ // TODO: Implement package.json dependency removal
307
+ refactoringResults.dependenciesRemoved.push(dep);
308
+ this.emit('refactoring:dependency-removed', dep);
309
+ } catch (error) {
310
+ refactoringResults.errors.push({
311
+ type: 'dependency-removal',
312
+ package: dep.package,
313
+ error: error.message
314
+ });
315
+ this.emit('refactoring:error', { type: 'dependency-removal', package: dep.package, error });
316
+ }
317
+ }
318
+ });
319
+
320
+ this.isRunning = false;
321
+ this.emit('refactoring:complete', refactoringResults);
322
+ return refactoringResults;
323
+ } catch (error) {
324
+ this.isRunning = false;
325
+ this.emit('refactoring:error', { error });
326
+ throw error;
327
+ }
328
+ }
329
+
330
+ /**
331
+ * Get current analysis metrics
332
+ * @returns {Object} Metrics
333
+ */
334
+ getMetrics() {
335
+ if (!this.engine) {
336
+ throw new Error('API not initialized. Call initialize() first.');
337
+ }
338
+ return this.engine.context.metrics;
339
+ }
340
+
341
+ /**
342
+ * Get all registered plugins
343
+ * @returns {Array} Plugin instances
344
+ */
345
+ getPlugins() {
346
+ if (!this.engine) {
347
+ throw new Error('API not initialized. Call initialize() first.');
348
+ }
349
+ return this.engine.context.pluginRegistry?.getPlugins() || [];
350
+ }
351
+
352
+ /**
353
+ * Get analysis results
354
+ * @returns {Object} Analysis results
355
+ */
356
+ getAnalysisResults() {
357
+ return this.analysisResults;
358
+ }
359
+
360
+ /**
361
+ * Check if API is currently running
362
+ * @returns {boolean}
363
+ */
364
+ isProcessing() {
365
+ return this.isRunning;
366
+ }
367
+ }
368
+
369
+ export default HeadlessAPI;
@@ -0,0 +1,135 @@
1
+ /**
2
+ * ============================================================================
3
+ * Plugin SDK for entkapp v4.7.0 (Enterprise Edition)
4
+ * ============================================================================
5
+ * Provides utilities and helpers for developing custom plugins that extend
6
+ * entkapp's analysis and healing capabilities.
7
+ */
8
+
9
+ import { BasePlugin } from '../plugins/BasePlugin.mjs';
10
+
11
+ /**
12
+ * Extended plugin base class with SDK utilities
13
+ */
14
+ export class PluginSDKBase extends BasePlugin {
15
+ constructor(context) {
16
+ super(context);
17
+ this.hooks = new Map();
18
+ this.transformers = [];
19
+ this.validators = [];
20
+ }
21
+
22
+ /**
23
+ * Register a hook for a specific lifecycle event
24
+ */
25
+ registerHook(eventName, handler) {
26
+ if (!this.hooks.has(eventName)) {
27
+ this.hooks.set(eventName, []);
28
+ }
29
+ this.hooks.get(eventName).push(handler);
30
+ }
31
+
32
+ /**
33
+ * Emit a hook event
34
+ */
35
+ async emitHook(eventName, data) {
36
+ const handlers = this.hooks.get(eventName) || [];
37
+ for (const handler of handlers) {
38
+ await handler(data);
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Register a code transformer
44
+ */
45
+ registerTransformer(transformer) {
46
+ this.transformers.push(transformer);
47
+ }
48
+
49
+ /**
50
+ * Register a validator
51
+ */
52
+ registerValidator(validator) {
53
+ this.validators.push(validator);
54
+ }
55
+
56
+ /**
57
+ * Enterprise: Register a Call Graph visitor
58
+ */
59
+ registerCallGraphVisitor(visitor) {
60
+ this.context.callGraphVisitors = this.context.callGraphVisitors || [];
61
+ this.context.callGraphVisitors.push(visitor);
62
+ }
63
+ }
64
+
65
+ /**
66
+ * SDK utilities for plugin development
67
+ */
68
+ export class PluginSDK {
69
+ /**
70
+ * Create a custom plugin class
71
+ */
72
+ static createPlugin(config) {
73
+ return class CustomPlugin extends PluginSDKBase {
74
+ get name() {
75
+ return config.name;
76
+ }
77
+
78
+ getConfigFiles() {
79
+ return config.configFiles || [];
80
+ }
81
+
82
+ getRoutePatterns() {
83
+ return config.routePatterns || [];
84
+ }
85
+
86
+ getRequiredSystemContracts() {
87
+ return config.requiredContracts || ['default'];
88
+ }
89
+
90
+ async isActive(baseDir) {
91
+ if (config.isActive) {
92
+ return await config.isActive(baseDir);
93
+ }
94
+ return super.isActive(baseDir);
95
+ }
96
+
97
+ async initialize() {
98
+ if (config.initialize) {
99
+ await config.initialize(this.context);
100
+ }
101
+ }
102
+
103
+ async analyze(node, filePath) {
104
+ if (config.analyze) {
105
+ return await config.analyze(node, filePath, this.context);
106
+ }
107
+ }
108
+ };
109
+ }
110
+
111
+ /**
112
+ * Create a specialized member usage analyzer
113
+ */
114
+ static createMemberAnalyzer(config = {}) {
115
+ return this.createPlugin({
116
+ name: config.name || 'custom-member-analyzer',
117
+ async analyze(node, filePath, context) {
118
+ // Custom logic to protect or flag specific members
119
+ if (config.protectMembers) {
120
+ for (const [symbol, meta] of node.internalExports.entries()) {
121
+ if (meta.members) {
122
+ meta.members.forEach(m => {
123
+ if (config.protectMembers.includes(m.name)) {
124
+ m.isPublic = true; // Mark as public to protect from deletion
125
+ }
126
+ });
127
+ }
128
+ }
129
+ }
130
+ }
131
+ });
132
+ }
133
+ }
134
+
135
+ export default PluginSDK;
@@ -66,6 +66,17 @@ export class ASTAnalyzer {
66
66
  fileNode.isPureBarrel = true;
67
67
  }
68
68
 
69
+ // ADVANCED: Deep reachability analysis for dynamic imports
70
+ if (fileNode.dynamicImports.size > 0) {
71
+ fileNode.dynamicImports.forEach(specifier => {
72
+ const resolved = this.resolveAbsoluteTargetFile(specifier, filePath);
73
+ if (resolved) {
74
+ fileNode.outgoingEdges.add(resolved);
75
+ this.context.getOrCreateNode(resolved).isDynamicEntry = true;
76
+ }
77
+ });
78
+ }
79
+
69
80
  this.scopeStack = [];
70
81
  this.currentScope = null;
71
82
  }
@@ -222,6 +233,9 @@ export class ASTAnalyzer {
222
233
  case ts.SyntaxKind.ExpressionStatement:
223
234
  this.handleAssignmentExpressionPass1(node, fileNode, sourceFile);
224
235
  break;
236
+ case ts.SyntaxKind.ExportDeclaration:
237
+ this.handleExportDeclaration(node, fileNode, sourceFile);
238
+ break;
225
239
  }
226
240
  } else {
227
241
  // --- PASS 2: REFERENCE TRACKING ---
@@ -591,10 +605,10 @@ export class ASTAnalyzer {
591
605
  }
592
606
  } else {
593
607
  // Non-literal dynamic import
594
- if (!Array.isArray(fileNode.calculatedDynamicImports)) {
595
- fileNode.calculatedDynamicImports = [];
608
+ if (!fileNode.calculatedDynamicImports || Array.isArray(fileNode.calculatedDynamicImports)) {
609
+ fileNode.calculatedDynamicImports = new Set();
596
610
  }
597
- fileNode.calculatedDynamicImports.push({ kind: ts.SyntaxKind[arg.kind], start: arg.getStart(sourceFile) });
611
+ fileNode.calculatedDynamicImports.add({ kind: ts.SyntaxKind[arg.kind], start: arg.getStart(sourceFile) });
598
612
 
599
613
  // UPGRADE 2: Try to perform Glob-Analysis on Template Strings
600
614
  this.handlePartialEvaluation(arg, fileNode, sourceFile);