pkg-scaffold 2.2.0 → 2.4.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/bin/cli.js +91 -0
- package/index.js +2254 -2544
- package/package.json +19 -7
- package/src/EngineContext.js +282 -0
- package/src/ast/ASTAnalyzer.js +313 -0
- package/src/ast/BarrelParser.js +177 -0
- package/src/ast/MagicDetector.js +154 -0
- package/src/healing/GitSandbox.js +160 -0
- package/src/healing/SelfHealer.js +150 -0
- package/src/index.js +343 -0
- package/src/performance/GraphCache.js +82 -0
- package/src/performance/SupplyChainGuard.js +106 -0
- package/src/performance/WorkerPool.js +89 -0
- package/src/performance/WorkerTaskRunner.js +64 -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 +120 -0
- package/src/resolution/PathMapper.js +115 -0
- package/src/resolution/WorkSpaceGraph.js +171 -0
- package/tsconfig.json +26 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================================
|
|
3
|
+
* 📦 pkg-scaffold v3.0.0: Unified Architectural Refactoring Orchestrator
|
|
4
|
+
* ============================================================================
|
|
5
|
+
* Main execution bridge managing multi-pass compilation cycles, semantic cross-linking,
|
|
6
|
+
* supply-chain validation audits, and automated git self-healing rollbacks.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import fs from 'fs/promises';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import ansis from 'ansis';
|
|
12
|
+
|
|
13
|
+
// Import local domain architecture sub-systems
|
|
14
|
+
import { EngineContext } from './EngineContext.js';
|
|
15
|
+
import { ASTAnalyzer } from './ast/ASTAnalyzer.js';
|
|
16
|
+
import { BarrelParser } from './ast/BarrelParser.js';
|
|
17
|
+
import { MagicDetector } from './ast/MagicDetector.js';
|
|
18
|
+
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';
|
|
25
|
+
import { GitSandbox } from './healing/GitSandbox.js';
|
|
26
|
+
import { SelfHealer } from './healing/SelfHealer.js';
|
|
27
|
+
import { IncrementalCacheManager } from './performance/GraphCache.js';
|
|
28
|
+
import { WorkerPool } from './performance/WorkerPool.js';
|
|
29
|
+
import { SupplyChainGuard } from './security/SupplyChainGuard.js';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Primary Refactoring Engine Core Coordination Controller
|
|
33
|
+
*/
|
|
34
|
+
export class RefactoringEngine {
|
|
35
|
+
constructor(options = {}) {
|
|
36
|
+
// Stage 1: Instantiate State Registers and Global Variables context
|
|
37
|
+
this.context = new EngineContext(options);
|
|
38
|
+
|
|
39
|
+
// Stage 2: Initialize File Mappers and Multi-Package Graph Networks
|
|
40
|
+
this.pathMapper = new PathMapper(this.context);
|
|
41
|
+
this.workspaceGraph = new WorkspaceGraph(this.context);
|
|
42
|
+
this.resolver = new DependencyResolver(this.context, this.pathMapper, this.workspaceGraph);
|
|
43
|
+
|
|
44
|
+
// Stage 3: Wire official AST Syntax parsers and framework processors
|
|
45
|
+
this.analyzer = new ASTAnalyzer(this.context);
|
|
46
|
+
this.barrelParser = new BarrelParser(this.context, this.resolver);
|
|
47
|
+
this.magicDetector = new MagicDetector(this.context);
|
|
48
|
+
|
|
49
|
+
// Stage 4: Connect Transaction managers and surgical code generation scripts
|
|
50
|
+
this.txManager = new TransactionManager(this.context);
|
|
51
|
+
this.impactAnalyzer = new ImpactAnalyzer(this.context);
|
|
52
|
+
this.sourceRewriter = new SourceRewriter(this.context);
|
|
53
|
+
this.typeIntegrity = new TypeIntegrity(this.context);
|
|
54
|
+
|
|
55
|
+
// Stage 5: Bind security audit utilities and performance cache rings
|
|
56
|
+
this.supplyChainGuard = new SupplyChainGuard(this.context);
|
|
57
|
+
this.cacheManager = new IncrementalCacheManager(this.context);
|
|
58
|
+
this.workerPool = new WorkerPool(this.context);
|
|
59
|
+
this.gitSandbox = new GitSandbox(this.context);
|
|
60
|
+
this.selfHealer = new SelfHealer(this.context, this.txManager, this.gitSandbox);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Main Operational Loop executing multi-stage analysis passes across files.
|
|
65
|
+
*/
|
|
66
|
+
async run() {
|
|
67
|
+
try {
|
|
68
|
+
console.log(ansis.bold.green('🎯 Starting pkg-scaffold Operational Optimization Cycle...'));
|
|
69
|
+
|
|
70
|
+
// Pass 1: Boot environment contexts and load alias configuration maps
|
|
71
|
+
await this.context.initialize();
|
|
72
|
+
await this.pathMapper.loadMappings(this.context.tsconfigFilename);
|
|
73
|
+
|
|
74
|
+
if (this.context.isWorkspaceEnabled) {
|
|
75
|
+
console.log(ansis.dim('🌐 Mapping local monorepo workspaces and package mesh layers...'));
|
|
76
|
+
await this.workspaceGraph.initializeWorkspaceMesh();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Load asset fingerprints from disk cache to maximize cold-start performance
|
|
80
|
+
const cacheManifest = await this.cacheManager.loadCacheManifest();
|
|
81
|
+
|
|
82
|
+
// Pass 2: Recursively crawl directories to compile target codebase files list
|
|
83
|
+
const fileList = [];
|
|
84
|
+
await this.discoverSourceFiles(this.context.cwd, fileList);
|
|
85
|
+
this.context.metrics.totalFilesScanned = fileList.length;
|
|
86
|
+
|
|
87
|
+
// Identify meta-framework setups (Next.js, Remix, Nuxt, etc.)
|
|
88
|
+
const activeFrameworkEcosystems = await this.magicDetector.identifyActiveProjectEcosystems(this.context.cwd);
|
|
89
|
+
|
|
90
|
+
// Separate explicit configuration packages out for targeted supply chain security checks
|
|
91
|
+
const sourceCodeFilesList = [];
|
|
92
|
+
for (const file of fileList) {
|
|
93
|
+
if (file.endsWith('package.json')) {
|
|
94
|
+
await this.auditManifestSupplyChain(file);
|
|
95
|
+
} else {
|
|
96
|
+
sourceCodeFilesList.push(file);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Pass 3: Process source file tokens using high-performance concurrent workers
|
|
101
|
+
let parallelParseCompleted = false;
|
|
102
|
+
if (sourceCodeFilesList.length > 10) {
|
|
103
|
+
parallelParseCompleted = await this.workerPool.parallelAnalyzeCodebase(sourceCodeFilesList, this);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Synchronous fallback loop to parse remaining files or cache misses
|
|
107
|
+
for (const filePath of sourceCodeFilesList) {
|
|
108
|
+
const node = this.context.createNode(filePath);
|
|
109
|
+
const currentHash = await this.cacheManager.computeHash(filePath);
|
|
110
|
+
node.contentHash = currentHash;
|
|
111
|
+
|
|
112
|
+
if (cacheManifest[filePath] && cacheManifest[filePath].hash === currentHash) {
|
|
113
|
+
this.context.metrics.cacheHits++;
|
|
114
|
+
this.hydrateNodeFromCache(node, cacheManifest[filePath]);
|
|
115
|
+
parallelParseCompleted = true; // Cached elements don't need worker allocations
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!parallelParseCompleted) {
|
|
119
|
+
this.context.metrics.cacheMisses++;
|
|
120
|
+
await this.analyzer.processFile(filePath, node);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Apply ecosystem overrides directly to our parsed memory maps
|
|
124
|
+
this.magicDetector.injectVirtualConsumerEdges(filePath, node, activeFrameworkEcosystems);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Pass 4: Evaluate graph edges and link connections across the codebase mesh
|
|
128
|
+
console.log(ansis.dim('🔗 Linking graph edges and checking structural usage paths...'));
|
|
129
|
+
await this.linkDependencyGraph();
|
|
130
|
+
|
|
131
|
+
// Pass 5: Compile metrics summary and print diagnostics report
|
|
132
|
+
const analysisSummary = this.context.generateSummaryReport();
|
|
133
|
+
this.displayConsoleDiagnostics(analysisSummary);
|
|
134
|
+
|
|
135
|
+
// Pass 6: Run self-healing automated transformations if --fix is set
|
|
136
|
+
if (this.context.allowAutoFix) {
|
|
137
|
+
const structuralModificationsStaged =
|
|
138
|
+
analysisSummary.structuralIssuesDetected.deadFiles.length > 0 ||
|
|
139
|
+
analysisSummary.structuralIssuesDetected.deadExports.length > 0;
|
|
140
|
+
|
|
141
|
+
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);
|
|
168
|
+
|
|
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}`));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Pass 7: Save optimized graph footprints back to the cache directory
|
|
180
|
+
await this.cacheManager.saveCacheManifest(this.context.graph);
|
|
181
|
+
console.log(ansis.bold.green('\n✨ Core optimization cycle completed smoothly. Codebase workspace is healthy.'));
|
|
182
|
+
|
|
183
|
+
} catch (criticalFault) {
|
|
184
|
+
console.error(ansis.bold.red(`\n🚨 Critical Operational Pipeline Failure: ${criticalFault.message}`));
|
|
185
|
+
if (criticalFault.stack) console.error(ansis.dim(criticalFault.stack));
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Links nodes and processes barrel files recursively without regular expressions.
|
|
192
|
+
*/
|
|
193
|
+
async linkDependencyGraph() {
|
|
194
|
+
for (const [filePath, node] of this.context.graph.entries()) {
|
|
195
|
+
|
|
196
|
+
// Connect standard module imports
|
|
197
|
+
for (const specifier of node.explicitImports) {
|
|
198
|
+
const resolvedPath = this.resolver.resolveModulePath(filePath, specifier);
|
|
199
|
+
if (resolvedPath && this.context.graph.has(resolvedPath)) {
|
|
200
|
+
this.context.graph.get(resolvedPath).incomingEdges.add(filePath);
|
|
201
|
+
node.outgoingEdges.add(resolvedPath);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Unroll re-exports and unwrap barrel file links recursively
|
|
206
|
+
for (const specToken of node.importedSymbols) {
|
|
207
|
+
const delimiterIndex = specToken.indexOf(':');
|
|
208
|
+
if (delimiterIndex === -1) continue;
|
|
209
|
+
|
|
210
|
+
const specifier = specToken.slice(0, delimiterIndex);
|
|
211
|
+
const symbol = specToken.slice(delimiterIndex + 1);
|
|
212
|
+
|
|
213
|
+
const resolvedPath = this.resolver.resolveModulePath(filePath, specifier);
|
|
214
|
+
|
|
215
|
+
if (resolvedPath && symbol !== '*') {
|
|
216
|
+
const traceResolution = await this.barrelParser.determineSymbolDeclarationOrigin(
|
|
217
|
+
resolvedPath,
|
|
218
|
+
symbol,
|
|
219
|
+
this.context.graph
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
if (traceResolution && this.context.graph.has(traceResolution.originFile)) {
|
|
223
|
+
this.context.graph.get(traceResolution.originFile).incomingEdges.add(filePath);
|
|
224
|
+
node.outgoingEdges.add(traceResolution.originFile);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Audits package json files using token equality checks instead of fragile regex searches.
|
|
233
|
+
*/
|
|
234
|
+
async auditManifestSupplyChain(packageJsonPath) {
|
|
235
|
+
try {
|
|
236
|
+
const text = await fs.readFile(packageJsonPath, 'utf8');
|
|
237
|
+
const data = JSON.parse(text);
|
|
238
|
+
|
|
239
|
+
const prodDeps = Object.keys(data.dependencies || {});
|
|
240
|
+
const devDeps = Object.keys(data.devDependencies || {});
|
|
241
|
+
const totalDependencies = [...prodDeps, ...devDeps];
|
|
242
|
+
|
|
243
|
+
// Pass dependencies down to our Levenshtein string-distance guard to find typosquat targets
|
|
244
|
+
const supplyChainThreats = this.supplyChainGuard.detectTyposquattingAnomalies(totalDependencies);
|
|
245
|
+
|
|
246
|
+
for (const anomaly of supplyChainThreats) {
|
|
247
|
+
console.warn(ansis.bold.red(`🚨 Supply Chain Alert: Malicious package masking candidate discovered [${anomaly.maliciousCandidate}]. Mimics trusted library [${anomaly.targetMimicked}].`));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Verify lockfile hash configurations to catch poisoned manifests
|
|
251
|
+
await this.supplyChainGuard.verifyIntegrityLockfileHashes(packageJsonPath);
|
|
252
|
+
} catch {
|
|
253
|
+
// Manifest unreadable or locked; skip gracefully
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
hydrateNodeFromCache(node, cachedRecord) {
|
|
258
|
+
cachedRecord.explicitImports.forEach(i => node.explicitImports.add(i));
|
|
259
|
+
cachedRecord.dynamicImports.forEach(i => node.dynamicImports.add(i));
|
|
260
|
+
cachedRecord.importedSymbols.forEach(s => node.importedSymbols.add(s));
|
|
261
|
+
cachedRecord.rawStringReferences.forEach(r => node.rawStringReferences.add(r));
|
|
262
|
+
cachedRecord.instantiatedIdentifiers.forEach(i => node.instantiatedIdentifiers.add(i));
|
|
263
|
+
cachedRecord.propertyAccessChains.forEach(c => node.propertyAccessChains.add(c));
|
|
264
|
+
|
|
265
|
+
if (cachedRecord.internalExports) {
|
|
266
|
+
Object.entries(cachedRecord.internalExports).forEach(([k, v]) => {
|
|
267
|
+
node.internalExports.set(k, v);
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
node.isLibraryEntry = cachedRecord.isLibraryEntry || false;
|
|
271
|
+
node.securityThreats = cachedRecord.securityThreats || [];
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Crawls project roots using precise token extension checking instead of high-risk text matching.
|
|
276
|
+
*/
|
|
277
|
+
async discoverSourceFiles(currentDirectory, fileAccumulator) {
|
|
278
|
+
try {
|
|
279
|
+
const entries = await fs.readdir(currentDirectory, { withFileTypes: true });
|
|
280
|
+
|
|
281
|
+
for (const entry of entries) {
|
|
282
|
+
const absolutePath = path.join(currentDirectory, entry.name);
|
|
283
|
+
|
|
284
|
+
if (entry.isDirectory()) {
|
|
285
|
+
// Standard systemic exclusions filters
|
|
286
|
+
if (entry.name === 'node_modules' ||
|
|
287
|
+
entry.name === '.git' ||
|
|
288
|
+
entry.name === '.scaffold-cache' ||
|
|
289
|
+
entry.name === 'dist' ||
|
|
290
|
+
entry.name === 'build') {
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
await this.discoverSourceFiles(absolutePath, fileAccumulator);
|
|
294
|
+
} else if (entry.isFile()) {
|
|
295
|
+
const extension = path.extname(entry.name);
|
|
296
|
+
if (extension === '.js' ||
|
|
297
|
+
extension === '.ts' ||
|
|
298
|
+
extension === '.tsx' ||
|
|
299
|
+
extension === '.jsx' ||
|
|
300
|
+
entry.name === 'package.json') {
|
|
301
|
+
fileAccumulator.push(absolutePath);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
} catch {
|
|
306
|
+
// Path unreadable or access locked; close loop gracefully
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
displayConsoleDiagnostics(report) {
|
|
311
|
+
console.log(ansis.bold.green('\n📊 Codebase Structural Diagnostics Summary'));
|
|
312
|
+
console.log(ansis.dim('============================================================'));
|
|
313
|
+
console.log(`${ansis.bold('Processing Cycle Wall Duration:')} ${ansis.cyan(report.executionDuration)}`);
|
|
314
|
+
console.log(`${ansis.bold('Total Files Indexed Globally :')} ${ansis.white(report.totalFilesProcessed)}`);
|
|
315
|
+
console.log(`${ansis.bold('Delta Cache Optimization Ratio:')} ${ansis.green(report.graphCacheOptimization.ratio)} (Hits: ${report.graphCacheOptimization.hits} / Misses: ${report.graphCacheOptimization.misses})`);
|
|
316
|
+
console.log(ansis.dim('------------------------------------------------------------'));
|
|
317
|
+
|
|
318
|
+
if (report.structuralIssuesDetected.deadFiles.length > 0) {
|
|
319
|
+
console.log(ansis.bold.red(`\nOrphaned Files Flagged (${report.structuralIssuesDetected.deadFiles.length}):`));
|
|
320
|
+
report.structuralIssuesDetected.deadFiles.forEach(f => console.log(ansis.dim(` • ${f}`)));
|
|
321
|
+
} else {
|
|
322
|
+
console.log(ansis.green('\n✨ No orphaned or unreferenced component files found.'));
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (report.structuralIssuesDetected.deadExports.length > 0) {
|
|
326
|
+
console.log(ansis.bold.yellow(`\nUnused Named Symbol Exports Flagged (${report.structuralIssuesDetected.deadExports.length}):`));
|
|
327
|
+
report.structuralIssuesDetected.deadExports.forEach(e => {
|
|
328
|
+
console.log(` • ${ansis.dim(e.file)}:${e.line}:${e.column} -> [${ansis.yellow(e.symbol)}] (Type: ${e.type})`);
|
|
329
|
+
});
|
|
330
|
+
} else {
|
|
331
|
+
console.log(ansis.green('✨ No dead or unused named symbols exported across components.'));
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (report.structuralIssuesDetected.securityThreats.length > 0) {
|
|
335
|
+
console.log(ansis.bold.red(`\n⚠️ High-Risk Variable Assignment Alerts (${report.structuralIssuesDetected.securityThreats.length}):`));
|
|
336
|
+
report.structuralIssuesDetected.securityThreats.forEach(threat => {
|
|
337
|
+
console.log(` • ${ansis.red(threat.file)}:${threat.line} -> Variable [${ansis.bold(threat.identifier)}] contains a high-entropy secret (Shannon Score: ${threat.entropy})`);
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
console.log(ansis.dim('\n============================================================\n'));
|
|
342
|
+
}
|
|
343
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import crypto from 'crypto';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* High-Performance Graph State Persistence & Delta Hash Registry
|
|
7
|
+
* Automatically bypasses the AST compilation layer for unmodified files.
|
|
8
|
+
*/
|
|
9
|
+
export class IncrementalCacheManager {
|
|
10
|
+
constructor(context) {
|
|
11
|
+
this.context = context;
|
|
12
|
+
this.manifestPath = path.join(context.cacheDir, 'graph-manifest.json');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Computes a highly efficient SHA-256 hash checksum of a file directly from raw buffers.
|
|
17
|
+
* @param {string} filePath - Absolute path to the on-disk source component
|
|
18
|
+
*/
|
|
19
|
+
async computeHash(filePath) {
|
|
20
|
+
try {
|
|
21
|
+
const fileBuffer = await fs.readFile(filePath);
|
|
22
|
+
return crypto.createHash('sha256').update(fileBuffer).digest('hex');
|
|
23
|
+
} catch {
|
|
24
|
+
return '';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Loads the serialized manifest from disk, fallback to empty layout if unreadable.
|
|
30
|
+
* @returns {Promise<Object>} Mapped compilation cache states index
|
|
31
|
+
*/
|
|
32
|
+
async loadCacheManifest() {
|
|
33
|
+
try {
|
|
34
|
+
await fs.access(this.manifestPath);
|
|
35
|
+
const rawText = await fs.readFile(this.manifestPath, 'utf8');
|
|
36
|
+
return JSON.parse(rawText);
|
|
37
|
+
} catch {
|
|
38
|
+
if (this.context.verbose) {
|
|
39
|
+
console.log('✨ No structural performance cache found. Initializing a clean baseline manifest entry.');
|
|
40
|
+
}
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Serializes the current active dependency graph, translating Maps and Sets into JSON schemas.
|
|
47
|
+
* @param {Map<string, Object>} currentGraphState - In-memory structural project state map
|
|
48
|
+
*/
|
|
49
|
+
async saveCacheManifest(currentGraphState) {
|
|
50
|
+
const serializationOutput = {};
|
|
51
|
+
|
|
52
|
+
for (const [absolutePath, node] of currentGraphState.entries()) {
|
|
53
|
+
// Do not cache external configuration manifests like package.json
|
|
54
|
+
if (absolutePath.endsWith('package.json')) continue;
|
|
55
|
+
|
|
56
|
+
serializationOutput[absolutePath] = {
|
|
57
|
+
hash: node.contentHash,
|
|
58
|
+
isLibraryEntry: node.isLibraryEntry,
|
|
59
|
+
explicitImports: Array.from(node.explicitImports),
|
|
60
|
+
dynamicImports: Array.from(node.dynamicImports),
|
|
61
|
+
importedSymbols: Array.from(node.importedSymbols),
|
|
62
|
+
rawStringReferences: Array.from(node.rawStringReferences),
|
|
63
|
+
instantiatedIdentifiers: Array.from(node.instantiatedIdentifiers),
|
|
64
|
+
propertyAccessChains: Array.from(node.propertyAccessChains),
|
|
65
|
+
internalExports: Object.fromEntries(node.internalExports),
|
|
66
|
+
securityThreats: node.securityThreats || []
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
await fs.writeFile(
|
|
72
|
+
this.manifestPath,
|
|
73
|
+
JSON.stringify(serializationOutput, null, 2),
|
|
74
|
+
'utf8'
|
|
75
|
+
);
|
|
76
|
+
} catch (writeError) {
|
|
77
|
+
if (this.context.verbose) {
|
|
78
|
+
console.error(`🚨 [Cache Writer Instability] Failed to commit manifest log indices: ${writeError.message}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Monorepo Supply Chain Security & Typosquatting Anomaly Detection Engine
|
|
6
|
+
* Uses string distance algorithms to intercept package name substitution attacks.
|
|
7
|
+
*/
|
|
8
|
+
export class SupplyChainGuard {
|
|
9
|
+
constructor(context) {
|
|
10
|
+
this.context = context;
|
|
11
|
+
// Map popular dependencies to establish safe reference profiles
|
|
12
|
+
this.baselineEcosystemPackagesProfile = [
|
|
13
|
+
'lodash', 'react', 'react-dom', 'typescript', 'enhanced-resolve',
|
|
14
|
+
'commander', 'express', 'vue', 'next', 'svelte', 'ramda', 'execa'
|
|
15
|
+
];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Challenge #12: Compiles typo distance matrices to detect malicious package masking variants.
|
|
20
|
+
* @param {Array<string>} declaredDependenciesList - Manifest package name keys array
|
|
21
|
+
*/
|
|
22
|
+
detectTyposquattingAnomalies(declaredDependenciesList) {
|
|
23
|
+
const identifiedThreats = [];
|
|
24
|
+
|
|
25
|
+
for (const activeDependencyName of declaredDependenciesList) {
|
|
26
|
+
// Skip if the package is already recognized as a trusted ecosystem standard
|
|
27
|
+
if (this.baselineEcosystemPackagesProfile.includes(activeDependencyName)) continue;
|
|
28
|
+
|
|
29
|
+
for (const safePackageStandard of this.baselineEcosystemPackagesProfile) {
|
|
30
|
+
const structuralDistance = this.calculateLevenshteinDistance(
|
|
31
|
+
activeDependencyName,
|
|
32
|
+
safePackageStandard
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
// Flag an alert if a name mimics a top tier framework package down to 1-2 character edits
|
|
36
|
+
if (structuralDistance > 0 && structuralDistance <= 2) {
|
|
37
|
+
identifiedThreats.push({
|
|
38
|
+
maliciousCandidate: activeDependencyName,
|
|
39
|
+
targetMimicked: safePackageStandard,
|
|
40
|
+
severityLevel: 'CRITICAL_SUPPLY_CHAIN_THREAT',
|
|
41
|
+
distance: structuralDistance
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return identifiedThreats;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Challenge #13: Cross-references package lock signatures against on-disk configuration maps.
|
|
52
|
+
*/
|
|
53
|
+
async verifyIntegrityLockfileHashes(packageJsonPath) {
|
|
54
|
+
const rootDirectory = path.dirname(packageJsonPath);
|
|
55
|
+
const commonLockfileTargets = [
|
|
56
|
+
{ name: 'package-lock.json', type: 'npm' },
|
|
57
|
+
{ name: 'pnpm-lock.yaml', type: 'pnpm' },
|
|
58
|
+
{ name: 'yarn.lock', type: 'yarn' }
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
for (const target of commonLockfileTargets) {
|
|
62
|
+
try {
|
|
63
|
+
const absoluteLockPath = path.join(rootDirectory, target.name);
|
|
64
|
+
await fs.access(absoluteLockPath);
|
|
65
|
+
|
|
66
|
+
if (target.type === 'npm') {
|
|
67
|
+
const rawData = await fs.readFile(absoluteLockPath, 'utf8');
|
|
68
|
+
const lockJson = JSON.parse(rawData);
|
|
69
|
+
|
|
70
|
+
if (lockJson.packages) {
|
|
71
|
+
// Verify checksum entries for deep security profiling
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
} catch {
|
|
76
|
+
// Target lock configuration mismatch; try alternative package format options
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
calculateLevenshteinDistance(stringA, stringB) {
|
|
83
|
+
const matrix = [];
|
|
84
|
+
|
|
85
|
+
for (let i = 0; i <= stringB.length; i++) matrix[i] = [i];
|
|
86
|
+
for (let j = 0; j <= stringA.length; j++) matrix[0][j] = j;
|
|
87
|
+
|
|
88
|
+
for (let i = 1; i <= stringB.length; i++) {
|
|
89
|
+
for (let j = 1; j <= stringA.length; j++) {
|
|
90
|
+
if (stringB.charAt(i - 1) === stringA.charAt(j - 1)) {
|
|
91
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
92
|
+
} else {
|
|
93
|
+
matrix[i][j] = Math.min(
|
|
94
|
+
matrix[i - 1][j - 1] + 1, // Substitution mutation step
|
|
95
|
+
Math.min(
|
|
96
|
+
matrix[i][j - 1] + 1, // Insertion mutation step
|
|
97
|
+
matrix[i - 1][j] + 1 // Deletion mutation step
|
|
98
|
+
)
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return matrix[stringB.length][stringA.length];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Worker, isMainThread, parentPort, workerData } from 'worker_threads';
|
|
2
|
+
import os from 'core-os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Host CPU Thread-Distribution Pipeline Supervisor
|
|
7
|
+
* Parallelizes compiler parsing logic without triggering filesystem write collisions.
|
|
8
|
+
*/
|
|
9
|
+
export class WorkerPool {
|
|
10
|
+
constructor(context, maximumConcurrencyLimit = null) {
|
|
11
|
+
this.context = context;
|
|
12
|
+
// Dynamically query host specs; default down to 1 if threading channels are choked
|
|
13
|
+
this.hardwareConcurrencyCoreCount = maximumConcurrencyLimit || os.availableParallelism?.() || os.cpus().length || 2;
|
|
14
|
+
this.workerScriptPath = path.resolve(path.dirname(import.meta.url.replace('file://', '')), 'WorkerTaskRunner.js');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Distributes a collection of target filenames across concurrent thread pools.
|
|
19
|
+
* @param {Array<string>} totalFilePathsCollection - Absolute filesystem target pointers array
|
|
20
|
+
* @param {Object} masterEngineInstanceReference - Main RefactoringEngine context loop channel
|
|
21
|
+
*/
|
|
22
|
+
async parallelAnalyzeCodebase(totalFilePathsCollection, masterEngineInstanceReference) {
|
|
23
|
+
if (totalFilePathsCollection.length < 12) {
|
|
24
|
+
// Optimization: Do not waste overhead spin-up cycles on small layout codebases
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.log(`⚡ Spawning native compiler thread pools across [${this.hardwareConcurrencyCoreCount}] CPU cores concurrently...`);
|
|
29
|
+
|
|
30
|
+
// Chunk the workload array evenly across the generated worker targets
|
|
31
|
+
const analyticalWorkloadChunks = Array.from(
|
|
32
|
+
{ length: this.hardwareConcurrencyCoreCount },
|
|
33
|
+
() => []
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
totalFilePathsCollection.forEach((filePath, fileIndex) => {
|
|
37
|
+
analyticalWorkloadChunks[fileIndex % this.hardwareConcurrencyCoreCount].push(filePath);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const threadTaskExecutionsList = analyticalWorkloadChunks.map(chunk => {
|
|
41
|
+
if (chunk.length === 0) return Promise.resolve([]);
|
|
42
|
+
return this.executeChunkInsideThread(chunk);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const analyticalResultsSubsets = await Promise.all(threadTaskExecutionsList);
|
|
47
|
+
|
|
48
|
+
// Merge thread structural subsets back into the primary context graph nodes
|
|
49
|
+
analyticalResultsSubsets.flat().forEach(result => {
|
|
50
|
+
if (!result) return;
|
|
51
|
+
const node = masterEngineInstanceReference.context.createNode(result.filePath);
|
|
52
|
+
|
|
53
|
+
result.explicitImports.forEach(i => node.explicitImports.add(i));
|
|
54
|
+
result.dynamicImports.forEach(i => node.dynamicImports.add(i));
|
|
55
|
+
result.importedSymbols.forEach(s => node.importedSymbols.add(s));
|
|
56
|
+
result.rawStringReferences.forEach(r => node.rawStringReferences.add(r));
|
|
57
|
+
result.instantiatedIdentifiers.forEach(i => node.instantiatedIdentifiers.add(i));
|
|
58
|
+
result.propertyAccessChains.forEach(c => node.propertyAccessChains.add(c));
|
|
59
|
+
|
|
60
|
+
Object.entries(result.internalExports).forEach(([k, v]) => {
|
|
61
|
+
node.internalExports.set(k, v);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
node.securityThreats = result.securityThreats || [];
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return true;
|
|
68
|
+
} catch (poolThreadFault) {
|
|
69
|
+
if (this.context.verbose) {
|
|
70
|
+
console.warn(`⚠️ ThreadPool runtime fault: ${poolThreadFault.message}. Falling back to main-thread processing.`);
|
|
71
|
+
}
|
|
72
|
+
return false; // Safely fall back to single-thread synchronous recovery processing
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
executeChunkInsideThread(fileChunkSubset) {
|
|
77
|
+
return new Promise((resolve, reject) => {
|
|
78
|
+
const workerInstance = new Worker(this.workerScriptPath, {
|
|
79
|
+
workerData: { files: fileChunkSubset, contextOptions: { verbose: this.context.verbose } }
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
workerInstance.on('message', (payload) => resolve(payload));
|
|
83
|
+
workerInstance.on('error', (err) => reject(err));
|
|
84
|
+
workerInstance.on('exit', (exitCode) => {
|
|
85
|
+
if (exitCode !== 0) reject(new Error(`Worker thread collapsed unexpectedly with code: ${exitCode}`));
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { parentPort, workerData } from 'worker_threads';
|
|
2
|
+
import { ASTAnalyzer } from '../ast/ASTAnalyzer.js';
|
|
3
|
+
import ts from 'typescript';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Isolated Worker Thread Target Pipeline Task Loop Execution Instance
|
|
8
|
+
*/
|
|
9
|
+
async function processThreadChunks() {
|
|
10
|
+
const { files, contextOptions } = workerData;
|
|
11
|
+
const partialGraphPayloadResults = [];
|
|
12
|
+
|
|
13
|
+
// Construct a lightweight standalone instance of our analyzer core inside the worker
|
|
14
|
+
const standaloneAnalyzer = new ASTAnalyzer({ verbose: contextOptions.verbose });
|
|
15
|
+
|
|
16
|
+
for (const file of files) {
|
|
17
|
+
if (file.endsWith('package.json')) continue;
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const text = fs.readFileSync(file, 'utf8');
|
|
21
|
+
|
|
22
|
+
// Build a minimal virtual reference mapping node to capture features
|
|
23
|
+
const mockNode = {
|
|
24
|
+
explicitImports: new Set(),
|
|
25
|
+
dynamicImports: new Set(),
|
|
26
|
+
importedSymbols: new Set(),
|
|
27
|
+
rawStringReferences: new Set(),
|
|
28
|
+
instantiatedIdentifiers: new Set(),
|
|
29
|
+
propertyAccessChains: new Set(),
|
|
30
|
+
internalExports: new Map(),
|
|
31
|
+
securityThreats: []
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const sourceFile = ts.createSourceFile(
|
|
35
|
+
file,
|
|
36
|
+
text,
|
|
37
|
+
ts.ScriptTarget.Latest,
|
|
38
|
+
true,
|
|
39
|
+
file.endsWith('.ts') ? ts.ScriptKind.TS : file.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.JS
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
standaloneAnalyzer.traverseNodeTree(sourceFile, sourceFile, mockNode);
|
|
43
|
+
|
|
44
|
+
partialGraphPayloadResults.push({
|
|
45
|
+
filePath: file,
|
|
46
|
+
explicitImports: Array.from(mockNode.explicitImports),
|
|
47
|
+
dynamicImports: Array.from(mockNode.dynamicImports),
|
|
48
|
+
importedSymbols: Array.from(mockNode.importedSymbols),
|
|
49
|
+
rawStringReferences: Array.from(mockNode.rawStringReferences),
|
|
50
|
+
instantiatedIdentifiers: Array.from(mockNode.instantiatedIdentifiers),
|
|
51
|
+
propertyAccessChains: Array.from(mockNode.propertyAccessChains),
|
|
52
|
+
internalExports: Object.fromEntries(mockNode.internalExports),
|
|
53
|
+
securityThreats: mockNode.securityThreats
|
|
54
|
+
});
|
|
55
|
+
} catch {
|
|
56
|
+
// Ignore unparseable or locked syntax nodes in thread loops
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Stream compiled metadata structures directly back to the primary supervisor pool thread channel
|
|
61
|
+
parentPort.postMessage(partialGraphPayloadResults);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
processThreadChunks();
|