entkapp 4.1.0 → 4.2.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/README.md CHANGED
@@ -28,6 +28,19 @@ It represents the process of stripping away the unnecessary layers of a codebase
28
28
  ## šŸ“¦ Installation
29
29
 
30
30
  ```bash
31
- npm install -D entkapp
31
+ npm install -g entkapp
32
32
  # or
33
- pnpm add -D entkapp
33
+ pnpm add -g entkapp
34
+ ```
35
+
36
+ ## Usage
37
+ ```bash
38
+ npx entkapp
39
+ # or
40
+ entkapp
41
+ # and then
42
+ npx entkapp -r
43
+ # or
44
+ entkapp -r
45
+ # It is recommended to run with --no-fix tag on your first run!
46
+ ```
package/bin/cli.js CHANGED
@@ -28,7 +28,7 @@ async function bootstrap() {
28
28
  program
29
29
  .name('entkapp')
30
30
  .description(ansis.cyan('Enterprise-Grade AST Syntax Refactoring & Self-Healing Engine'))
31
- .version(packageJsonContent.version || '4.0.0');
31
+ .version(packageJsonContent.version || '4.2.0');
32
32
 
33
33
  program
34
34
  .option('-c, --cwd <path>', 'Specify the execution context root directory', process.cwd())
@@ -132,7 +132,7 @@ async function bootstrap() {
132
132
  }, timeoutMs);
133
133
  timeoutTimer.unref(); // Allow process to exit if work finishes
134
134
 
135
- console.log(ansis.bold.green(`\nšŸ“¦ entkapp v${packageJsonContent.version || '4.0.0'} Engine Activation`));
135
+ console.log(ansis.bold.green(`\nšŸ“¦ entkapp v${packageJsonContent.version || '4.2.0'} Engine Activation`));
136
136
  console.log(ansis.dim('------------------------------------------------------------'));
137
137
  console.log(`${ansis.bold('Target Workspace Root :')} ${ansis.blue(targetCwd)}`);
138
138
  console.log(`${ansis.bold('Refactoring Mode :')} ${options.fix ? ansis.yellow('Active Fixing & Self-Healing Enabled') : ansis.gray('Dry-Run Reporting Only')}`);
