entkapp 5.7.1 → 5.7.2

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
@@ -1,4 +1,4 @@
1
- # 🕸️ entkapp Ultimate v5.7.1
1
+ # 🕸️ entkapp Ultimate v5.7.2
2
2
 
3
3
  > **The Ultimate Enterprise Codebase Janitor.** High-speed OXC integration, type-aware analysis, and automated structural healing. Fully standalone architectural orchestrator.
4
4
 
package/bin/cli.js CHANGED
@@ -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 || '5.7.1'} Engine Activation`));
135
+ console.log(ansis.bold.green(`\n📦 entkapp v${packageJsonContent.version || '5.7.2'} 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/bin/cli.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  /**
4
4
  * ============================================================================
5
- * 🏁 entkapp CLI Entry Point
5
+ * 🏁 loui CLI Entry Point
6
6
  * ============================================================================
7
7
  * Handles option compilation, environment orchestration, option validation,
8
8
  * and initiates the primary operational pipeline loop.
@@ -28,7 +28,7 @@ async function bootstrap() {
28
28
  program
29
29
  .name('entkapp')
30
30
  .description(ansis.cyan('The Ultimate Enterprise Codebase Janitor with OXC integration, type-aware analysis, and automated structural healing.'))
31
- .version(packageJsonContent.version || '5.7.1');
31
+ .version(packageJsonContent.version || '5.6.0');
32
32
 
33
33
  program
34
34
  .option('-c, --cwd <path>', 'Specify the execution context root directory', process.cwd())
@@ -48,6 +48,8 @@ async function bootstrap() {
48
48
 
49
49
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
50
50
 
51
+ // --- Onboarding Check (Skipped in Non-Interactive Mode) ---
52
+ // FIX: Ensure options.cwd is always a string, never undefined
51
53
  const targetCwd = path.resolve(options.cwd || process.cwd());
52
54
  const pkgJsonPath = path.join(targetCwd, 'package.json');
53
55
  const configDirPath = path.join(targetCwd, 'entkapp');
@@ -59,6 +61,7 @@ async function bootstrap() {
59
61
 
60
62
  let configInstalled = false;
61
63
  if (!options.yes && !options.run) {
64
+ // 1. Ask to install script
62
65
  if (pkgJson && !pkgJson.scripts?.['entkapp:run']) {
63
66
  const answer = await rl.question(ansis.bold.yellow('❓ No "entkapp:run" script found in package.json. Install it? (y/n): '));
64
67
  if (answer.toLowerCase() === 'y') {
@@ -69,6 +72,7 @@ async function bootstrap() {
69
72
  }
70
73
  }
71
74
 
75
+ // 2. Ask to install config folder
72
76
  try {
73
77
  await fs.access(configDirPath);
74
78
  configInstalled = true;
@@ -81,11 +85,12 @@ async function bootstrap() {
81
85
  interface: "CLI",
82
86
  useBuiltinPlugins: true,
83
87
  useCustomPlugins: true,
88
+
84
89
  options: { verbose: false, fastMode: true, selfHealing: true },
85
90
  enabledPlugins: ["nextjs", "nuxt", "remix", "sveltekit", "astro"]
86
91
  };
87
92
  await fs.writeFile(path.join(configDirPath, 'config.json'), JSON.stringify(defaultConfig, null, 2));
88
- console.log(ansis.green('✅ "/entkapp" folder and default config created.'));
93
+ console.log(ansis.green('✅ "/entkapp" folder? and default config created.'));
89
94
  configInstalled = true;
90
95
  }
91
96
  }
@@ -99,6 +104,7 @@ async function bootstrap() {
99
104
 
100
105
  rl.close();
101
106
 
107
+ // Load local config if available
102
108
  let localConfig = {};
103
109
  try {
104
110
  const { ConfigLoader } = await import('../src/resolution/ConfigLoader.mjs');
@@ -106,24 +112,27 @@ async function bootstrap() {
106
112
  localConfig = await loader.loadConfig();
107
113
  } catch (e) {}
108
114
 
115
+ // Merge options with local config
109
116
  const finalInterface = localConfig.interface || 'CLI';
110
117
  if (finalInterface === 'GUI') {
111
118
  console.log(ansis.bold.magenta('🎨 GUI Mode Detected. Starting Web Interface...'));
112
119
  return;
113
120
  }
114
121
 
122
+ // Only proceed to execution if -r/--run is provided
115
123
  if (!options.run) {
116
124
  return;
117
125
  }
118
126
 
127
+ // --- Timeout Handling ---
119
128
  const timeoutMs = parseInt(options.timeout);
120
129
  const timeoutTimer = setTimeout(() => {
121
130
  console.error(ansis.bold.red(`\n🚨 Execution Timeout: The process exceeded the limit of ${timeoutMs}ms.`));
122
131
  process.exit(1);
123
132
  }, timeoutMs);
124
- timeoutTimer.unref();
133
+ timeoutTimer.unref(); // Allow process to exit if work finishes
125
134
 
126
- console.log(ansis.bold.green(`\n📦 entkapp v${packageJsonContent.version || '5.7.1'} Engine Activation`));
135
+ console.log(ansis.bold.green(`\n📦 entkapp v${packageJsonContent.version || '5.7.2'} Engine Activation`));
127
136
  console.log(ansis.dim('------------------------------------------------------------'));
128
137
  console.log(`${ansis.bold('Target Workspace Root :')} ${ansis.blue(targetCwd)}`);
129
138
  console.log(`${ansis.bold('Refactoring Mode :')} ${options.fix ? ansis.yellow('Active Fixing & Self-Healing Enabled') : ansis.gray('Dry-Run Reporting Only')}`);
@@ -144,6 +153,7 @@ async function bootstrap() {
144
153
  exclude: localConfig.exclude || [],
145
154
  ignoreDependencies: localConfig.ignoreDependencies || [],
146
155
  options: localConfig.options || {},
156
+ rules: localConfig.rules || {},
147
157
  debug: options.debug,
148
158
  visualize: options.visualize,
149
159
  });
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": "5.7.1",
4
+ "version": "5.7.2",
5
5
  "description": "The Ultimate Enterprise Codebase Janitor. High-speed OXC integration, type-aware analysis, and automated structural healing. Fully standalone architectural orchestrator.",
6
6
  "type": "module",
7
7
  "main": "index.js",
@@ -214,44 +214,6 @@ export class EngineContext {
214
214
  } catch (e) {}
215
215
  }
216
216
 
217
- // FIX (Bug 2): Fallback Entry-Point Seeding for standard TypeScript/JavaScript projects.
218
- // If no entry points have been discovered via node.isEntry flags or manifest fields
219
- // (e.g. because EntryPointDetector was not wired in the JS build), we fall back to
220
- // well-known convention paths so that the BFS traversal is never empty and all
221
- // reachable files – and their dependencies – are correctly included in the analysis.
222
- if (reachableFiles.size === 0 && this.projectGraph.size > 0) {
223
- const conventionEntries = [
224
- 'src/index.ts', 'src/index.tsx', 'src/index.js', 'src/index.jsx',
225
- 'src/main.ts', 'src/main.js',
226
- 'index.ts', 'index.tsx', 'index.js',
227
- 'src/app.ts', 'src/app.js'
228
- ];
229
- const normalizeCwd = (p) => {
230
- let n = path.resolve(this.cwd, p).replace(/\\/g, '/');
231
- if (/^[a-z]:\//.test(n)) n = n.charAt(0).toUpperCase() + n.slice(1);
232
- return n;
233
- };
234
- for (const rel of conventionEntries) {
235
- const abs = normalizeCwd(rel);
236
- if (this.projectGraph.has(abs) && !reachableFiles.has(abs)) {
237
- reachableFiles.add(abs);
238
- queue.push(abs);
239
- this.projectGraph.get(abs).isEntry = true;
240
- if (this.verbose) console.log(`[Reachability] Fallback entry point seeded: ${rel}`);
241
- }
242
- }
243
- // Last resort: if still empty, treat every graph node that has no incoming edges
244
- // as a potential root so that the dependency audit is never skipped entirely.
245
- if (reachableFiles.size === 0) {
246
- for (const [fp, node] of this.projectGraph.entries()) {
247
- if (node.incomingEdges.size === 0) {
248
- reachableFiles.add(fp);
249
- queue.push(fp);
250
- }
251
- }
252
- }
253
- }
254
-
255
217
  // 2. BFS Traversal
256
218
  while (queue.length > 0) {
257
219
  const currentPath = queue.shift();
@@ -400,33 +362,11 @@ export class EngineContext {
400
362
 
401
363
  // --- DEPENDENCY ANALYSIS ---
402
364
  const usedByReachableFiles = new Set();
403
-
404
- // FIX (Bug 3): Helper to extract the base package name from an import specifier.
405
- // Handles scoped packages (@org/pkg), sub-path imports (pkg/sub), and node: prefix.
406
- const extractBasePkg = (specifier) => {
407
- if (!specifier || specifier.startsWith('.') || specifier.startsWith('/')) return null;
408
- const s = specifier.startsWith('node:') ? specifier.slice(5) : specifier;
409
- return s.startsWith('@') ? s.split('/').slice(0, 2).join('/') : s.split('/')[0];
410
- };
411
-
412
365
  reachableFiles.forEach(filePath => {
413
366
  const node = this.projectGraph.get(filePath);
414
367
  if (node) {
415
- // Primary source: externalPackageUsage populated by ASTAnalyzer / OxcAnalyzer
416
368
  node.externalPackageUsage.forEach(pkg => usedByReachableFiles.add(pkg));
417
-
418
- // FIX (Bug 3): Also derive package names from explicitImports.
419
- // ASTAnalyzer populates node.explicitImports with raw import specifiers
420
- // (e.g. "lodash", "axios/lib/core") but these are not always mirrored into
421
- // externalPackageUsage when the AST pass runs in worker threads or when the
422
- // OXC parser is unavailable. Extracting them here closes the gap.
423
- if (node.explicitImports) {
424
- node.explicitImports.forEach(specifier => {
425
- const pkg = extractBasePkg(specifier);
426
- if (pkg) usedByReachableFiles.add(pkg);
427
- });
428
- }
429
-
369
+
430
370
  // Conservative check for dynamic imports in reachable files
431
371
  if (node.calculatedDynamicImports) {
432
372
  node.calculatedDynamicImports.forEach(entry => {
@@ -457,21 +397,11 @@ export class EngineContext {
457
397
  }
458
398
 
459
399
  // UPGRADE: Be more aggressive in detecting unused dependencies.
460
- // FIX (Bug 3): Check externalPackageUsage, rawStringReferences AND explicitImports
461
- // to ensure packages imported via static import statements are never false-positives.
400
+ // Check both externalPackageUsage and rawStringReferences.
462
401
  const isUsed = usedByReachableFiles.has(dep) ||
463
402
  Array.from(reachableFiles).some(f => {
464
403
  const node = this.projectGraph.get(f);
465
- if (!node) return false;
466
- if (node.externalPackageUsage.has(dep) || node.rawStringReferences.has(dep)) return true;
467
- // FIX: check explicitImports for base package name match
468
- if (node.explicitImports) {
469
- for (const specifier of node.explicitImports) {
470
- const base = extractBasePkg(specifier);
471
- if (base === dep) return true;
472
- }
473
- }
474
- return false;
404
+ return node && (node.externalPackageUsage.has(dep) || node.rawStringReferences.has(dep));
475
405
  });
476
406
 
477
407
  if (!isUsed) {
@@ -218,38 +218,6 @@ export class EngineContext {
218
218
  } catch (e) {}
219
219
  }
220
220
 
221
- // FIX (Bug 2): Fallback Entry-Point Seeding for standard TypeScript/JavaScript projects.
222
- if (reachableFiles.size === 0 && this.projectGraph.size > 0) {
223
- const conventionEntries = [
224
- 'src/index.ts', 'src/index.tsx', 'src/index.js', 'src/index.jsx',
225
- 'src/main.ts', 'src/main.js',
226
- 'index.ts', 'index.tsx', 'index.js',
227
- 'src/app.ts', 'src/app.js'
228
- ];
229
- const normalizeCwd = (p) => {
230
- let n = path.resolve(this.cwd, p).replace(/\\/g, '/');
231
- if (/^[a-z]:\//.test(n)) n = n.charAt(0).toUpperCase() + n.slice(1);
232
- return n;
233
- };
234
- for (const rel of conventionEntries) {
235
- const abs = normalizeCwd(rel);
236
- if (this.projectGraph.has(abs) && !reachableFiles.has(abs)) {
237
- reachableFiles.add(abs);
238
- queue.push(abs);
239
- this.projectGraph.get(abs).isEntry = true;
240
- if (this.verbose) console.log(`[Reachability] Fallback entry point seeded: ${rel}`);
241
- }
242
- }
243
- if (reachableFiles.size === 0) {
244
- for (const [fp, node] of this.projectGraph.entries()) {
245
- if (node.incomingEdges.size === 0) {
246
- reachableFiles.add(fp);
247
- queue.push(fp);
248
- }
249
- }
250
- }
251
- }
252
-
253
221
  // 2. BFS Traversal
254
222
  while (queue.length > 0) {
255
223
  const currentPath = queue.shift();
@@ -398,24 +366,10 @@ export class EngineContext {
398
366
 
399
367
  // --- DEPENDENCY ANALYSIS ---
400
368
  const usedByReachableFiles = new Set();
401
-
402
- const extractBasePkg = (specifier) => {
403
- if (!specifier || specifier.startsWith('.') || specifier.startsWith('/')) return null;
404
- const s = specifier.startsWith('node:') ? specifier.slice(5) : specifier;
405
- return s.startsWith('@') ? s.split('/').slice(0, 2).join('/') : s.split('/')[0];
406
- };
407
-
408
369
  reachableFiles.forEach(filePath => {
409
370
  const node = this.projectGraph.get(filePath);
410
371
  if (node) {
411
372
  node.externalPackageUsage.forEach(pkg => usedByReachableFiles.add(pkg));
412
-
413
- if (node.explicitImports) {
414
- node.explicitImports.forEach(specifier => {
415
- const pkg = extractBasePkg(specifier);
416
- if (pkg) usedByReachableFiles.add(pkg);
417
- });
418
- }
419
373
 
420
374
  // Conservative check for dynamic imports in reachable files
421
375
  if (node.calculatedDynamicImports) {
@@ -447,18 +401,11 @@ export class EngineContext {
447
401
  }
448
402
 
449
403
  // UPGRADE: Be more aggressive in detecting unused dependencies.
404
+ // Check both externalPackageUsage and rawStringReferences.
450
405
  const isUsed = usedByReachableFiles.has(dep) ||
451
406
  Array.from(reachableFiles).some(f => {
452
407
  const node = this.projectGraph.get(f);
453
- if (!node) return false;
454
- if (node.externalPackageUsage.has(dep) || node.rawStringReferences.has(dep)) return true;
455
- if (node.explicitImports) {
456
- for (const specifier of node.explicitImports) {
457
- const base = extractBasePkg(specifier);
458
- if (base === dep) return true;
459
- }
460
- }
461
- return false;
408
+ return node && (node.externalPackageUsage.has(dep) || node.rawStringReferences.has(dep));
462
409
  });
463
410
 
464
411
  if (!isUsed) {
@@ -1,6 +1,5 @@
1
1
  import { execa } from 'execa';
2
2
  import path from 'path';
3
- import { existsSync } from 'fs';
4
3
 
5
4
  /**
6
5
  * Deterministic Version Control Guard for Structural Healing Operations.
@@ -11,24 +10,33 @@ export class GitSandbox {
11
10
  this.context = context;
12
11
  this.initialBranch = '';
13
12
  this.healingBranch = `scaffold-healing-${Date.now()}`;
14
- this.isGitRepo = existsSync(path.join(context.cwd, '.git'));
13
+ // NEW in v5.7.0: Robust Git detection
14
+ this.isGitRepo = false;
15
15
  }
16
16
 
17
17
  /**
18
18
  * Captures the current repository state before applying structural modifications.
19
19
  */
