pkg-scaffold 2.4.0 → 3.0.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
@@ -1,107 +1,94 @@
1
- # šŸš€ pkg-scaffold
2
-
3
- **Advanced Dependency Intelligence & Zero-Config Workspace Initializer**
4
-
5
- `pkg-scaffold` is a high-performance CLI tool designed to audit, clean, and initialize your JavaScript/TypeScript workspaces. It goes far beyond simple scaffolding by analyzing your source code's Abstract Syntax Tree (AST) to find critical issues like undeclared dependencies, orphaned packages, and security leaks.
6
-
7
-
8
- [![npm version](https://img.shields.io/npm/v/pkg-scaffold.svg?style=flat&color=CB3837)](https://www.npmjs.com/package/pkg-scaffold)
9
- [![npm downloads](https://img.shields.io/npm/dm/pkg-scaffold.svg?style=flat&color=34ADFF)](https://www.npmjs.com/package/pkg-scaffold)
10
- [![License](https://img.shields.io/badge/license-MIT-orange.svg?style=flat)](LICENSE)
11
- [![GitHub stars](https://img.shields.io/github/stars/DreamLongYT/pkg-scaffold.svg?style=flat&color=gold)](https://github.com/DreamLongYT/pkg-scaffold/stargazers)
12
-
13
- ---
14
-
15
- ## ✨ Why pkg-scaffold?
16
-
17
- Most tools just create a `package.json`. `pkg-scaffold` **understands** your code. It bridges the gap between your source files and your configuration.
18
-
19
- ### šŸ” Deep Intelligence Features
20
-
21
- * **🚨 Ghost Dependency Detection**: Finds packages you `import` in your code but forgot to add to `package.json`. These are critical errors that will break your CI/CD or production builds.
22
- * **šŸ—‘ļø Orphaned Package Identification**: Finds packages listed in your `package.json` that are never actually used in your code. Perfect for reducing bundle size and "dependency bloat".
23
- * **⚔ Unused Import Auditor**: Pinpoints specific files and line numbers where a package is imported but its identifier is never referenced.
24
- * **šŸ” Security Compliance**: Automatically scans for hardcoded secrets (API keys, tokens, passwords) and offers to move them safely into `.env` files.
25
- * **šŸ“› Deprecation Guard**: Live-checks your dependencies against the npm registry to warn you about deprecated or non-existent packages.
26
- * **šŸ—ļø Intelligent Scaffolding**: Detects your framework (React, Vue, Svelte, Next.js, etc.) and automatically generates optimized `tsconfig.json`, `eslint.config.js`, `.prettierrc`, and `.gitignore`.
27
- * **šŸ“¦ Monorepo Ready**: Automatically detects sub-workspaces and offers to set up `pnpm-workspace.yaml` or npm/yarn workspaces.
28
-
29
- ---
30
-
31
- ## šŸš€ Quick Start
32
-
33
- You don't even need to install it to try it out:
34
-
35
- ```bash
36
- npx pkg-scaffold
37
- ```
38
-
39
- Or install it globally:
40
-
41
- ```bash
42
- npm install -g pkg-scaffold
43
- # then run
44
- pkg-scaffold
45
- ```
46
-
47
- ---
48
-
49
- ## šŸ› ļø How it works
50
-
51
- ### 1. The Scan
52
- `pkg-scaffold` crawls your workspace, ignoring `node_modules` and build artifacts. It parses every `.js`, `.ts`, `.jsx`, and `.tsx` file using a high-performance AST engine.
53
-
54
- ### 2. The Analysis
55
- It maps your imports against your `package.json`. It knows about:
56
- * **Aliases**: Understands that `lodash` is often imported as `_`.
57
- * **Binaries**: Knows that `tsc` comes from `typescript` and `vite` from `vite`.
58
- * **Type-only imports**: Correctly handles TypeScript `import type` without flagging them as unused.
59
- * **Side-effects**: Detects `import 'css-file'` or polyfills correctly.
60
-
61
- ### 3. The Fix
62
- The tool is interactive. It will ask you before:
63
- * Adding missing (ghost) dependencies.
64
- * Pruning unused (orphaned) packages.
65
- * Injecting `dotenv` initialization.
66
- * Moving secrets to `.env`.
67
-
68
- ---
69
-
70
- ## šŸ“Š Summary Report
71
- At the end of every run, you get a clear intelligence summary:
72
-
73
- ```text
74
- ═══════════════════════════════════════════════════════════════════
75
- šŸ“Š DEPENDENCY INTELLIGENCE SUMMARY
76
- ═══════════════════════════════════════════════════════════════════
77
- šŸ“ Files scanned: 42
78
- šŸ“¦ Packages imported: 18
79
- 🚨 Ghost deps (missing): 2 — CRITICAL
80
- šŸ—‘ļø Orphaned deps (unused): 3
81
- ⚔ Unused imports: 5
82
- šŸ“› Deprecated packages: 1
83
- šŸ” Hardcoded secrets: 1 — SECURITY RISK
84
- ═══════════════════════════════════════════════════════════════════
85
- ```
86
-
87
- ---
88
-
89
- ## šŸ¤ Contributing
90
-
91
- Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
92
-
93
- 1. Fork the Project
94
- 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
95
- 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
96
- 4. Push to the Branch (`git push origin feature/AmazingFeature`)
97
- 5. Open a Pull Request
98
-
99
- ---
100
-
101
- ## šŸ“„ License
102
-
103
- Distributed under the MIT License. See `LICENSE` for more information.
104
-
105
- ---
106
-
107
- **Built with ā¤ļø by [DreamLongYT](https://github.com/DreamLongYT)**
1
+ # šŸ“¦ pkg-scaffold v3.0.0
2
+
3
+ **Enterprise-Grade AST Syntax Refactoring & Self-Healing Engine**
4
+
5
+ [![npm version](https://img.shields.io/npm/v/pkg-scaffold.svg?style=flat&color=CB3837)](https://www.npmjs.com/package/pkg-scaffold)
6
+ [![npm downloads](https://img.shields.io/npm/dm/pkg-scaffold.svg?style=flat&color=34ADFF)](https://www.npmjs.com/package/pkg-scaffold)
7
+ [![License](https://img.shields.io/badge/license-MIT-orange.svg?style=flat)](LICENSE)
8
+ [![GitHub stars](https://img.shields.io/github/stars/DreamLongYT/pkg-scaffold.svg?style=flat&color=gold)](https://github.com/DreamLongYT/pkg-scaffold)
9
+
10
+ `pkg-scaffold` is an advanced, AST-driven tool designed to prune dead code, orphaned files, and unused exports from your JavaScript and TypeScript codebases with unprecedented safety. Unlike traditional linters, `pkg-scaffold` doesn't just report issues—it fixes them within a secure, transactional sandbox.
11
+
12
+ ---
13
+
14
+ ## šŸš€ Key Features
15
+
16
+ - **AST-Based Deep Tracing**: Leverages the official TypeScript compiler to trace dependencies through complex barrel files, re-exports, and default export chains.
17
+ - **Automated Self-Healing**: Automatically runs your project's test suite after refactoring. If a regression is detected (non-zero exit code), it triggers an immediate rollback.
18
+ - **Transactional Safety**: All modifications occur in a Git-isolated sandbox with a backup journaling system. Your original state is always recoverable.
19
+ - **Interactive Optimization Plan**: Generates a detailed "dry-run" plan for user confirmation before any file is touched.
20
+ - **Suppression Engine**: Fine-grained control using `@scaffold-suppress` directives to protect specific files or exports from being pruned.
21
+
22
+ ---
23
+
24
+ ## āš”ļø Competitive Analysis: Why pkg-scaffold?
25
+
26
+ While tools like [Knip](https://knip.dev/) or [Depcheck](https://github.com/depcheck/depcheck) are excellent for reporting, `pkg-scaffold` is built for **automated architectural cleanup**.
27
+
28
+ | Feature | **pkg-scaffold** | Knip.dev | Depcheck | ts-prune |
29
+ | :--- | :---: | :---: | :---: | :---: |
30
+ | **Dead Code Detection** | āœ… AST-Deep | āœ… Comprehensive | āš ļø Regex/Loose | āœ… Basic |
31
+ | **Automated Pruning** | āœ… Native | āš ļø Experimental | āŒ No | āŒ No |
32
+ | **Self-Healing (Auto-Rollback)** | āœ… **Yes** | āŒ No | āŒ No | āŒ No |
33
+ | **Transaction Sandbox** | āœ… **Git + Journal** | āŒ No | āŒ No | āŒ No |
34
+ | **Interactive Approval** | āœ… **Yes** | āŒ No | āŒ No | āŒ No |
35
+ | **Barrel File Tracing** | āœ… High-Fidelity | āœ… Good | āŒ No | āš ļø Limited |
36
+ | **Status** | šŸš€ Active | šŸš€ Active | šŸ› ļø Maintenance | šŸ› ļø Maintenance |
37
+
38
+ > **The Verdict:** `pkg-scaffold` wins when you need a **safe, automated workflow** that guarantees your project stays functional after cleaning up technical debt.
39
+
40
+ ---
41
+
42
+ ## šŸ› ļø Installation & Usage
43
+
44
+ ### Installation
45
+ ```bash
46
+ npm install -g pkg-scaffold
47
+ ```
48
+
49
+ ### Basic Usage (Dry-Run)
50
+ Analyze your project without making any changes:
51
+ ```bash
52
+ pkg-scaffold --cwd ./my-project --no-fix
53
+ ```
54
+
55
+ ### Active Refactoring (Self-Healing)
56
+ Analyze, prune, and validate with automatic rollback on test failure:
57
+ ```bash
58
+ pkg-scaffold --cwd ./my-project --fix --test-command "npm test"
59
+ ```
60
+
61
+ ### Options
62
+ | Flag | Description | Default |
63
+ | :--- | :--- | :--- |
64
+ | `--cwd <path>` | Execution context root directory | `process.cwd()` |
65
+ | `--fix` | Enable atomic code updates and pruning | `true` |
66
+ | `--test-command` | Script to run for self-healing validation | `npm test` |
67
+ | `--yes` | Skip confirmation prompts | `false` |
68
+ | `--verbose` | Toggle expanded debug telemetry | `false` |
69
+
70
+ ---
71
+
72
+ ## šŸ›”ļø Suppression
73
+
74
+ Protect a file or a specific export from being removed:
75
+
76
+ ```javascript
77
+ /**
78
+ * @scaffold-suppress legacyFunction
79
+ */
80
+ export const legacyFunction = () => {
81
+ // This export is safe even if unused
82
+ };
83
+
84
+ /**
85
+ * @scaffold-suppress
86
+ */
87
+ // This entire file is protected from being flagged as orphaned
88
+ ```
89
+
90
+ ---
91
+
92
+ ## šŸ“œ License
93
+
94
+ MIT Ā© DreamLongYT
package/bin/cli.js CHANGED
@@ -36,7 +36,8 @@ async function bootstrap() {
36
36
  .option('--tsconfig <filename>', 'Specify path to custom layout configurations', 'tsconfig.json')
37
37
  .option('--test-command <command>', 'Integrated continuous safety test validation script execution path', 'npm test')
38
38
  .option('--workspace', 'Enable high-density workspace workspace/monorepo cluster mesh evaluation parsing', false)
39
- .option('--verbose', 'Toggle expanded trace telemetry for debug operational diagnostics', false);
39
+ .option('--verbose', 'Toggle expanded trace telemetry for debug operational diagnostics', false)
40
+ .option('-y, --yes', 'Skip confirmation prompts and execute planned structural modifications automatically', false);
40
41
 
41
42
  program.parse(process.argv);
42
43
  const options = program.opts();
@@ -65,13 +66,15 @@ async function bootstrap() {
65
66
  process.exit(1);
66
67
  }
67
68
 
69
+ // Ensure cwd is always an absolute path regardless of how it was passed (e.g., '--cwd .')
68
70
  const engine = new RefactoringEngine({
69
- cwd: options.cwd,
71
+ cwd: path.resolve(options.cwd),
70
72
  autoFix: options.fix,
71
73
  tsconfig: options.tsconfig,
72
74
  testCommand: options.testCommand,
73
75
  workspace: options.workspace,
74
- verbose: options.verbose
76
+ verbose: options.verbose,
77
+ skipConfirm: options.yes
75
78
  });
76
79
 
77
80
  await engine.run();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pkg-scaffold",
3
- "version": "2.4.0",
3
+ "version": "3.0.0",
4
4
  "description": "An advanced, AST-driven dependency resolution, refactoring, and self-healing engine.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -11,6 +11,7 @@ import fs from 'fs/promises';
11
11
 
12
12
  /**
13
13
  * High-Fidelity Graph Element Node representing a single file asset boundary.
14
+ * @scaffold-suppress GraphNode
14
15
  */
15
16
  export class GraphNode {
16
17
  constructor(filePath) {
@@ -105,6 +106,7 @@ export class EngineContext {
105
106
  this.allowAutoFix = options.autoFix ?? true;
106
107
  this.isWorkspaceEnabled = options.workspace ?? false;
107
108
  this.verbose = options.verbose ?? false;
109
+ this.skipConfirm = options.skipConfirm ?? false;
108
110
 
109
111
  // Core Memory Repositories
110
112
  this.graph = new Map(); // Absolute File Path -> GraphNode
@@ -235,7 +237,10 @@ export class EngineContext {
235
237
  const relativePath = path.relative(this.cwd, filePath);
236
238
 
237
239
  // Category A: Completely orphaned components (no references, not library/framework entries)
238
- if (node.incomingEdges.size === 0 && !node.isLibraryEntry && !node.isFrameworkContract) {
240
+ // A file with ANY @scaffold-suppress directive is considered intentionally retained and must
241
+ // never be flagged as orphaned, even if no other file imports it directly.
242
+ const fileHasSuppressDirective = node.localSuppressedRules.size > 0;
243
+ if (node.incomingEdges.size === 0 && !node.isLibraryEntry && !node.isFrameworkContract && !fileHasSuppressDirective) {
239
244
  summary.structuralIssuesDetected.deadFiles.push(relativePath);
240
245
  continue; // An orphaned file implies all internal sub-exports are dead; skip sub-checks
241
246
  }
@@ -32,8 +32,8 @@ export class ASTAnalyzer {
32
32
  this.getScriptKind(filePath)
33
33
  );
34
34
 
35
- this.walkNode(sourceFile, sourceFile, fileNode);
36
35
  this.extractTopLevelJSDocSuppreessions(sourceFile, fileNode);
36
+ this.walkNode(sourceFile, sourceFile, fileNode);
37
37
 
38
38
  return true;
39
39
  } catch (parseError) {
@@ -130,11 +130,19 @@ export class ASTAnalyzer {
130
130
  }
131
131
 
132
132
  // Handle Named Function Node Exports Matrix Configurations
133
+ // If the function carries both ExportKeyword AND DefaultKeyword modifiers, it is an
134
+ // `export default function` declaration. In that case the canonical export name is
135
+ // 'default', not the function's identifier, so that barrel-file tracing (which looks
136
+ // up 'default' when following `export { default as X } from './y'`) resolves correctly.
133
137
  case ts.SyntaxKind.FunctionDeclaration: {
134
- if (node.name && ts.isIdentifier(node.name)) {
135
- const name = node.name.text;
136
- if (this.hasExportModifier(node)) {
137
- fileNode.internalExports.set(name, { type: 'function', start: node.getStart(sourceFile), end: node.getEnd() });
138
+ if (this.hasExportModifier(node)) {
139
+ const isDefaultExport = node.modifiers && node.modifiers.some(m => m.kind === ts.SyntaxKind.DefaultKeyword);
140
+ if (isDefaultExport) {
141
+ // Register under 'default'; also store the real name as referencedSymbol for diagnostics
142
+ const realName = node.name && ts.isIdentifier(node.name) ? node.name.text : 'anonymous';
143
+ fileNode.internalExports.set('default', { type: 'default-function', referencedSymbol: realName, start: node.getStart(sourceFile), end: node.getEnd() });
144
+ } else if (node.name && ts.isIdentifier(node.name)) {
145
+ fileNode.internalExports.set(node.name.text, { type: 'function', start: node.getStart(sourceFile), end: node.getEnd() });
138
146
  }
139
147
  }
140
148
  break;
@@ -142,10 +150,13 @@ export class ASTAnalyzer {
142
150
 
143
151
  // Handle Structural Class Definitions and Class Export Signatures
144
152
  case ts.SyntaxKind.ClassDeclaration: {
145
- if (node.name && ts.isIdentifier(node.name)) {
146
- const name = node.name.text;
147
- if (this.hasExportModifier(node)) {
148
- fileNode.internalExports.set(name, { type: 'class', start: node.getStart(sourceFile), end: node.getEnd() });
153
+ if (this.hasExportModifier(node)) {
154
+ const isDefaultExport = node.modifiers && node.modifiers.some(m => m.kind === ts.SyntaxKind.DefaultKeyword);
155
+ if (isDefaultExport) {
156
+ const realName = node.name && ts.isIdentifier(node.name) ? node.name.text : 'anonymous';
157
+ fileNode.internalExports.set('default', { type: 'default-class', referencedSymbol: realName, start: node.getStart(sourceFile), end: node.getEnd() });
158
+ } else if (node.name && ts.isIdentifier(node.name)) {
159
+ fileNode.internalExports.set(node.name.text, { type: 'class', start: node.getStart(sourceFile), end: node.getEnd() });
149
160
  }
150
161
  }
151
162
  break;
@@ -234,8 +245,8 @@ export class ASTAnalyzer {
234
245
  */
235
246
  auditAssignmentSafety(variableName, initializer, fileNode, sourceFile) {
236
247
  // Process variable mapping target indicators first
237
- if (this.hasExportModifier(variableName?.parent)) {
238
- fileNode.internalExports.set(variableName, { type: 'variable', start: variableName.parent.getStart(sourceFile), end: variableName.parent.getEnd() });
248
+ if (this.hasExportModifier(variableName.parent)) {
249
+ fileNode.internalExports.set(variableName.text, { type: 'variable', start: variableName.parent.getStart(sourceFile), end: variableName.parent.getEnd() });
239
250
  }
240
251
 
241
252
  if (!initializer || !ts.isStringLiteral(initializer)) return;
@@ -274,13 +285,14 @@ export class ASTAnalyzer {
274
285
  */
275
286
  extractTopLevelJSDocSuppreessions(sourceFile, fileNode) {
276
287
  const fullText = sourceFile.text;
277
- const commentRanges = ts.getLeadingCommentRanges(fullText, 0) || [];
278
-
279
- for (const range of commentRanges) {
280
- const comment = fullText.slice(range.pos, range.end);
281
- const matches = comment.match(/@scaffold-suppress\s+([a-zA-Z0-9_\-*:]+)/g);
282
- if (matches) {
283
- matches.forEach(m => {
288
+ // Scan all comments in the file for @scaffold-suppress
289
+ const commentRegex = /\/\*\*?[\s\S]*?\*\/|\/\/.*/g;
290
+ let match;
291
+ while ((match = commentRegex.exec(fullText)) !== null) {
292
+ const comment = match[0];
293
+ const suppressMatches = comment.match(/@scaffold-suppress\s+([a-zA-Z0-9_\-*:]+)/g);
294
+ if (suppressMatches) {
295
+ suppressMatches.forEach(m => {
284
296
  const directive = m.replace('@scaffold-suppress', '').trim();
285
297
  fileNode.localSuppressedRules.add(directive);
286
298
  });
@@ -104,6 +104,18 @@ export class BarrelParser {
104
104
  }
105
105
  break;
106
106
  }
107
+
108
+ // Track default exports declared directly within the file boundary:
109
+ // Handles both `export default function foo()` and `export default expression`.
110
+ // The canonical symbol name stored is 'default' so that forwardedNamedExports
111
+ // entries whose sourceSymbol is 'default' can resolve correctly via Rule A.
112
+ case ts.SyntaxKind.ExportAssignment: {
113
+ // ExportAssignment covers `export default <expr>` (isExportEquals === false)
114
+ // as well as `export = <expr>` (isExportEquals === true, CommonJS style).
115
+ // We register 'default' for both to ensure the barrel tracer can settle here.
116
+ spec.declaredLocalExports.add('default');
117
+ break;
118
+ }
107
119
  }
108
120
 
109
121
  ts.forEachChild(node, child => this.harvestExportSignatures(child, spec));
@@ -117,7 +117,8 @@ export class MagicDetector {
117
117
  const testEnvironments = [
118
118
  'jest.config.', 'vitest.config.', 'playwright.config.', 'cypress.config.',
119
119
  'webpack.config.', 'vite.config.', 'rollup.config.', 'tailwind.config.',
120
- '.eslintrc.', 'prettier.config.', '.postcssrc.', 'postcss.config.'
120
+ '.eslintrc.', 'prettier.config.', '.postcssrc.', 'postcss.config.',
121
+ 'bin/cli.js', 'index.js', 'WorkerTaskRunner.js'
121
122
  ];
122
123
 
123
124
  return testEnvironments.some(matchPattern => normalizedPath.includes(matchPattern));
@@ -29,7 +29,7 @@ export class SelfHealer {
29
29
  await refactorTask();
30
30
 
31
31
  // Step 3: Stage changes inside our Git tracking context
32
- await this.gitSandbox.checkpointChanges();
32
+ await this.gitSandbox.stageAndCheckpointChanges();
33
33
 
34
34
  console.log('🧪 Running testing suites to verify workspace integrity...');
35
35
  const validationPassed = await this.verifyProjectHealthStatus();
package/src/index.js CHANGED
@@ -9,6 +9,7 @@
9
9
  import fs from 'fs/promises';
10
10
  import path from 'path';
11
11
  import ansis from 'ansis';
12
+ import readline from 'readline/promises';
12
13
 
13
14
  // Import local domain architecture sub-systems
14
15
  import { EngineContext } from './EngineContext.js';
@@ -16,17 +17,17 @@ import { ASTAnalyzer } from './ast/ASTAnalyzer.js';
16
17
  import { BarrelParser } from './ast/BarrelParser.js';
17
18
  import { MagicDetector } from './ast/MagicDetector.js';
18
19
  import { PathMapper } from './resolution/PathMapper.js';
19
- import { WorkspaceGraph } from './resolution/WorkspaceGraph.js';
20
- import { DependencyResolver } from './resolution/DependencyResolver.js';
21
- import { TransactionManager } from './refactor/TransactionManager.js';
22
- import { ImpactAnalyzer } from './refactor/ImpactAnalyzer.js';
23
- import { SourceRewriter } from './refactor/SourceRewriter.js';
24
- import { TypeIntegrity } from './refactor/TypeIntegrity.js';
20
+ import { WorkspaceGraph } from './resolution/WorkSpaceGraph.js';
21
+ import { DependencyResolver } from './resolution/DepencyResolver.js';
22
+ import { TransactionManager } from './refractor/TransactionManager.js';
23
+ import { ImpactAnalyzer } from './refractor/ImpactAnalyzer.js';
24
+ import { SourceRewriter } from './refractor/SourceRewriter.js';
25
+ import { TypeIntegrity } from './refractor/TypeIntegrity.js';
25
26
  import { GitSandbox } from './healing/GitSandbox.js';
26
27
  import { SelfHealer } from './healing/SelfHealer.js';
27
28
  import { IncrementalCacheManager } from './performance/GraphCache.js';
28
29
  import { WorkerPool } from './performance/WorkerPool.js';
29
- import { SupplyChainGuard } from './security/SupplyChainGuard.js';
30
+ import { SupplyChainGuard } from './performance/SupplyChainGuard.js';
30
31
 
31
32
  /**
32
33
  * Primary Refactoring Engine Core Coordination Controller
@@ -67,6 +68,11 @@ export class RefactoringEngine {
67
68
  try {
68
69
  console.log(ansis.bold.green('šŸŽÆ Starting pkg-scaffold Operational Optimization Cycle...'));
69
70
 
71
+ const rl = readline.createInterface({
72
+ input: process.stdin,
73
+ output: process.stdout
74
+ });
75
+
70
76
  // Pass 1: Boot environment contexts and load alias configuration maps
71
77
  await this.context.initialize();
72
78
  await this.pathMapper.loadMappings(this.context.tsconfigFilename);
@@ -103,19 +109,23 @@ export class RefactoringEngine {
103
109
  parallelParseCompleted = await this.workerPool.parallelAnalyzeCodebase(sourceCodeFilesList, this);
104
110
  }
105
111
 
106
- // Synchronous fallback loop to parse remaining files or cache misses
112
+ // Synchronous fallback loop to parse remaining files or cache misses.
113
+ // IMPORTANT: The `parallelParseCompleted` flag only controls whether the *worker pool*
114
+ // already processed all files. It must NOT be set per-file inside this loop — doing so
115
+ // would cause all files after the first cache-hit to be silently skipped.
107
116
  for (const filePath of sourceCodeFilesList) {
108
117
  const node = this.context.createNode(filePath);
109
118
  const currentHash = await this.cacheManager.computeHash(filePath);
110
119
  node.contentHash = currentHash;
111
120
 
112
- if (cacheManifest[filePath] && cacheManifest[filePath].hash === currentHash) {
121
+ // Check if this individual file is already up-to-date in the cache
122
+ const isFileCached = cacheManifest[filePath] && cacheManifest[filePath].hash === currentHash;
123
+
124
+ if (isFileCached) {
113
125
  this.context.metrics.cacheHits++;
114
126
  this.hydrateNodeFromCache(node, cacheManifest[filePath]);
115
- parallelParseCompleted = true; // Cached elements don't need worker allocations
116
- }
117
-
118
- if (!parallelParseCompleted) {
127
+ } else if (!parallelParseCompleted) {
128
+ // Only parse if the worker pool did not already handle this file
119
129
  this.context.metrics.cacheMisses++;
120
130
  await this.analyzer.processFile(filePath, node);
121
131
  }
@@ -139,45 +149,68 @@ export class RefactoringEngine {
139
149
  analysisSummary.structuralIssuesDetected.deadExports.length > 0;
140
150
 
141
151
  if (structuralModificationsStaged) {
142
- await this.selfHealer.runSelfHealingLifecycle(async () => {
143
-
144
- // Sub-Task A: Purge completely unreferenced dangling components
145
- for (const relPath of analysisSummary.structuralIssuesDetected.deadFiles) {
146
- const absPath = path.resolve(this.context.cwd, relPath);
147
- console.log(ansis.red(`āœ‚ļø Removing unreferenced file: ${relPath}`));
148
- await this.txManager.stageDeletion(absPath);
149
- }
150
-
151
- // Sub-Task B: Surgically remove unused named export blocks from active files
152
- for (const unusedExport of analysisSummary.structuralIssuesDetected.deadExports) {
153
- const absPath = path.resolve(this.context.cwd, unusedExport.file);
154
- const node = this.context.graph.get(absPath);
155
-
156
- if (!node) continue;
157
- const meta = node.internalExports.get(unusedExport.symbol);
158
-
159
- // Perform safety analysis to ensure the token isn't called via dynamic runtime methods
160
- const safetyVerdict = await this.impactAnalyzer.verifyRefactorSafety(absPath, unusedExport.symbol, this.context.graph);
161
-
162
- if (safetyVerdict.isSafeToPrune) {
163
- console.log(ansis.yellow(`⚔ Stripping unused export [${unusedExport.symbol}] from: ${unusedExport.file}:${unusedExport.line}`));
164
- const currentText = await fs.readFile(absPath, 'utf8');
165
- const nextText = await this.sourceRewriter.stripNamedExportSignature(absPath, unusedExport.symbol, meta);
166
-
167
- await this.txManager.stageWrite(absPath, nextText);
152
+ console.log(ansis.bold.yellow('\nšŸ“‹ Proposed Optimization Plan:'));
153
+ console.log(ansis.dim('------------------------------------------------------------'));
154
+
155
+ if (analysisSummary.structuralIssuesDetected.deadFiles.length > 0) {
156
+ console.log(ansis.bold(` šŸ—‘ļø Delete ${analysisSummary.structuralIssuesDetected.deadFiles.length} orphaned files:`));
157
+ analysisSummary.structuralIssuesDetected.deadFiles.forEach(f => console.log(ansis.dim(` • ${f}`)));
158
+ }
159
+
160
+ if (analysisSummary.structuralIssuesDetected.deadExports.length > 0) {
161
+ console.log(ansis.bold(` āœ‚ļø Prune ${analysisSummary.structuralIssuesDetected.deadExports.length} unused named exports:`));
162
+ analysisSummary.structuralIssuesDetected.deadExports.forEach(e => console.log(ansis.dim(` • ${e.symbol} in ${e.file}:${e.line}`)));
163
+ }
164
+ console.log(ansis.dim('------------------------------------------------------------'));
165
+
166
+ let proceed = this.context.skipConfirm;
167
+ if (!proceed) {
168
+ const answer = await rl.question(ansis.bold.cyan('\nā“ Apply these structural modifications? (y/N): '));
169
+ proceed = answer.toLowerCase() === 'y';
170
+ }
171
+
172
+ if (proceed) {
173
+ await this.selfHealer.runSelfHealingLifecycle(async () => {
174
+ // Sub-Task A: Purge completely unreferenced dangling components
175
+ for (const relPath of analysisSummary.structuralIssuesDetected.deadFiles) {
176
+ const absPath = path.resolve(this.context.cwd, relPath);
177
+ console.log(ansis.red(`āœ‚ļø Removing unreferenced file: ${relPath}`));
178
+ await this.txManager.stageDeletion(absPath);
179
+ }
180
+
181
+ // Sub-Task B: Surgically remove unused named export blocks from active files
182
+ for (const unusedExport of analysisSummary.structuralIssuesDetected.deadExports) {
183
+ const absPath = path.resolve(this.context.cwd, unusedExport.file);
184
+ const node = this.context.graph.get(absPath);
168
185
 
169
- // Align matching type declaration boundaries (.d.ts) to prevent compilation errors
170
- await this.typeIntegrity.synchronizeDeclarationFile(absPath, unusedExport.symbol);
171
- } else if (this.context.verbose) {
172
- console.log(ansis.gray(`šŸ›”ļø Preserving symbol export [${unusedExport.symbol}] due to: ${safetyVerdict.blockReason}`));
186
+ if (!node) continue;
187
+ const meta = node.internalExports.get(unusedExport.symbol);
188
+
189
+ // Perform safety analysis to ensure the token isn't called via dynamic runtime methods
190
+ const safetyVerdict = await this.impactAnalyzer.verifyRefactorSafety(absPath, unusedExport.symbol, this.context.graph);
191
+
192
+ if (safetyVerdict.isSafeToPrune) {
193
+ console.log(ansis.yellow(`⚔ Stripping unused export [${unusedExport.symbol}] from: ${unusedExport.file}:${unusedExport.line}`));
194
+ const nextText = await this.sourceRewriter.stripNamedExportSignature(absPath, unusedExport.symbol, meta);
195
+
196
+ await this.txManager.stageWrite(absPath, nextText);
197
+
198
+ // Align matching type declaration boundaries (.d.ts) to prevent compilation errors
199
+ await this.typeIntegrity.synchronizeDeclarationFile(absPath, unusedExport.symbol);
200
+ } else if (this.context.verbose) {
201
+ console.log(ansis.gray(`šŸ›”ļø Preserving symbol export [${unusedExport.symbol}] due to: ${safetyVerdict.blockReason}`));
202
+ }
173
203
  }
174
- }
175
- });
204
+ });
205
+ } else {
206
+ console.log(ansis.bold.yellow('\nāš ļø Optimization plan aborted by user. No changes applied.'));
207
+ }
176
208
  }
177
209
  }
178
210
 
179
211
  // Pass 7: Save optimized graph footprints back to the cache directory
180
212
  await this.cacheManager.saveCacheManifest(this.context.graph);
213
+ rl.close();
181
214
  console.log(ansis.bold.green('\n✨ Core optimization cycle completed smoothly. Codebase workspace is healthy.'));
182
215
 
183
216
  } catch (criticalFault) {
@@ -269,6 +302,14 @@ export class RefactoringEngine {
269
302
  }
270
303
  node.isLibraryEntry = cachedRecord.isLibraryEntry || false;
271
304
  node.securityThreats = cachedRecord.securityThreats || [];
305
+ if (cachedRecord.localSuppressedRules) {
306
+ cachedRecord.localSuppressedRules.forEach(r => node.localSuppressedRules.add(r));
307
+ }
308
+ if (cachedRecord.symbolSourceLocations) {
309
+ Object.entries(cachedRecord.symbolSourceLocations).forEach(([k, v]) => {
310
+ node.symbolSourceLocations.set(k, v);
311
+ });
312
+ }
272
313
  }
273
314
 
274
315
  /**
@@ -63,7 +63,9 @@ export class IncrementalCacheManager {
63
63
  instantiatedIdentifiers: Array.from(node.instantiatedIdentifiers),
64
64
  propertyAccessChains: Array.from(node.propertyAccessChains),
65
65
  internalExports: Object.fromEntries(node.internalExports),
66
- securityThreats: node.securityThreats || []
66
+ securityThreats: node.securityThreats || [],
67
+ localSuppressedRules: Array.from(node.localSuppressedRules),
68
+ symbolSourceLocations: Object.fromEntries(node.symbolSourceLocations)
67
69
  };
68
70
  }
69
71
 
@@ -1,5 +1,5 @@
1
1
  import { Worker, isMainThread, parentPort, workerData } from 'worker_threads';
2
- import os from 'core-os';
2
+ import os from 'os';
3
3
  import path from 'path';
4
4
 
5
5
  /**
@@ -62,6 +62,9 @@ export class WorkerPool {
62
62
  });
63
63
 
64
64
  node.securityThreats = result.securityThreats || [];
65
+ if (result.localSuppressedRules) {
66
+ result.localSuppressedRules.forEach(r => node.localSuppressedRules.add(r));
67
+ }
65
68
  });
66
69
 
67
70
  return true;
@@ -36,10 +36,11 @@ async function processThreadChunks() {
36
36
  text,
37
37
  ts.ScriptTarget.Latest,
38
38
  true,
39
- file.endsWith('.ts') ? ts.ScriptKind.TS : file.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.JS
39
+ standaloneAnalyzer.getScriptKind(file)
40
40
  );
41
41
 
42
- standaloneAnalyzer.traverseNodeTree(sourceFile, sourceFile, mockNode);
42
+ standaloneAnalyzer.extractTopLevelJSDocSuppreessions(sourceFile, mockNode);
43
+ standaloneAnalyzer.walkNode(sourceFile, sourceFile, mockNode);
43
44
 
44
45
  partialGraphPayloadResults.push({
45
46
  filePath: file,
@@ -50,7 +51,8 @@ async function processThreadChunks() {
50
51
  instantiatedIdentifiers: Array.from(mockNode.instantiatedIdentifiers),
51
52
  propertyAccessChains: Array.from(mockNode.propertyAccessChains),
52
53
  internalExports: Object.fromEntries(mockNode.internalExports),
53
- securityThreats: mockNode.securityThreats
54
+ securityThreats: mockNode.securityThreats,
55
+ localSuppressedRules: Array.from(mockNode.localSuppressedRules)
54
56
  });
55
57
  } catch {
56
58
  // Ignore unparseable or locked syntax nodes in thread loops
@@ -28,6 +28,17 @@ export class DependencyResolver {
28
28
  * @returns {string|null} Resolved absolute file path location on disk, or null if external/third-party node_module
29
29
  */
30
30
  resolveModulePath(containingFile, importSpecifier) {
31
+ // Challenge #16: Ignore built-in Node.js modules (fs, path, etc.)
32
+ if (importSpecifier.startsWith('node:') || [
33
+ 'assert', 'async_hooks', 'buffer', 'child_process', 'cluster', 'console', 'constants',
34
+ 'crypto', 'dgram', 'dns', 'domain', 'events', 'fs', 'fs/promises', 'http', 'http2',
35
+ 'https', 'inspector', 'module', 'net', 'os', 'path', 'perf_hooks', 'process',
36
+ 'punycode', 'querystring', 'readline', 'repl', 'stream', 'string_decoder',
37
+ 'timers', 'tls', 'trace_events', 'tty', 'url', 'util', 'v8', 'vm', 'worker_threads', 'zlib'
38
+ ].includes(importSpecifier)) {
39
+ return null;
40
+ }
41
+
31
42
  const containingDir = path.dirname(containingFile);
32
43
 
33
44
  // Rule A: Intercept and resolve local monorepo workspace cross-links
@@ -1,6 +1,6 @@
1
1
  import fs from 'fs/promises';
2
2
  import path from 'path';
3
- import { glob } from 'fs/promises'; // Native sub-directory crawling
3
+ // Native sub-directory crawling removed as it's not in fs/promises in older node, but we use readdir anyway.
4
4
 
5
5
  /**
6
6
  * Monorepo Cross-Linking Topology Manager