package/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  /**
4
4
  * ============================================================================
5
- * šŸ“¦ entkapp v4.1.0: Ultimate Enterprise Codebase Janitor & Self-Healing Engine
5
+ * šŸ“¦ entkapp v4.2.0: Ultimate Enterprise Codebase Janitor & Self-Healing Engine
6
6
  * ============================================================================
7
7
  * * Eine hochgradig integrierte Code-Analyse- und Projektbootstrapping-Engine.
8
8
  * Kombiniert rekursive Erreichbarkeitsanalysen (Reachability Graphs) auf
@@ -648,7 +648,7 @@ function smartPrepend(originalCode, declarationBlock) {
648
648
  async function inspectNpmPackage(pkgName) {
649
649
  try {
650
650
  const response = await fetch(`https://registry.npmjs.org/${pkgName}/latest`, {
651
- headers: { 'User-Agent': 'entkapp-dx-client/4.1.0' },
651
+ headers: { 'User-Agent': 'entkapp-dx-client/4.2.0' },
652
652
  signal: AbortSignal.timeout(4000)
653
653
  });
654
654
  if (response.status === 200) {
@@ -682,7 +682,7 @@ async function inspectNpmPackage(pkgName) {
682
682
  async function fetchRemoteLicense(licenseKey) {
683
683
  try {
684
684
  const response = await fetch(`https://api.github.com/licenses/${licenseKey.toLowerCase()}`, {
685
- headers: { 'User-Agent': 'entkapp-dx-client/4.1.0' },
685
+ headers: { 'User-Agent': 'entkapp-dx-client/4.2.0' },
686
686
  signal: AbortSignal.timeout(5000)
687
687
  });
688
688
  if (response.status === 200) {
@@ -1681,7 +1681,7 @@ function postProcessAnalysis(stats, graphEngine) {
1681
1681
  // ============================================================
1682
1682
  async function main() {
1683
1683
  if (process.argv.includes('--help') || process.argv.includes('-h')) {
1684
- console.log(`\nšŸ“¦ entkapp v4.1.0: Advanced Dependency Intelligence Engine\n`);
1684
+ console.log(`\nšŸ“¦ entkapp v4.2.0: Advanced Dependency Intelligence Engine\n`);
1685
1685
  console.log(`Usage: npx entkapp [options]\n`);
1686
1686
  console.log(`Options:`);
1687
1687
  console.log(` -h, --help Show this comprehensive workspace helper panel`);
@@ -1766,7 +1766,7 @@ async function main() {
1766
1766
  let detectedFrameworks = [];
1767
1767
 
1768
1768
  console.log(`\n${'═'.repeat(67)}`);
1769
- console.log(`šŸš€ entkapp v4.1.0: Enterprise Graph Intelligence Analyzer`);
1769
+ console.log(`šŸš€ entkapp v4.2.0: Enterprise Graph Intelligence Analyzer`);
1770
1770
  console.log(`${'═'.repeat(67)}\n`);
1771
1771
 
1772
1772
  const topLevelItems = fs.readdirSync(targetDir);
package/logo.png CHANGED
Binary file
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
3
  "name": "entkapp",
4
- "version": "4.1.0",
4
+ "version": "4.2.0",
5
5
  "description": "The Ultimate Enterprise Codebase Janitor. Faster than Knip with OXC integration, type-aware analysis, and automated structural healing. Fully standalone - solving what Knip cannot.",
6
6
  "type": "module",
7
7
  "main": "index.js",
@@ -64,7 +64,12 @@
64
64
  "unresolved",
65
65
  "unused",
66
66
  "unused-packages",
67
- "workspace"
67
+ "workspace",
68
+ "entkapp",
69
+ "cut",
70
+ "kapp",
71
+ "dreamlong",
72
+ "DreamLong"
68
73
  ],
69
74
  "author": "DreamLongYT",
70
75
  "license": "Apache-2.0",
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * ============================================================================
3
- * Headless API for entkapp v4.1.0
3
+ * Headless API for entkapp v4.2.0
4
4
  * ============================================================================
5
5
  * Provides a programmatic interface for integrating entkapp into
6
6
  * custom workflows, CI/CD pipelines, and third-party tools.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * ============================================================================
3
- * Plugin SDK for entkapp v4.0.0
3
+ * Plugin SDK for entkapp v4.2.0
4
4
  * ============================================================================
5
5
  * Provides utilities and helpers for developing custom plugins that extend
6
6
  * entkapp's analysis and healing capabilities.
@@ -178,15 +178,6 @@ export class BarrelParser {
178
178
  }
179
179
 
180
180
  // Rule D: Sweep through anonymous star re-exports vectors (export * from 'module')
181
- //
182
- // Algorithm:
183
- // 1. For each `export * from './child'`, recursively resolve the symbol in the child.
184
- // 2. A non-barrel child immediately returns `{ originFile: childPath }` regardless of
185
- // whether it actually declares the symbol. We therefore must verify that the
186
- // returned origin file actually contains the symbol in its declaredLocalExports
187
- // before accepting the result.
188
- // 3. If the child is itself a barrel, the recursive call already performs the full
189
- // chain walk, so we only need to verify the final origin.
190
181
  for (const relativePath of spec.wildcardExports) {
191
182
  const fullyResolvedPath = this.resolver.resolveModulePath(contextFilePath, relativePath);
192
183
 
@@ -195,24 +186,15 @@ export class BarrelParser {
195
186
  fullyResolvedPath,
196
187
  targetSymbolName,
197
188
  activeProjectGraph,
198
- new Set(protectionStack) // Use a copy so sibling branches don't block each other
189
+ new Set(protectionStack)
199
190
  );
200
191
 
201
192
  if (!continuousResolutionTrace) continue;
202
- if (continuousResolutionTrace.originFile === contextFilePath) continue;
203
193
 
204
- // Verify that the resolved origin actually declares the symbol.
205
- // This prevents a non-barrel sibling (e.g. constants.ts) from being
206
- // incorrectly returned for a symbol it does not export (e.g. formatData).
207
194
  const originSpec = await this.parseBarrelSpecification(continuousResolutionTrace.originFile);
208
195
  if (originSpec.declaredLocalExports.has(continuousResolutionTrace.originSymbol)) {
209
196
  return continuousResolutionTrace;
210
197
  }
211
- // The origin spec is itself a barrel (isBarrelInstance = true) and the
212
- // recursive call already resolved through it – accept the result.
213
- if (originSpec.isBarrelInstance) {
214
- return continuousResolutionTrace;
215
- }
216
198
  }
217
199
  }
218
200
 
@@ -3,20 +3,17 @@ export class OxcAnalyzer {
3
3
  this.context = context;
4
4
  this.oxc = null;
5
5
  this.isAvailable = false;
6
- // Initialization is handled via init() or lazily during parseFile
7
6
  }
8
7
 
9
8
  async init() {
10
9
  if (this.isAvailable) return true;
11
10
  try {
12
- // In ESM, we use dynamic import()
13
11
  const oxc = await import("oxc-parser");
14
12
  this.oxc = oxc;
15
13
  this.isAvailable = true;
16
14
  return true;
17
15
  } catch (e) {
18
16
  try {
19
- // Fallback for older node versions or specific bundling setups
20
17
  const { createRequire } = await import('module');
21
18
  const require = createRequire(import.meta.url);
22
19
  this.oxc = require("oxc-parser");
@@ -25,7 +22,7 @@ export class OxcAnalyzer {
25
22
  } catch (err) {
26
23
  this.isAvailable = false;
27
24
  if (this.context.verbose) {
28
- console.warn("[OxcAnalyzer] oxc-parser not found or failed to load, falling back to TypeScript compiler API.");
25
+ console.warn("[OxcAnalyzer] oxc-parser not found or failed to load.");
29
26
  }
30
27
  return false;
31
28
  }
@@ -39,24 +36,18 @@ export class OxcAnalyzer {
39
36
  }
40
37
 
41
38
  try {
42
- if (this.context.verbose) {
43
- console.log(`[OXC] Fast-parsing: ${filePath}`);
44
- }
45
-
46
39
  const ast = this.oxc.parseSync(content, {
47
40
  sourceType: "module",
48
41
  sourceFilename: filePath,
49
42
  ecmaVersion: "latest",
50
43
  });
51
44
 
52
- // Initialize new properties for JSX and Decorator analysis
53
45
  fileNode.jsxComponents = new Set();
54
46
  fileNode.jsxProps = new Set();
55
47
  fileNode.decorators = new Set();
56
48
 
57
49
  this.walkOxcAst(ast.program, fileNode, content);
58
50
 
59
- // Compute line/column for each export start position
60
51
  const lines = content.split('\n');
61
52
  const getLineCol = (pos) => {
62
53
  let count = 0;
@@ -100,19 +91,17 @@ export class OxcAnalyzer {
100
91
  this.handleCallExpression(node, fileNode);
101
92
  break;
102
93
  case "JSXElement":
103
- case "JSXFragment": // Consider fragments as well
94
+ case "JSXFragment":
104
95
  this.handleJsxElement(node, fileNode);
105
96
  break;
106
97
  case "Decorator":
107
98
  this.handleDecorator(node, fileNode);
108
99
  break;
109
100
  case "StringLiteral":
110
- // Track for secret scanning if it looks like a secret
111
101
  fileNode.rawStringReferences.add(node.value);
112
102
  break;
113
103
  }
114
104
 
115
- // Traverse children
116
105
  for (const key in node) {
117
106
  if (node[key] && typeof node[key] === "object") {
118
107
  if (Array.isArray(node[key])) {
@@ -128,7 +117,6 @@ export class OxcAnalyzer {
128
117
  const specifier = node.source.value;
129
118
  fileNode.explicitImports.add(specifier);
130
119
 
131
- // Track external package usage for dependency analysis
132
120
  if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
133
121
  fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
134
122
  }
@@ -136,7 +124,7 @@ export class OxcAnalyzer {
136
124
  if (node.specifiers) {
137
125
  node.specifiers.forEach((spec) => {
138
126
  if (spec.type === "ImportSpecifier") {
139
- const importedName = spec.imported.name;
127
+ const importedName = spec.imported.name || (spec.imported.type === "Identifier" ? spec.imported.name : spec.imported.value);
140
128
  fileNode.importedSymbols.add(`${specifier}:${importedName}`);
141
129
  } else if (spec.type === "ImportDefaultSpecifier") {
142
130
  fileNode.importedSymbols.add(`${specifier}:default`);
@@ -148,86 +136,83 @@ export class OxcAnalyzer {
148
136
  }
149
137
 
150
138
  handleExportDeclaration(node, fileNode, content) {
139
+ // 1. Default Exports
151
140
  if (node.type === "ExportDefaultDeclaration") {
152
- fileNode.internalExports.set("default", { type: "default", start: node.start, end: node.end });
141
+ fileNode.internalExports.set("default", {
142
+ type: "default",
143
+ start: node.start,
144
+ end: node.end
145
+ });
153
146
  return;
154
147
  }
155
148
 
149
+ // 2. Re-export All: export * from 'mod' or export * as ns from 'mod'
156
150
  if (node.type === "ExportAllDeclaration") {
157
- const sourceSpecifier = node.source ? node.source.value : null;
158
- if (sourceSpecifier) {
159
- // Register re-export source as an explicit import so the graph linker
160
- // creates an incomingEdge on the re-exported file.
161
- fileNode.explicitImports.add(sourceSpecifier);
162
-
163
- // Track external package usage from re-exports
164
- if (!sourceSpecifier.startsWith('.') && !sourceSpecifier.startsWith('/')) {
165
- fileNode.externalPackageUsage.add(this._extractPackageName(sourceSpecifier));
166
- }
151
+ const sourceSpecifier = node.source.value;
152
+ fileNode.explicitImports.add(sourceSpecifier);
153
+ if (!sourceSpecifier.startsWith('.') && !sourceSpecifier.startsWith('/')) {
154
+ fileNode.externalPackageUsage.add(this._extractPackageName(sourceSpecifier));
155
+ }
167
156
 
168
- if (node.exported) {
169
- // export * as name from 'module'
170
- const name = node.exported.name || (node.exported.type === "Identifier" ? node.exported.name : null);
171
- if (name) {
172
- fileNode.internalExports.set(name, { type: "re-export-namespace", source: sourceSpecifier, originalName: "*", start: node.start, end: node.end });
173
- fileNode.importedSymbols.add(`${sourceSpecifier}:*`);
174
- }
175
- } else {
176
- // export * from 'module'
177
- fileNode.internalExports.set("*", { type: "re-export-all", source: sourceSpecifier });
178
- // Register as wildcard importedSymbol so graph linker creates incomingEdge
157
+ if (node.exported) {
158
+ // export * as ns from 'mod'
159
+ const name = node.exported.name || (node.exported.type === "Identifier" ? node.exported.name : null);
160
+ if (name) {
161
+ fileNode.internalExports.set(name, {
162
+ type: "re-export-namespace",
163
+ source: sourceSpecifier,
164
+ originalName: "*",
165
+ start: node.start,
166
+ end: node.end
167
+ });
179
168
  fileNode.importedSymbols.add(`${sourceSpecifier}:*`);
180
169
  }
170
+ } else {
171
+ // export * from 'mod'
172
+ fileNode.internalExports.set("*", {
173
+ type: "re-export-all",
174
+ source: sourceSpecifier
175
+ });
176
+ fileNode.importedSymbols.add(`${sourceSpecifier}:*`);
181
177
  }
182
178
  return;
183
179
  }
184
180
 
181
+ // 3. Named Exports & Re-exports with specifiers
185
182
  if (node.source) {
186
- // Re-export with source: export { x } from 'module'
183
+ // Re-export: export { x } from 'mod'
187
184
  const specifier = node.source.value;
188
- // Register re-export source as an explicit import
189
185
  fileNode.explicitImports.add(specifier);
190
-
191
- // Track external package usage from re-exports
192
186
  if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
193
187
  fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
194
188
  }
195
189
 
196
190
  if (node.specifiers) {
197
191
  node.specifiers.forEach((spec) => {
198
- const exportedName = spec.exported.name;
199
- const localName = spec.local.name;
192
+ const exportedName = spec.exported.name || (spec.exported.type === "Identifier" ? spec.exported.name : spec.exported.value);
193
+ const localName = spec.local.name || (spec.local.type === "Identifier" ? spec.local.name : spec.local.value);
200
194
  fileNode.internalExports.set(exportedName, {
201
195
  type: "re-export",
202
196
  source: specifier,
203
197
  originalName: localName,
204
- start: node.start,
205
- end: node.end,
198
+ start: spec.start,
199
+ end: spec.end,
206
200
  });
207
- // Register as importedSymbol so barrel-tracer can resolve origin file
208
201
  fileNode.importedSymbols.add(`${specifier}:${localName}`);
209
202
  });
210
203
  }
211
204
  } else if (node.declaration) {
212
- // Direct export
205
+ // Direct declaration export: export const x = 1, export function f() {}
213
206
  const decl = node.declaration;
214
207
  if (decl.type === "VariableDeclaration") {
215
208
  decl.declarations.forEach((d) => {
216
- if (d.id.type === "Identifier") {
217
- fileNode.internalExports.set(d.id.name, { type: "variable", start: d.start, end: d.end });
218
- } else if (d.id.type === "ObjectPattern") {
219
- d.id.properties.forEach((p) => {
220
- if (p.type === "Property" && p.value.type === "Identifier") {
221
- fileNode.internalExports.set(p.value.name, { type: "variable", start: p.start, end: p.end });
222
- }
223
- });
224
- } else if (d.id.type === "ArrayPattern") {
225
- d.id.elements.forEach((e) => {
226
- if (e && e.type === "Identifier") {
227
- fileNode.internalExports.set(e.name, { type: "variable", start: e.start, end: e.end });
228
- }
209
+ this._extractNamesFromPattern(d.id, (name) => {
210
+ fileNode.internalExports.set(name, {
211
+ type: "variable",
212
+ start: d.start,
213
+ end: d.end
229
214
  });
230
- }
215
+ });
231
216
  });
232
217
  } else if (decl.id && decl.id.name) {
233
218
  let type = "unknown";
@@ -238,29 +223,50 @@ export class OxcAnalyzer {
238
223
  else if (decl.type === "TSTypeAliasDeclaration") type = "type";
239
224
  else if (decl.type === "TSModuleDeclaration") type = "namespace";
240
225
 
241
- const exportInfo = { type, start: decl.start, end: decl.end };
242
- fileNode.internalExports.set(decl.id.name, exportInfo);
243
-
244
- if (decl.type === "TSEnumDeclaration") {
245
- exportInfo.members = decl.members.map((m) => m.id.name || (m.id.type === "Identifier" ? m.id.name : ""));
246
- } else if (decl.type === "TSInterfaceDeclaration" || decl.type === "ClassDeclaration") {
247
- exportInfo.members = decl.body.body.filter((m) => m.key && m.key.name).map((m) => m.key.name);
248
- }
226
+ fileNode.internalExports.set(decl.id.name, {
227
+ type,
228
+ start: decl.start,
229
+ end: decl.end
230
+ });
249
231
  }
250
232
  } else if (node.specifiers) {
233
+ // Export existing locals: export { x, y as z }
251
234
  node.specifiers.forEach((spec) => {
252
- const exportedName = spec.exported.name;
253
- const localName = spec.local.name;
235
+ const exportedName = spec.exported.name || (spec.exported.type === "Identifier" ? spec.exported.name : spec.exported.value);
236
+ const localName = spec.local.name || (spec.local.type === "Identifier" ? spec.local.name : spec.local.value);
254
237
  fileNode.internalExports.set(exportedName, {
255
238
  type: "export",
256
239
  originalName: localName,
257
- start: node.start,
258
- end: node.end,
240
+ start: spec.start,
241
+ end: spec.end,
259
242
  });
260
243
  });
261
244
  }
262
245
  }
263
246
 
247
+ _extractNamesFromPattern(node, callback) {
248
+ if (!node) return;
249
+ if (node.type === "Identifier") {
250
+ callback(node.name);
251
+ } else if (node.type === "ObjectPattern") {
252
+ node.properties.forEach(p => {
253
+ if (p.type === "Property") {
254
+ this._extractNamesFromPattern(p.value, callback);
255
+ } else if (p.type === "RestElement") {
256
+ this._extractNamesFromPattern(p.argument, callback);
257
+ }
258
+ });
259
+ } else if (node.type === "ArrayPattern") {
260
+ node.elements.forEach(e => {
261
+ if (e) this._extractNamesFromPattern(e, callback);
262
+ });
263
+ } else if (node.type === "AssignmentPattern") {
264
+ this._extractNamesFromPattern(node.left, callback);
265
+ } else if (node.type === "RestElement") {
266
+ this._extractNamesFromPattern(node.argument, callback);
267
+ }
268
+ }
269
+
264
270
  handleCallExpression(node, fileNode) {
265
271
  // Dynamic import(): import('./module')
266
272
  if (node.callee.type === "Import" && node.arguments.length > 0) {
@@ -269,11 +275,11 @@ export class OxcAnalyzer {
269
275
  const specifier = arg.value;
270
276
  fileNode.explicitImports.add(specifier);
271
277
  fileNode.dynamicImports.add(specifier);
278
+ fileNode.importedSymbols.add(`${specifier}:*`); // Dynamic import usually consumes the whole namespace
272
279
  if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
273
280
  fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
274
281
  }
275
282
  } else {
276
- // Non-literal dynamic import: record as calculated
277
283
  if (fileNode.calculatedDynamicImports) {
278
284
  fileNode.calculatedDynamicImports.push({ kind: arg.type, start: arg.start });
279
285
  }
@@ -281,6 +287,7 @@ export class OxcAnalyzer {
281
287
  } else if (node.callee.type === "Identifier" && node.callee.name === "require" && node.arguments.length > 0 && node.arguments[0].type === "StringLiteral") {
282
288
  const specifier = node.arguments[0].value;
283
289
  fileNode.explicitImports.add(specifier);
290
+ fileNode.importedSymbols.add(`${specifier}:*`);
284
291
  if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
285
292
  fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
286
293
  }
@@ -291,6 +298,7 @@ export class OxcAnalyzer {
291
298
  const getElementName = (nameNode) => {
292
299
  if (nameNode.type === "JSXIdentifier") return nameNode.name;
293
300
  if (nameNode.type === "JSXMemberExpression") return `${getElementName(nameNode.object)}.${nameNode.property.name}`;
301
+ if (nameNode.type === "JSXNamespacedName") return `${nameNode.namespace.name}:${nameNode.name.name}`;
294
302
  return "unknown";
295
303
  };
296
304
 
@@ -311,19 +319,17 @@ export class OxcAnalyzer {
311
319
  handleDecorator(node, fileNode) {
312
320
  const getDecoratorName = (expr) => {
313
321
  if (expr.type === "Identifier") return expr.name;
314
- if (expr.type === "CallExpression" && expr.callee.type === "Identifier") return expr.callee.name;
315
- if (expr.type === "CallExpression" && expr.callee.type === "MemberExpression" && expr.callee.property.type === "Identifier") return expr.callee.property.name;
322
+ if (expr.type === "CallExpression") return getDecoratorName(expr.callee);
323
+ if (expr.type === "MemberExpression") {
324
+ const prop = expr.property.name || expr.property.value;
325
+ return prop || "unknown";
326
+ }
316
327
  return "unknown";
317
328
  };
318
329
 
319
330
  const decoratorName = getDecoratorName(node.expression);
320
- fileNode.decorators.add(decoratorName);
321
-
322
- // Optionally, extract decorator arguments
323
- if (node.expression.type === "CallExpression") {
324
- node.expression.arguments.forEach(arg => {
325
- // Further analysis of arguments can be done here if needed
326
- });
331
+ if (decoratorName !== "unknown") {
332
+ fileNode.decorators.add(decoratorName);
327
333
  }
328
334
  }
329
335
 
package/src/index.js CHANGED
@@ -3,7 +3,7 @@ import { OxcAnalyzer } from "./ast/OxcAnalyzer.js";
3
3
  import { SecretScanner } from './ast/SecretScanner.js';
4
4
  /**
5
5
  * ============================================================================
6
- * šŸ“¦ entkapp v4.1.0: Unified Architectural Refactoring Orchestrator
6
+ * šŸ“¦ entkapp v4.2.0: Unified Architectural Refactoring Orchestrator
7
7
  * ============================================================================
8
8
  * Main execution bridge managing multi-pass compilation cycles, semantic cross-linking,
9
9
  * supply-chain validation audits, and automated structural healing rollbacks.
@@ -262,7 +262,11 @@ export class RefactoringEngine {
262
262
  if (!this.context.unlistedDependencies) this.context.unlistedDependencies = [];
263
263
 
264
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, '/');
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
+ };
266
270
 
267
271
  if (this.context.projectGraph && typeof this.context.projectGraph.entries === 'function') {
268
272
  for (const [filePath, fileNode] of this.context.projectGraph.entries()) {
@@ -271,12 +275,10 @@ export class RefactoringEngine {
271
275
  const cleanFilePath = slashify(filePath);
272
276
 
273
277
  // šŸš€ 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
278
  if (fileNode.externalPackageUsage) {
277
279
  fileNode.externalPackageUsage.forEach(pkg => {
278
- const relativeToRoot = path.relative(this.context.cwd, filePath);
279
- if (relativeToRoot.startsWith('packages' + path.sep) || relativeToRoot.startsWith('packages/')) {
280
+ const relativeToRoot = path.relative(this.context.cwd, filePath).replace(/\\/g, '/');
281
+ if (relativeToRoot.startsWith('packages/')) {
280
282
  this.context.consumedWorkspacePackages.add(pkg);
281
283
  } else {
282
284
  this.context.consumedRootPackages.add(pkg);
@@ -294,17 +296,17 @@ export class RefactoringEngine {
294
296
  if (!this.context.exportRegistry.has(cleanFilePath)) {
295
297
  this.context.exportRegistry.set(cleanFilePath, new Set());
296
298
  }
297
- exportKeys.forEach(key => this.context.exportRegistry.get(cleanFilePath).add(key));
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
+ });
298
304
  }
299
305
  }
300
306
 
301
307
  // 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 (fileNode.importedSymbols) {
309
+ for (const symbolToken of fileNode.importedSymbols) {
308
310
  if (typeof symbolToken !== 'string') continue;
309
311
 
310
312
  const splitIndex = symbolToken.indexOf(':');
@@ -314,31 +316,20 @@ export class RefactoringEngine {
314
316
  const symbolName = symbolToken.slice(splitIndex + 1);
315
317
 
316
318
  let targetFile = null;
317
- if (this.workspaceGraph && typeof this.workspaceGraph.isLocalWorkspaceSpecifier === 'function' && this.workspaceGraph.isLocalWorkspaceSpecifier(specifier)) {
319
+ // If specifier is already an absolute path (resolved during linkDependencyGraph)
320
+ if (path.isAbsolute(specifier)) {
321
+ targetFile = specifier;
322
+ } else if (this.workspaceGraph && this.workspaceGraph.isLocalWorkspaceSpecifier(specifier)) {
318
323
  const match = this.workspaceGraph.getWorkspacePackageMatch(specifier);
319
324
  if (match && match.entryPoints && match.entryPoints.length > 0) {
320
- targetFile = Array.isArray(match.entryPoints) ? match.entryPoints : match.entryPoints;
325
+ targetFile = match.entryPoints[0];
321
326
  }
322
327
  } 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
- }
328
+ targetFile = this.resolver.resolveModulePath(filePath, specifier);
337
329
  }
338
330
 
339
331
  if (targetFile) {
340
- // Enforce uniform forward slash formats on targets
341
- const cleanTargetFile = Array.isArray(targetFile) ? slashify(targetFile[0]) : slashify(targetFile);
332
+ const cleanTargetFile = slashify(targetFile);
342
333
  this.context.importUsageRegistry.add(`${cleanTargetFile}:${symbolName}`);
343
334
  }
344
335
  }
@@ -478,16 +469,33 @@ export class RefactoringEngine {
478
469
  }
479
470
  if (isPackageEntryPoint) continue;
480
471
 
472
+ const originalNode = this.context.projectGraph.get(cleanExportedFile);
473
+ const unusedExportsInThisFile = [];
474
+
481
475
  for (const symbol of exportsSet) {
482
476
  const consumptionToken = `${cleanExportedFile}:${symbol}`;
483
477
  if (!this.context.importUsageRegistry?.has(consumptionToken)) {
484
- analysisSummary.deadExports.push({
478
+ // Retrieve the real source location if available
479
+ const loc = (originalNode && originalNode.symbolSourceLocations) ? originalNode.symbolSourceLocations.get(symbol) || { line: 0 } : { line: 0 };
480
+ unusedExportsInThisFile.push({
485
481
  symbol: symbol,
486
482
  file: relativeExportedFile,
487
- line: 7
483
+ line: loc.line
488
484
  });
489
485
  }
490
486
  }
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
+ }
491
499
  }
492
500
  }
493
501
  analysisSummary.unlistedDependencies = this.context.unlistedDependencies || [];
@@ -598,34 +606,52 @@ export class RefactoringEngine {
598
606
  }
599
607
 
600
608
  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
601
634
  for (const [filePath, node] of this.context.projectGraph.entries()) {
602
- // Pass A: Link all explicit imports (static + dynamic + re-export sources)
635
+ // A. Explicit Imports (Files)
603
636
  for (const specifier of node.explicitImports) {
604
637
  const resolvedPath = this.resolver.resolveModulePath(filePath, specifier);
605
638
  if (resolvedPath && this.context.projectGraph.has(resolvedPath)) {
606
- this.context.projectGraph.get(resolvedPath).incomingEdges.add(filePath);
639
+ const targetNode = this.context.projectGraph.get(resolvedPath);
640
+ targetNode.incomingEdges.add(filePath);
607
641
  node.outgoingEdges.add(resolvedPath);
608
642
 
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;
643
+ // Re-export protection: If this file re-exports everything from another file,
644
+ // the target file should be treated as an entry point for its own exports.
645
+ const isReExportAll = Array.from(node.internalExports.values()).some(exp =>
646
+ (exp.type === "re-export-all" || exp.type === "re-export-namespace") && exp.source === specifier
647
+ );
648
+ if (isReExportAll) {
649
+ targetNode.isLibraryEntry = true;
624
650
  }
625
651
  }
626
652
  }
627
653
 
628
- // Pass B: Link named-symbol imports through barrel/re-export chains
654
+ // B. Symbol-level Linking (Tracing through barrels)
629
655
  for (const specToken of node.importedSymbols) {
630
656
  const delimiterIndex = specToken.indexOf(':');
631
657
  if (delimiterIndex === -1) continue;
@@ -636,18 +662,18 @@ export class RefactoringEngine {
636
662
  if (!resolvedPath) continue;
637
663
 
638
664
  if (symbol === '*') {
639
- // Wildcard import / re-export-all: add a direct edge to the resolved file.
640
665
  if (this.context.projectGraph.has(resolvedPath)) {
641
666
  this.context.projectGraph.get(resolvedPath).incomingEdges.add(filePath);
642
667
  node.outgoingEdges.add(resolvedPath);
643
668
  }
644
669
  } else {
645
- // Named import: trace through barrel files to the actual declaration origin.
646
670
  const traceResolution = await this.barrelParser.determineSymbolDeclarationOrigin(resolvedPath, symbol, this.context.projectGraph);
647
671
  if (traceResolution && this.context.projectGraph.has(traceResolution.originFile)) {
648
- this.context.projectGraph.get(traceResolution.originFile).incomingEdges.add(filePath);
672
+ const originNode = this.context.projectGraph.get(traceResolution.originFile);
673
+ originNode.incomingEdges.add(filePath);
649
674
  node.outgoingEdges.add(traceResolution.originFile);
650
- node.importedSymbols.add(`${traceResolution.originFile}:${traceResolution.symbolName}`);
675
+ // Register the actual resolved symbol for dead export detection
676
+ node.importedSymbols.add(`${traceResolution.originFile}:${traceResolution.originSymbol}`);
651
677
  }
652
678
  }
653
679
  }
@@ -0,0 +1,87 @@
1
+ import { parentPort, workerData } from 'worker_threads';
2
+ import fs from 'fs/promises';
3
+ import { ASTAnalyzer } from '../ast/ASTAnalyzer.js';
4
+ import { OxcAnalyzer } from '../ast/OxcAnalyzer.js';
5
+
6
+ /**
7
+ * Worker Thread Execution Script
8
+ * Handles parallel AST parsing for a chunk of files and returns serialized results.
9
+ */
10
+ async function runTask() {
11
+ const { files, contextOptions } = workerData;
12
+ const results = [];
13
+
14
+ // Create a minimal context for analyzers
15
+ const mockContext = {
16
+ verbose: contextOptions.verbose,
17
+ projectGraph: new Map(),
18
+ getOrCreateNode: (path) => ({
19
+ filePath: path,
20
+ explicitImports: new Set(),
21
+ dynamicImports: new Set(),
22
+ importedSymbols: new Set(),
23
+ rawStringReferences: new Set(),
24
+ instantiatedIdentifiers: new Set(),
25
+ propertyAccessChains: new Set(),
26
+ internalExports: new Map(),
27
+ securityThreats: [],
28
+ localSuppressedRules: new Set(),
29
+ externalPackageUsage: new Set(),
30
+ symbolSourceLocations: new Map(),
31
+ jsxComponents: new Set(),
32
+ jsxProps: new Set(),
33
+ decorators: new Set(),
34
+ isFrameworkContract: false
35
+ })
36
+ };
37
+
38
+ const astAnalyzer = new ASTAnalyzer(mockContext);
39
+ const oxcAnalyzer = new OxcAnalyzer(mockContext);
40
+
41
+ for (const filePath of files) {
42
+ try {
43
+ const content = await fs.readFile(filePath, 'utf8');
44
+ const node = mockContext.getOrCreateNode(filePath);
45
+
46
+ // Use OXC if available, fallback to TS AST
47
+ if (oxcAnalyzer.isAvailable) {
48
+ await oxcAnalyzer.parseFile(filePath, content, node);
49
+ } else {
50
+ await astAnalyzer.parseFile(filePath, content, node);
51
+ }
52
+
53
+ // Serialize the node data for transfer back to main thread
54
+ results.push({
55
+ filePath: node.filePath,
56
+ explicitImports: Array.from(node.explicitImports),
57
+ dynamicImports: Array.from(node.dynamicImports),
58
+ importedSymbols: Array.from(node.importedSymbols),
59
+ rawStringReferences: Array.from(node.rawStringReferences),
60
+ instantiatedIdentifiers: Array.from(node.instantiatedIdentifiers),
61
+ propertyAccessChains: Array.from(node.propertyAccessChains),
62
+ internalExports: Object.fromEntries(node.internalExports),
63
+ securityThreats: node.securityThreats,
64
+ localSuppressedRules: Array.from(node.localSuppressedRules),
65
+ externalPackageUsage: Array.from(node.externalPackageUsage),
66
+ symbolSourceLocations: Object.fromEntries(node.symbolSourceLocations),
67
+ jsxComponents: Array.from(node.jsxComponents),
68
+ jsxProps: Array.from(node.jsxProps),
69
+ decorators: Array.from(node.decorators),
70
+ isFrameworkContract: node.isFrameworkContract
71
+ });
72
+ } catch (err) {
73
+ if (contextOptions.verbose) {
74
+ console.error(`[Worker] Failed to parse ${filePath}: ${err.message}`);
75
+ }
76
+ // Push null to keep array indices if needed, or just skip
77
+ results.push(null);
78
+ }
79
+ }
80
+
81
+ parentPort.postMessage(results);
82
+ }
83
+
84
+ runTask().catch(err => {
85
+ console.error(`[Worker Critical Fault] ${err.stack}`);
86
+ process.exit(1);
87
+ });
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * ============================================================================
3
- * Backend Services Plugins for entkapp v4.1.0
3
+ * Backend Services Plugins for entkapp v4.2.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.0.0
3
+ * Modern Frameworks Plugins for entkapp v4.2.0
4
4
  * ============================================================================
5
5
  * Built-in support for React, Vue, Svelte, and Angular.
6
6
  */