pkg-scaffold 2.3.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/.scaffold-ignore +22 -0
- package/README.md +94 -107
- package/bin/cli.js +94 -0
- package/index.js +0 -0
- package/package.json +18 -6
- package/src/EngineContext.js +287 -0
- package/src/ast/ASTAnalyzer.js +325 -0
- package/src/ast/BarrelParser.js +189 -0
- package/src/ast/MagicDetector.js +155 -0
- package/src/healing/GitSandbox.js +160 -0
- package/src/healing/SelfHealer.js +150 -0
- package/src/index.js +384 -0
- package/src/performance/GraphCache.js +84 -0
- package/src/performance/SupplyChainGuard.js +106 -0
- package/src/performance/WorkerPool.js +92 -0
- package/src/performance/WorkerTaskRunner.js +66 -0
- package/src/refractor/ImpactAnalyzer.js +92 -0
- package/src/refractor/SourceRewriter.js +86 -0
- package/src/refractor/TransactionManager.js +131 -0
- package/src/refractor/TypeIntegrity.js +75 -0
- package/src/resolution/DepencyResolver.js +131 -0
- package/src/resolution/PathMapper.js +115 -0
- package/src/resolution/WorkSpaceGraph.js +171 -0
- package/tsconfig.json +26 -0
package/.scaffold-ignore
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# ⚙️ pkg-scaffold Rule Suppression & Intent Profiles
|
|
3
|
+
# ============================================================================
|
|
4
|
+
# Define exact symbols, file patterns, or dependency keys that must remain
|
|
5
|
+
# preserved during dead-code scanning and transactional refactoring pruning.
|
|
6
|
+
|
|
7
|
+
# Framework & Meta Entrypoints (Implicitly Alive)
|
|
8
|
+
pages/api/*
|
|
9
|
+
app/routes/*
|
|
10
|
+
src/entry-point.js
|
|
11
|
+
|
|
12
|
+
# Intent Suppression: Library Code / Consumer Consumption Contracts
|
|
13
|
+
export:publicApiMethod
|
|
14
|
+
export:initializePlugin
|
|
15
|
+
export:onConfigLoaded
|
|
16
|
+
|
|
17
|
+
# Globally Ignored Module Specifiers
|
|
18
|
+
@types/node
|
|
19
|
+
tslib
|
|
20
|
+
|
|
21
|
+
# Secret Heuristic Exemptions (Explicit False Positive Control)
|
|
22
|
+
exempt_token_pattern
|
package/README.md
CHANGED
|
@@ -1,107 +1,94 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
**
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
[**
|
|
1
|
+
# 📦 pkg-scaffold v3.0.0
|
|
2
|
+
|
|
3
|
+
**Enterprise-Grade AST Syntax Refactoring & Self-Healing Engine**
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/pkg-scaffold)
|
|
6
|
+
[](https://www.npmjs.com/package/pkg-scaffold)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](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
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ============================================================================
|
|
5
|
+
* 🏁 pkg-scaffold CLI Entry Point
|
|
6
|
+
* ============================================================================
|
|
7
|
+
* Handles option compilation, environment orchestration, option validation,
|
|
8
|
+
* and initiates the primary operational pipeline loop.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { Command } from 'commander';
|
|
12
|
+
import ansis from 'ansis';
|
|
13
|
+
import path from 'path';
|
|
14
|
+
import fs from 'fs/promises';
|
|
15
|
+
import { fileURLToPath } from 'url';
|
|
16
|
+
|
|
17
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
18
|
+
const __dirname = path.dirname(__filename);
|
|
19
|
+
|
|
20
|
+
const program = new Command();
|
|
21
|
+
|
|
22
|
+
async function bootstrap() {
|
|
23
|
+
try {
|
|
24
|
+
const packageJsonPath = path.resolve(__dirname, '../package.json');
|
|
25
|
+
const packageJsonContent = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
|
|
26
|
+
|
|
27
|
+
program
|
|
28
|
+
.name('pkg-scaffold')
|
|
29
|
+
.description(ansis.cyan('Enterprise-Grade AST Syntax Refactoring & Self-Healing Engine'))
|
|
30
|
+
.version(packageJsonContent.version || '3.0.0');
|
|
31
|
+
|
|
32
|
+
program
|
|
33
|
+
.option('-c, --cwd <path>', 'Specify the execution context root directory', process.cwd())
|
|
34
|
+
.option('--fix', 'Enable atomic code updates, structural file pruning, and active type sanitization', true)
|
|
35
|
+
.option('--no-fix', 'Disable direct file manipulation modifications (dry-run reporting mode)')
|
|
36
|
+
.option('--tsconfig <filename>', 'Specify path to custom layout configurations', 'tsconfig.json')
|
|
37
|
+
.option('--test-command <command>', 'Integrated continuous safety test validation script execution path', 'npm test')
|
|
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)
|
|
40
|
+
.option('-y, --yes', 'Skip confirmation prompts and execute planned structural modifications automatically', false);
|
|
41
|
+
|
|
42
|
+
program.parse(process.argv);
|
|
43
|
+
const options = program.opts();
|
|
44
|
+
|
|
45
|
+
console.log(ansis.bold.green(`\n📦 pkg-scaffold v${packageJsonContent.version || '3.0.0'} Engine Activation`));
|
|
46
|
+
console.log(ansis.dim('------------------------------------------------------------'));
|
|
47
|
+
console.log(`${ansis.bold('Target Workspace Root :')} ${ansis.blue(path.resolve(options.cwd))}`);
|
|
48
|
+
console.log(`${ansis.bold('Refactoring Mode :')} ${options.fix ? ansis.yellow('Active Fixing & Self-Healing Enabled') : ansis.gray('Dry-Run Reporting Only')}`);
|
|
49
|
+
console.log(`${ansis.bold('Validation Sandbox :')} ${ansis.magenta(options.testCommand)}`);
|
|
50
|
+
console.log(ansis.dim('------------------------------------------------------------\n'));
|
|
51
|
+
|
|
52
|
+
const engineModulePath = path.resolve(__dirname, '../src/EngineContext.js');
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
await fs.access(engineModulePath);
|
|
56
|
+
} catch {
|
|
57
|
+
console.error(ansis.red(`🚨 Execution Fault: Core engine architecture files missing. Ensure src/ directory layout is fully generated.`));
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Lazy load execution context wrapper to align with domain initialization
|
|
62
|
+
const { RefactoringEngine } = await import('../src/index.js');
|
|
63
|
+
|
|
64
|
+
if (!RefactoringEngine) {
|
|
65
|
+
console.error(ansis.red('🚨 Architecture Boundary Error: RefactoringEngine could not be resolved from code topology channels.'));
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Ensure cwd is always an absolute path regardless of how it was passed (e.g., '--cwd .')
|
|
70
|
+
const engine = new RefactoringEngine({
|
|
71
|
+
cwd: path.resolve(options.cwd),
|
|
72
|
+
autoFix: options.fix,
|
|
73
|
+
tsconfig: options.tsconfig,
|
|
74
|
+
testCommand: options.testCommand,
|
|
75
|
+
workspace: options.workspace,
|
|
76
|
+
verbose: options.verbose,
|
|
77
|
+
skipConfirm: options.yes
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
await engine.run();
|
|
81
|
+
|
|
82
|
+
console.log(ansis.bold.green('\n✨ Core cycle execution completed successfully. Structural layout is clean.'));
|
|
83
|
+
process.exit(0);
|
|
84
|
+
|
|
85
|
+
} catch (criticalBootError) {
|
|
86
|
+
console.error(ansis.bold.red(`\n🚨 Critical Lifecycle Boot Instability: ${criticalBootError.message}`));
|
|
87
|
+
if (criticalBootError.stack) {
|
|
88
|
+
console.error(ansis.dim(criticalBootError.stack));
|
|
89
|
+
}
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
bootstrap();
|
package/index.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pkg-scaffold",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "An advanced, AST-driven dependency resolution, refactoring, and self-healing engine.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"bin": {
|
|
8
|
-
"pkg-scaffold": "./
|
|
8
|
+
"pkg-scaffold": "./bin/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node bin/cli.js",
|
|
12
|
+
"test": "echo \"Error: no test specified\" && exit 0",
|
|
13
|
+
"test:stability": "npm run test"
|
|
9
14
|
},
|
|
10
15
|
"keywords": [
|
|
11
16
|
"scaffold",
|
|
@@ -35,8 +40,15 @@
|
|
|
35
40
|
},
|
|
36
41
|
"homepage": "https://github.com/DreamLongYT/pkg-scaffold#readme",
|
|
37
42
|
"dependencies": {
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
43
|
+
"ansis": "^3.0.0",
|
|
44
|
+
"commander": "^12.0.0",
|
|
45
|
+
"enhanced-resolve": "^5.16.0",
|
|
46
|
+
"execa": "^8.0.1",
|
|
47
|
+
"ramda": "^0.29.1",
|
|
48
|
+
"typescript": "^5.4.5",
|
|
49
|
+
"yocto-spinner": "^0.1.0"
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=18.0.0"
|
|
41
53
|
}
|
|
42
54
|
}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================================
|
|
3
|
+
* 📦 pkg-scaffold v3.0.0: Enterprise In-Memory Codebase State Manifest
|
|
4
|
+
* ============================================================================
|
|
5
|
+
* Implements a high-density, centralized graph database context for tracking
|
|
6
|
+
* software engineering debt, dependencies, types, and vulnerabilities.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import fs from 'fs/promises';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* High-Fidelity Graph Element Node representing a single file asset boundary.
|
|
14
|
+
* @scaffold-suppress GraphNode
|
|
15
|
+
*/
|
|
16
|
+
export class GraphNode {
|
|
17
|
+
constructor(filePath) {
|
|
18
|
+
this.filePath = path.normalize(filePath);
|
|
19
|
+
this.contentHash = '';
|
|
20
|
+
this.isLibraryEntry = false;
|
|
21
|
+
this.isFrameworkContract = false;
|
|
22
|
+
this.scriptKind = 0; // Ambient enum mapping
|
|
23
|
+
|
|
24
|
+
// Explicit and Computed Dynamic Syntax Boundaries
|
|
25
|
+
this.explicitImports = new Set();
|
|
26
|
+
this.dynamicImports = new Set();
|
|
27
|
+
this.importedSymbols = new Set(); // Format: 'specifier:symbol' or 'specifier:*'
|
|
28
|
+
|
|
29
|
+
// Internal API Exposed Interfaces (Symbol Name -> ExportMetadata)
|
|
30
|
+
this.internalExports = new Map();
|
|
31
|
+
this.typeOnlyExports = new Set();
|
|
32
|
+
|
|
33
|
+
// Semantic Reference Verification Registries
|
|
34
|
+
this.instantiatedIdentifiers = new Set();
|
|
35
|
+
this.rawStringReferences = new Set();
|
|
36
|
+
this.propertyAccessChains = new Set();
|
|
37
|
+
|
|
38
|
+
// Dependency Mesh Connection Maps
|
|
39
|
+
this.incomingEdges = new Set(); // Set of absolute filePaths depending on this component
|
|
40
|
+
this.outgoingEdges = new Set(); // Set of absolute internal filePaths this component calls
|
|
41
|
+
|
|
42
|
+
// Security & Compliance Anomaly Matrices
|
|
43
|
+
this.securityThreats = [];
|
|
44
|
+
this.calculatedDynamicImports = [];
|
|
45
|
+
this.localSuppressedRules = new Set();
|
|
46
|
+
|
|
47
|
+
// Detailed AST Location Diagnostics (Symbol -> Structural Location Mapping)
|
|
48
|
+
this.symbolSourceLocations = new Map(); // Symbol -> { line: number, column: number, length: number }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Evaluates if a specific exposed symbol token is utilized by any incoming edges.
|
|
53
|
+
* Leverages precise syntax identity collections.
|
|
54
|
+
*/
|
|
55
|
+
isSymbolReferencedExternally(symbolName, projectGraph) {
|
|
56
|
+
if (this.isLibraryEntry) return true;
|
|
57
|
+
|
|
58
|
+
for (const parentPath of this.incomingEdges) {
|
|
59
|
+
const parentNode = projectGraph.get(parentPath);
|
|
60
|
+
if (!parentNode) continue;
|
|
61
|
+
|
|
62
|
+
// Direct identity reference check
|
|
63
|
+
if (parentNode.instantiatedIdentifiers.has(symbolName)) return true;
|
|
64
|
+
|
|
65
|
+
// Property lookup reference checks (e.g., config.databaseUrl)
|
|
66
|
+
for (const accessChain of parentNode.propertyAccessChains) {
|
|
67
|
+
if (accessChain.endsWith(`.${symbolName}`) || accessChain.includes(`.${symbolName}.`)) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Safe fallback lookup inside string reference caches (e.g., obj['databaseUrl'])
|
|
73
|
+
if (parentNode.rawStringReferences.has(symbolName)) return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Compiles complete localized diagnostic telemetry tracking metrics for this node instance.
|
|
81
|
+
*/
|
|
82
|
+
compileNodeTelemetry() {
|
|
83
|
+
return {
|
|
84
|
+
path: this.filePath,
|
|
85
|
+
totalExplicitImportsCount: this.explicitImports.size,
|
|
86
|
+
totalExposedExportsCount: this.internalExports.size,
|
|
87
|
+
incomingDependenciesCount: this.incomingEdges.size,
|
|
88
|
+
outgoingDependenciesCount: this.outgoingEdges.size,
|
|
89
|
+
isDanglingOrphan: this.incomingEdges.size === 0 && !this.isLibraryEntry,
|
|
90
|
+
trackedThreatsCount: this.securityThreats.length
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Enterprise Engine Run State Registry & Suppression Context Matrix
|
|
97
|
+
*/
|
|
98
|
+
export class EngineContext {
|
|
99
|
+
constructor(options = {}) {
|
|
100
|
+
this.cwd = path.normalize(options.cwd || process.cwd());
|
|
101
|
+
this.cacheDir = path.join(this.cwd, '.scaffold-cache');
|
|
102
|
+
this.ignoreFilePath = path.join(this.cwd, '.scaffold-ignore');
|
|
103
|
+
this.tsconfigFilename = options.tsconfig || 'tsconfig.json';
|
|
104
|
+
this.testCommand = options.testCommand || 'npm test';
|
|
105
|
+
|
|
106
|
+
this.allowAutoFix = options.autoFix ?? true;
|
|
107
|
+
this.isWorkspaceEnabled = options.workspace ?? false;
|
|
108
|
+
this.verbose = options.verbose ?? false;
|
|
109
|
+
this.skipConfirm = options.skipConfirm ?? false;
|
|
110
|
+
|
|
111
|
+
// Core Memory Repositories
|
|
112
|
+
this.graph = new Map(); // Absolute File Path -> GraphNode
|
|
113
|
+
this.registryHashes = new Map(); // Package Name -> Secure Lockfile Signature String
|
|
114
|
+
this.globallyIgnoredSymbols = new Set();
|
|
115
|
+
this.globallyIgnoredPaths = [];
|
|
116
|
+
this.monorepoPackageRoots = new Set();
|
|
117
|
+
|
|
118
|
+
// Structural Heuristic Verification Metrics Tracker
|
|
119
|
+
this.metrics = {
|
|
120
|
+
startTime: 0,
|
|
121
|
+
endTime: 0,
|
|
122
|
+
totalFilesScanned: 0,
|
|
123
|
+
cacheHits: 0,
|
|
124
|
+
cacheMisses: 0,
|
|
125
|
+
prunedFilesCount: 0,
|
|
126
|
+
prunedExportsCount: 0,
|
|
127
|
+
totalSymbolsAnalyzed: 0,
|
|
128
|
+
securityVulnerabilitiesMitigated: 0
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Initializes baseline context options, directory footprints, and suppression maps.
|
|
134
|
+
*/
|
|
135
|
+
async initialize() {
|
|
136
|
+
this.metrics.startTime = Date.now();
|
|
137
|
+
await fs.mkdir(this.cacheDir, { recursive: true });
|
|
138
|
+
await this.compileIgnoreConfigurations();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Parses .scaffold-ignore layers using precise token segment matching
|
|
143
|
+
* instead of high-risk loose regex blocks.
|
|
144
|
+
*/
|
|
145
|
+
async compileIgnoreConfigurations() {
|
|
146
|
+
try {
|
|
147
|
+
const content = await fs.readFile(this.ignoreFilePath, 'utf8');
|
|
148
|
+
const lines = content.split('\n');
|
|
149
|
+
|
|
150
|
+
for (let line of lines) {
|
|
151
|
+
line = line.trim();
|
|
152
|
+
if (!line || line.startsWith('#')) continue;
|
|
153
|
+
|
|
154
|
+
if (line.startsWith('export:')) {
|
|
155
|
+
const symbolToken = line.replace('export:', '').trim();
|
|
156
|
+
this.globallyIgnoredSymbols.add(symbolToken);
|
|
157
|
+
} else if (line.startsWith('path:')) {
|
|
158
|
+
const pathToken = line.replace('path:', '').trim();
|
|
159
|
+
this.globallyIgnoredPaths.push(path.normalize(pathToken));
|
|
160
|
+
} else {
|
|
161
|
+
// Standard structural path rule fallback
|
|
162
|
+
this.globallyIgnoredPaths.push(path.normalize(line));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
} catch {
|
|
166
|
+
// Configuration optionally omitted; proceed with default execution flags
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Allocates or resolves a unified GraphNode reference inside our memory map index.
|
|
172
|
+
*/
|
|
173
|
+
createNode(absoluteFilePath) {
|
|
174
|
+
const normalizedPath = path.normalize(absoluteFilePath);
|
|
175
|
+
if (this.graph.has(normalizedPath)) {
|
|
176
|
+
return this.graph.get(normalizedPath);
|
|
177
|
+
}
|
|
178
|
+
const node = new GraphNode(normalizedPath);
|
|
179
|
+
this.graph.set(normalizedPath, node);
|
|
180
|
+
return node;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Checks if an absolute file token path matches configuration ignore directives.
|
|
185
|
+
* Evaluates sub-path sequences exactly to prevent regular expression parsing drops.
|
|
186
|
+
*/
|
|
187
|
+
isPathIgnored(absoluteFilePath) {
|
|
188
|
+
const relativeText = path.relative(this.cwd, absoluteFilePath);
|
|
189
|
+
|
|
190
|
+
for (const ignoredTarget of this.globallyIgnoredPaths) {
|
|
191
|
+
if (relativeText === ignoredTarget || relativeText.startsWith(path.join(ignoredTarget, path.sep))) {
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
// Handle explicit wildcard terminal indicators
|
|
195
|
+
if (ignoredTarget.endsWith('*')) {
|
|
196
|
+
const baseSegment = ignoredTarget.slice(0, -1);
|
|
197
|
+
if (relativeText.startsWith(baseSegment)) return true;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Processes the entire active dependency map to compile structural issue indices.
|
|
205
|
+
* Evaluates orphaned components, dead exports, and supply-chain threats.
|
|
206
|
+
*/
|
|
207
|
+
generateSummaryReport() {
|
|
208
|
+
this.metrics.endTime = Date.now();
|
|
209
|
+
const durationSeconds = ((this.metrics.endTime - this.metrics.startTime) / 1000).toFixed(2);
|
|
210
|
+
|
|
211
|
+
const summary = {
|
|
212
|
+
executionDuration: `${durationSeconds}s`,
|
|
213
|
+
totalFilesProcessed: this.metrics.totalFilesScanned,
|
|
214
|
+
graphCacheOptimization: {
|
|
215
|
+
hits: this.metrics.cacheHits,
|
|
216
|
+
misses: this.metrics.cacheMisses,
|
|
217
|
+
ratio: this.metrics.totalFilesScanned > 0
|
|
218
|
+
? `${((this.metrics.cacheHits / this.metrics.totalFilesScanned) * 100).toFixed(1)}%`
|
|
219
|
+
: '0%'
|
|
220
|
+
},
|
|
221
|
+
structuralIssuesDetected: {
|
|
222
|
+
deadFiles: [],
|
|
223
|
+
deadExports: [],
|
|
224
|
+
securityThreats: []
|
|
225
|
+
},
|
|
226
|
+
modificationsExecuted: {
|
|
227
|
+
filesUnlinked: this.metrics.prunedFilesCount,
|
|
228
|
+
exportsStripped: this.metrics.prunedExportsCount
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
for (const [filePath, node] of this.graph.entries()) {
|
|
233
|
+
// Skip package control files from standard structural dead-code checks
|
|
234
|
+
if (filePath.endsWith('package.json')) continue;
|
|
235
|
+
if (this.isPathIgnored(filePath)) continue;
|
|
236
|
+
|
|
237
|
+
const relativePath = path.relative(this.cwd, filePath);
|
|
238
|
+
|
|
239
|
+
// Category A: Completely orphaned components (no references, not library/framework entries)
|
|
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) {
|
|
244
|
+
summary.structuralIssuesDetected.deadFiles.push(relativePath);
|
|
245
|
+
continue; // An orphaned file implies all internal sub-exports are dead; skip sub-checks
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Category B: Dead Named Exports inside active files
|
|
249
|
+
for (const [exportName, meta] of node.internalExports.entries()) {
|
|
250
|
+
this.metrics.totalSymbolsAnalyzed++;
|
|
251
|
+
|
|
252
|
+
// Skip entry configurations, global suppresses, and type-suppressed symbols
|
|
253
|
+
if (exportName === 'default' ||
|
|
254
|
+
this.globallyIgnoredSymbols.has(exportName) ||
|
|
255
|
+
node.localSuppressedRules.has(exportName)) {
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (!node.isSymbolReferencedExternally(exportName, this.graph)) {
|
|
260
|
+
const diagnosticLocation = node.symbolSourceLocations.get(exportName) || { line: 1, column: 1 };
|
|
261
|
+
summary.structuralIssuesDetected.deadExports.push({
|
|
262
|
+
file: relativePath,
|
|
263
|
+
symbol: exportName,
|
|
264
|
+
type: meta.type || 'named',
|
|
265
|
+
line: diagnosticLocation.line,
|
|
266
|
+
column: diagnosticLocation.column
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Category C: High-Entropy Password / Key Hardcode Vulnerabilities
|
|
272
|
+
if (node.securityThreats && node.securityThreats.length > 0) {
|
|
273
|
+
node.securityThreats.forEach(threat => {
|
|
274
|
+
summary.structuralIssuesDetected.securityThreats.push({
|
|
275
|
+
file: relativePath,
|
|
276
|
+
identifier: threat.variableKey,
|
|
277
|
+
riskCode: threat.riskCode || 'HIGH_RISK_SECRET_LEAK',
|
|
278
|
+
entropy: threat.entropyValue,
|
|
279
|
+
line: threat.line || 1
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return summary;
|
|
286
|
+
}
|
|
287
|
+
}
|