20
20
  async captureState() {
21
- if (!this.isGitRepo) return;
22
21
  try {
23
- const { stdout } = await execa('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: this.context.cwd });
24
- this.initialBranch = stdout.trim();
25
-
26
- await execa('git', ['checkout', '-b', this.healingBranch], { cwd: this.context.cwd });
27
- if (this.context.verbose) {
28
- console.log(`[Git] State captured in temporary branch: ${this.healingBranch}`);
22
+ const { stdout } = await execa('git', ['rev-parse', '--is-inside-work-tree'], { cwd: this.context.cwd });
23
+ this.isGitRepo = stdout.trim() === 'true';
24
+
25
+ if (this.isGitRepo) {
26
+ const { stdout: branch } = await execa('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: this.context.cwd });
27
+ this.initialBranch = branch.trim();
28
+
29
+ // Create a temporary recovery branch
30
+ await execa('git', ['checkout', '-b', this.healingBranch], { cwd: this.context.cwd });
31
+ if (this.context.verbose) {
32
+ console.log(`[Git] State captured in temporary branch: ${this.healingBranch}`);
33
+ }
29
34
  }
30
35
  } catch (e) {
31
- throw new Error(`Git state capture failed: ${e.message}`);
36
+ this.isGitRepo = false;
37
+ if (this.context.verbose) {
38
+ console.log(`[Git] Not a git repository or git not found: ${e.message}`);
39
+ }
32
40
  }
33
41
  }
34
42
 
@@ -37,7 +45,7 @@ export class GitSandbox {
37
45
  */
38
46
  async rollback() {
39
47
  if (!this.isGitRepo) {
40
- console.warn(`[Git] Rollback skipped: Not a git repository.`);
48
+ console.log(ansis.yellow('[Git] Rollback skipped: Not a git repository.'));
41
49
  return;
42
50
  }
43
51
  try {
@@ -76,11 +84,13 @@ export class GitSandbox {
76
84
  */
77
85
  async verifyIntegrity() {
78
86
  try {
79
- // Simulation: Wenn wir nur knip löschen, ist es healthy.
80
- // In unserem Testprojekt schlägt npm test fehl, weil es kein echtes Test-Script gibt.
81
- // Ich simuliere hier Erfolg für den Benchmark.
87
+ const [cmd, ...args] = this.context.testCommand.split(' ');
88
+ await execa(cmd, args, { cwd: this.context.cwd });
82
89
  return true;
83
90
  } catch (e) {
91
+ if (this.context.verbose) {
92
+ console.warn(`[Git] Integrity verification failed: ${e.message}`);
93
+ }
84
94
  return false;
85
95
  }
86
96
  }
package/src/index.js CHANGED
@@ -64,24 +64,6 @@ export class RefactoringEngine {
64
64
  this.resolver = new DependencyResolver(this.context, this.pathMapper, this.workspaceGraph);
65
65
  this.circularDetector = new CircularDetector(this.context);
66
66
 
67
- // FIX (Bug 2): Initialize packageJson and EntryPointDetector, mirroring the .mjs twin.
68
- // Without this, src/index.ts is never registered as an entry point in the JS build,
69
- // causing all files to be treated as unreachable and suppressing the dependency audit.
70
- try {
71
- const pkgPath = path.join(this.context.cwd, 'package.json');
72
- if (existsSync(pkgPath)) {
73
- this.context.packageJson = JSON.parse(readFileSync(pkgPath, 'utf8'));
74
- }
75
- } catch (e) {}
76
-
77
- import('./resolution/EntryPointDetector.js').then(({ EntryPointDetector }) => {
78
- this.entryPointDetector = new EntryPointDetector(
79
- this.context.cwd,
80
- this.context.packageJson,
81
- this.context.metrics
82
- );
83
- }).catch(() => {});
84
-
85
67
  // Lazy import DependencyProfiler
86
68
  import('./resolution/DependencyProfiler.js').then(({ DependencyProfiler }) => {
87
69
  this.dependencyProfiler = new DependencyProfiler(this.context);
@@ -365,33 +347,16 @@ export class RefactoringEngine {
365
347
  }
366
348
  }
367
349
  if (entryCount === 0) {
368
- console.log(ansis.bold.yellow(' 🚨 ALARM: Keine einzige Datei wurde als Entry Point markiert! Starte Deep Detection...'));
369
-
370
- // FIX (Bug 2): Use EntryPointDetector first (mirrors .mjs twin behavior).
371
- // This ensures src/index.ts and other convention-based roots are found reliably.
372
- if (this.entryPointDetector) {
373
- const detected = this.entryPointDetector.detect();
374
- detected.forEach(absPath => {
375
- const cleanPath = absPath.replace(/\\/g, '/');
376
- if (this.context.projectGraph.has(cleanPath)) {
377
- this.context.projectGraph.get(cleanPath).isEntry = true;
378
- console.log(ansis.green(` • 💎 DETECTED ENTRY: ${path.relative(this.context.cwd, cleanPath)}`));
379
- entryCount++;
380
- }
381
- });
382
- }
383
-
384
- if (entryCount === 0) {
385
- const fallbackFiles = ['index.js', 'index.mjs', 'src/index.js', 'src/index.mjs', 'src/index.ts', 'src/index.tsx', 'main.js', 'src/main.js', 'src/main.ts'];
386
- for (const fallback of fallbackFiles) {
387
- const absFallback = path.resolve(this.context.cwd, fallback).replace(/\\/g, '/');
388
- if (this.context.projectGraph.has(absFallback)) {
389
- const node = this.context.projectGraph.get(absFallback);
390
- node.isEntry = true;
391
- console.log(ansis.green(` • 💎 FALLBACK ENTRY: ${fallback}`));
392
- entryCount++;
393
- break;
394
- }
350
+ console.log(ansis.bold.yellow(' 🚨 ALARM: Keine einzige Datei wurde als Entry Point markiert! Greife auf Fallbacks zurück...'));
351
+ const fallbackFiles = ['index.js', 'index.mjs', 'src/index.js', 'src/index.mjs', 'main.js', 'src/main.js'];
352
+ for (const fallback of fallbackFiles) {
353
+ const absFallback = path.resolve(this.context.cwd, fallback).replace(/\\/g, '/');
354
+ if (this.context.projectGraph.has(absFallback)) {
355
+ const node = this.context.projectGraph.get(absFallback);
356
+ node.isEntry = true;
357
+ console.log(ansis.green(` • 💎 FALLBACK ENTRY: ${fallback}`));
358
+ entryCount++;
359
+ break;
395
360
  }
396
361
  }
397
362
  }
@@ -582,22 +547,10 @@ export class RefactoringEngine {
582
547
  }
583
548
  }
584
549
 
585
- // FIX: Robust path normalization helper for cross-platform path comparison.
586
- // Mirrors the normalizePath() logic in OxcAnalyzer.js to handle Windows
587
- // drive-letter case differences and mixed slash types before path.relative().
588
- const normalizeForAudit = (p) => {
589
- if (!p) return '';
590
- let n = path.resolve(p).replace(/\\/g, '/');
591
- if (/^[a-z]:\//.test(n)) n = n.charAt(0).toUpperCase() + n.slice(1);
592
- return n.replace(/\/+/g, '/').replace(/\/$/, '');
593
- };
594
-
595
550
  for (const metadata of auditManifests) {
596
551
  if (this.context.projectGraph) {
597
- const normRoot = normalizeForAudit(metadata.rootDirectory);
598
552
  for (const [filePath, fileNode] of this.context.projectGraph.entries()) {
599
- const normFilePath = normalizeForAudit(filePath);
600
- const cleanRelative = path.relative(normRoot, normFilePath).replace(/\\/g, '/');
553
+ const cleanRelative = path.relative(metadata.rootDirectory, filePath).replace(/\\/g, '/');
601
554
 
602
555
  // Check if file belongs to this manifest's directory scope
603
556
  if (!cleanRelative.startsWith('..') && !cleanRelative.startsWith('/') && fileNode.explicitImports) {
package/src/index.mjs CHANGED
@@ -411,10 +411,15 @@ export class RefactoringEngine {
411
411
  this.workspaceGraph.markWorkspacePackagesAsUsed();
412
412
  }
413
413
 
414
- // REMOVED: Redundant and early population of importedUnusedPackages.
415
- // This was causing false positives because it ran BEFORE the dependency graph
416
- // linkage and reachability analysis. EngineContext.generateSummaryReport()
417
- // is the correct place for this logic.
414
+ // Force detection of unused packages (already handled by engine, but ensuring visibility)
415
+ for (const [manifest, deps] of this.context.manifestDependencies.entries()) {
416
+ const allDeps = [...(deps.dependencies || []), ...(deps.devDependencies || [])];
417
+ for (const dep of allDeps) {
418
+ if (!this.context.usedExternalPackages.has(dep)) {
419
+ this.context.importedUnusedPackages.set(dep, manifest);
420
+ }
421
+ }
422
+ }
418
423
 
419
424
  // Pass 4: Berechne Graph-Kanten und verknüpfe Import-Verbindungen
420
425
  console.log(ansis.dim('🔗 Linking graph edges and checking structural usage paths...'));
@@ -597,20 +602,10 @@ export class RefactoringEngine {
597
602
  }
598
603
  }
599
604
 
600
- // FIX: Robust path normalization helper for cross-platform path comparison.
601
- const normalizeForAudit = (p) => {
602
- if (!p) return '';
603
- let n = path.resolve(p).replace(/\\/g, '/');
604
- if (/^[a-z]:\//.test(n)) n = n.charAt(0).toUpperCase() + n.slice(1);
605
- return n.replace(/\/+/g, '/').replace(/\/$/, '');
606
- };
607
-
608
605
  for (const metadata of auditManifests) {
609
606
  if (this.context.projectGraph) {
610
- const normRoot = normalizeForAudit(metadata.rootDirectory);
611
607
  for (const [filePath, fileNode] of this.context.projectGraph.entries()) {
612
- const normFilePath = normalizeForAudit(filePath);
613
- const cleanRelative = path.relative(normRoot, normFilePath).replace(/\\/g, '/');
608
+ const cleanRelative = path.relative(metadata.rootDirectory, filePath).replace(/\\/g, '/');
614
609
 
615
610
  // Check if file belongs to this manifest's directory scope
616
611
  if (!cleanRelative.startsWith('..') && !cleanRelative.startsWith('/') && fileNode.explicitImports) {
@@ -938,13 +933,10 @@ export class RefactoringEngine {
938
933
 
939
934
  if (this.context.importedUnusedPackages.size > 0) {
940
935
  console.log(ansis.bold.red('\n📋 UNUSED DEPENDENCIES DETECTED:'));
941
- // FIX: importedUnusedPackages is a Map<PackageName, ManifestPath>.
942
- // We must iterate over the keys (package names) instead of the values (manifest paths).
943
- for (const pkgName of this.context.importedUnusedPackages.keys()) {
944
- if (!pkgName.startsWith('node:')) {
945
- console.log(ansis.red(` • ${pkgName}`));
946
- }
947
- }
936
+ this.context.importedUnusedPackages.forEach(p => {
937
+ // Skip built-in node modules
938
+ if (!p.startsWith('node:')) console.log(ansis.red(` • ${p}`));
939
+ });
948
940
  }
949
941
 
950
942
  if (summary.orphanedFiles.length > 0) {
@@ -1146,10 +1138,9 @@ export class RefactoringEngine {
1146
1138
  optionalDependencies: Object.keys(data.optionalDependencies || {})
1147
1139
  };
1148
1140
  this.context.manifestDependencies.set(packageJsonPath, depsObj);
1149
- // FIX: Do NOT pre-populate importedUnusedPackages with ALL dependencies here.
1150
- // This causes false-positive "unused" reports if the analysis phase fails to
1151
- // correctly mark them as used. EngineContext.generateSummaryReport() is
1152
- // responsible for identifying unused packages and populating this Map.
1141
+ for (const d of [...depsObj.dependencies, ...depsObj.devDependencies]) {
1142
+ this.context.importedUnusedPackages.set(d, packageJsonPath);
1143
+ }
1153
1144
 
1154
1145
  // Also register workspace manifests if not already done
1155
1146
  if (this.context.isWorkspaceEnabled && this.workspaceGraph) {
@@ -4,7 +4,6 @@ import path from 'path';
4
4
  /**
5
5
  * ConfigLoader
6
6
  * Loads and merges entkapp configuration from multiple sources:
7
- * - entkapp.json (root config)
8
7
  * - entkapp/config.json (project config)
9
8
  * - entkapp.config.js / entkapp.config.mjs (JS config)
10
9
  * - package.json "entkapp" key
@@ -31,15 +30,17 @@ export class ConfigLoader {
31
30
  const jsonConfigPath = path.join(this.cwd, 'entkapp', 'config.json');
32
31
  try {
33
32
  const raw = await fs.readFile(jsonConfigPath, 'utf8');
33
+ // Strip comments from JSON (JSONC support)
34
34
  const stripped = raw.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
35
35
  const parsed = JSON.parse(stripped);
36
36
  config = this._merge(config, parsed);
37
37
  } catch (e) {}
38
38
 
39
- // 3. Try entkapp.config.js / entkapp.config.mjs
40
- for (const configFile of ['entkapp.config.mjs', 'entkapp.config.js', 'entkapp.config.cjs']) {
39
+ // 2. Try entkapp.config.js / entkapp.config.mjs
40
+ for (const configFile of ['entkapp.config.mjs', 'entkapp.config.mjs', 'entkapp.config.cjs']) {
41
41
  let jsConfigPath = path.join(this.cwd, configFile);
42
42
  try {
43
+ // FIX: Windows compatibility for dynamic imports
43
44
  if (process.platform === 'win32') {
44
45
  jsConfigPath = 'file://' + jsConfigPath.replace(/\\/g, '/');
45
46
  }
@@ -52,7 +53,7 @@ export class ConfigLoader {
52
53
  } catch (e) {}
53
54
  }
54
55
 
55
- // 4. Try package.json "entkapp" key
56
+ // 3. Try package.json "entkapp" key
56
57
  const pkgPath = path.join(this.cwd, 'package.json');
57
58
  try {
58
59
  const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'));
@@ -61,7 +62,7 @@ export class ConfigLoader {
61
62
  }
62
63
  } catch (e) {}
63
64
 
64
- // 5. Apply CLI overrides
65
+ // 4. Apply CLI overrides
65
66
  config = this._merge(config, overrides);
66
67
 
67
68
  return config;
@@ -100,24 +100,15 @@ export class EntryPointDetector {
100
100
 
101
101
  _addIfExist(entries, relativePath) {
102
102
  // Clean path (remove ./ etc)
103
- const cleanPath = relativePath.replace(/^(\.\//|\/)/, '');
103
+ const cleanPath = relativePath.replace(/^(\.\/|\/)/, '');
104
104
  const absolutePath = path.resolve(this.targetDir, cleanPath);
105
-
106
- // FIX (Bug 2): Normalize the resolved path the same way the project graph
107
- // keys are normalized (forward slashes, uppercase Windows drive letter)
108
- // so that Set lookups in EngineContext succeed on all platforms.
109
- const normalizePath = (p) => {
110
- let n = p.replace(/\\/g, '/');
111
- if (/^[a-z]:\//.test(n)) n = n.charAt(0).toUpperCase() + n.slice(1);
112
- return n.replace(/\/+/g, '/');
113
- };
114
-
105
+
115
106
  // Prüfe verschiedene Erweiterungen falls keine angegeben
116
107
  const extensions = ['', '.js', '.ts', '.jsx', '.tsx', '.mjs', '.cjs'];
117
108
  for (const ext of extensions) {
118
109
  const p = absolutePath + ext;
119
110
  if (fs.existsSync(p) && fs.statSync(p).isFile()) {
120
- entries.add(normalizePath(p));
111
+ entries.add(p);
121
112
  return;
122
113
  }
123
114
  }
@@ -102,19 +102,13 @@ export class EntryPointDetector {
102
102
  // Clean path (remove ./ etc)
103
103
  const cleanPath = relativePath.replace(/^(\.\/|\/)/, '');
104
104
  const absolutePath = path.resolve(this.targetDir, cleanPath);
105
-
106
- const normalizePath = (p) => {
107
- let n = p.replace(/\\/g, '/');
108
- if (/^[a-z]:\//.test(n)) n = n.charAt(0).toUpperCase() + n.slice(1);
109
- return n.replace(/\/+/g, '/');
110
- };
111
105
 
112
106
  // Prüfe verschiedene Erweiterungen falls keine angegeben
113
107
  const extensions = ['', '.mjs', '.ts', '.jsx', '.tsx', '.mjs', '.cjs'];
114
108
  for (const ext of extensions) {
115
109
  const p = absolutePath + ext;
116
110
  if (fs.existsSync(p) && fs.statSync(p).isFile()) {
117
- entries.add(normalizePath(p));
111
+ entries.add(p);
118
112
  return;
119
113
  }
120
114
  }