pkg-scaffold 3.3.0 → 3.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +2 -2
- package/NOTICE +1 -1
- package/bin/cli.js +52 -59
- package/package.json +1 -1
- package/src/EngineContext.js +24 -20
- package/src/api/HeadlessAPI.js +11 -0
- package/src/ast/ASTAnalyzer.js +3 -2
- package/src/ast/OxcAnalyzer.js +17 -7
- package/src/index.js +119 -148
- package/src/performance/SecretDetector.js +378 -0
- package/src/resolution/CircularDetector.js +81 -30
package/LICENSE
CHANGED
|
@@ -186,7 +186,7 @@
|
|
|
186
186
|
same "printed page" as the copyright notice for easier
|
|
187
187
|
identification within third-party archives.
|
|
188
188
|
|
|
189
|
-
Copyright [
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
190
|
|
|
191
191
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
192
|
you may not use this file except in compliance with the License.
|
|
@@ -207,5 +207,5 @@
|
|
|
207
207
|
following attribution in a prominent location (e.g., README, about page,
|
|
208
208
|
or CLI output):
|
|
209
209
|
|
|
210
|
-
"The
|
|
210
|
+
"The Original Code was made by DreamLongYT"
|
|
211
211
|
============================================================================
|
package/NOTICE
CHANGED
package/bin/cli.js
CHANGED
|
@@ -28,7 +28,7 @@ async function bootstrap() {
|
|
|
28
28
|
program
|
|
29
29
|
.name('pkg-scaffold')
|
|
30
30
|
.description(ansis.cyan('Enterprise-Grade AST Syntax Refactoring & Self-Healing Engine'))
|
|
31
|
-
.version(packageJsonContent.version || '3.
|
|
31
|
+
.version(packageJsonContent.version || '3.3.2');
|
|
32
32
|
|
|
33
33
|
program
|
|
34
34
|
.option('-c, --cwd <path>', 'Specify the execution context root directory', process.cwd())
|
|
@@ -39,14 +39,15 @@ async function bootstrap() {
|
|
|
39
39
|
.option('--workspace', 'Enable high-density workspace workspace/monorepo cluster mesh evaluation parsing', false)
|
|
40
40
|
.option('--verbose', 'Toggle expanded trace telemetry for debug operational diagnostics', false)
|
|
41
41
|
.option('-r, --run', 'Execute the primary operational pipeline loop', false)
|
|
42
|
-
.option('-y, --yes', 'Skip confirmation prompts and execute planned structural modifications automatically', false)
|
|
42
|
+
.option('-y, --yes', 'Skip confirmation prompts and execute planned structural modifications automatically', false)
|
|
43
|
+
.option('--timeout <ms>', 'Set execution timeout in milliseconds', '30000');
|
|
43
44
|
|
|
44
45
|
program.parse(process.argv);
|
|
45
46
|
const options = program.opts();
|
|
46
47
|
|
|
47
48
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
48
49
|
|
|
49
|
-
// --- Onboarding Check ---
|
|
50
|
+
// --- Onboarding Check (Skipped in Non-Interactive Mode) ---
|
|
50
51
|
const targetCwd = path.resolve(options.cwd);
|
|
51
52
|
const pkgJsonPath = path.join(targetCwd, 'package.json');
|
|
52
53
|
const configDirPath = path.join(targetCwd, 'pkg-scaffold');
|
|
@@ -54,49 +55,49 @@ async function bootstrap() {
|
|
|
54
55
|
let pkgJson;
|
|
55
56
|
try {
|
|
56
57
|
pkgJson = JSON.parse(await fs.readFile(pkgJsonPath, 'utf8'));
|
|
57
|
-
} catch (e) {
|
|
58
|
-
// No package.json found in target
|
|
59
|
-
}
|
|
58
|
+
} catch (e) {}
|
|
60
59
|
|
|
61
|
-
|
|
62
|
-
if (
|
|
63
|
-
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
60
|
+
let configInstalled = false;
|
|
61
|
+
if (!options.yes && !options.run) {
|
|
62
|
+
// 1. Ask to install script
|
|
63
|
+
if (pkgJson && !pkgJson.scripts?.['pkg-scaffold:run']) {
|
|
64
|
+
const answer = await rl.question(ansis.bold.yellow('❓ No "pkg-scaffold:run" script found in package.json. Install it? (y/n): '));
|
|
65
|
+
if (answer.toLowerCase() === 'y') {
|
|
66
|
+
pkgJson.scripts = pkgJson.scripts || {};
|
|
67
|
+
pkgJson.scripts['pkg-scaffold:run'] = 'pkg-scaffold --fix';
|
|
68
|
+
await fs.writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
|
|
69
|
+
console.log(ansis.green('✅ "pkg-scaffold:run" script added to package.json.'));
|
|
70
|
+
}
|
|
69
71
|
}
|
|
70
|
-
}
|
|
71
72
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
await fs.access(configDirPath);
|
|
76
|
-
configInstalled = true;
|
|
77
|
-
} catch (e) {
|
|
78
|
-
const answer = await rl.question(ansis.bold.yellow('❓ No "/pkg-scaffold" configuration folder found. Create it with defaults? (y/n): '));
|
|
79
|
-
if (answer.toLowerCase() === 'y') {
|
|
80
|
-
await fs.mkdir(configDirPath, { recursive: true });
|
|
81
|
-
await fs.mkdir(path.join(configDirPath, 'plugins'), { recursive: true });
|
|
82
|
-
const defaultConfig = {
|
|
83
|
-
interface: "CLI",
|
|
84
|
-
useBuiltinPlugins: true,
|
|
85
|
-
useCustomPlugins: true,
|
|
86
|
-
supportKnipPlugins: true,
|
|
87
|
-
options: { verbose: false, fastMode: true, selfHealing: true },
|
|
88
|
-
enabledPlugins: ["nextjs", "nuxt", "remix", "sveltekit", "astro"]
|
|
89
|
-
};
|
|
90
|
-
await fs.writeFile(path.join(configDirPath, 'config.json'), JSON.stringify(defaultConfig, null, 2));
|
|
91
|
-
console.log(ansis.green('✅ "/pkg-scaffold" folder and default config created.'));
|
|
73
|
+
// 2. Ask to install config folder
|
|
74
|
+
try {
|
|
75
|
+
await fs.access(configDirPath);
|
|
92
76
|
configInstalled = true;
|
|
77
|
+
} catch (e) {
|
|
78
|
+
const answer = await rl.question(ansis.bold.yellow('❓ No "/pkg-scaffold" configuration folder found. Create it with defaults? (y/n): '));
|
|
79
|
+
if (answer.toLowerCase() === 'y') {
|
|
80
|
+
await fs.mkdir(configDirPath, { recursive: true });
|
|
81
|
+
await fs.mkdir(path.join(configDirPath, 'plugins'), { recursive: true });
|
|
82
|
+
const defaultConfig = {
|
|
83
|
+
interface: "CLI",
|
|
84
|
+
useBuiltinPlugins: true,
|
|
85
|
+
useCustomPlugins: true,
|
|
86
|
+
supportKnipPlugins: true,
|
|
87
|
+
options: { verbose: false, fastMode: true, selfHealing: true },
|
|
88
|
+
enabledPlugins: ["nextjs", "nuxt", "remix", "sveltekit", "astro"]
|
|
89
|
+
};
|
|
90
|
+
await fs.writeFile(path.join(configDirPath, 'config.json'), JSON.stringify(defaultConfig, null, 2));
|
|
91
|
+
console.log(ansis.green('✅ "/pkg-scaffold" folder and default config created.'));
|
|
92
|
+
configInstalled = true;
|
|
93
|
+
}
|
|
93
94
|
}
|
|
94
|
-
}
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
if (pkgJson?.scripts?.['pkg-scaffold:run'] || configInstalled) {
|
|
97
|
+
console.log(ansis.bold.cyan('\n🚀 Setup complete! To start the engine, run:'));
|
|
98
|
+
console.log(ansis.white(` - pkg-scaffold -r`));
|
|
99
|
+
console.log(ansis.white(` - npm run pkg-scaffold:run\n`));
|
|
100
|
+
}
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
rl.close();
|
|
@@ -113,7 +114,6 @@ async function bootstrap() {
|
|
|
113
114
|
const finalInterface = localConfig.interface || 'CLI';
|
|
114
115
|
if (finalInterface === 'GUI') {
|
|
115
116
|
console.log(ansis.bold.magenta('🎨 GUI Mode Detected. Starting Web Interface...'));
|
|
116
|
-
// In a real scenario, we'd start a local server or Electron here
|
|
117
117
|
return;
|
|
118
118
|
}
|
|
119
119
|
|
|
@@ -122,31 +122,23 @@ async function bootstrap() {
|
|
|
122
122
|
return;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
|
|
125
|
+
// --- Timeout Handling ---
|
|
126
|
+
const timeoutMs = parseInt(options.timeout);
|
|
127
|
+
const timeoutTimer = setTimeout(() => {
|
|
128
|
+
console.error(ansis.bold.red(`\n🚨 Execution Timeout: The process exceeded the limit of ${timeoutMs}ms.`));
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}, timeoutMs);
|
|
131
|
+
timeoutTimer.unref(); // Allow process to exit if work finishes
|
|
132
|
+
|
|
133
|
+
console.log(ansis.bold.green(`\n📦 pkg-scaffold v${packageJsonContent.version || '3.3.2'} Engine Activation`));
|
|
126
134
|
console.log(ansis.dim('------------------------------------------------------------'));
|
|
127
135
|
console.log(`${ansis.bold('Target Workspace Root :')} ${ansis.blue(path.resolve(options.cwd))}`);
|
|
128
136
|
console.log(`${ansis.bold('Refactoring Mode :')} ${options.fix ? ansis.yellow('Active Fixing & Self-Healing Enabled') : ansis.gray('Dry-Run Reporting Only')}`);
|
|
129
137
|
console.log(`${ansis.bold('Validation Sandbox :')} ${ansis.magenta(options.testCommand)}`);
|
|
130
138
|
console.log(ansis.dim('------------------------------------------------------------\n'));
|
|
131
139
|
|
|
132
|
-
const engineModulePath = path.resolve(__dirname, '../src/EngineContext.js');
|
|
133
|
-
|
|
134
|
-
try {
|
|
135
|
-
await fs.access(engineModulePath);
|
|
136
|
-
} catch {
|
|
137
|
-
console.error(ansis.red(`🚨 Execution Fault: Core engine architecture files missing. Ensure src/ directory layout is fully generated.`));
|
|
138
|
-
process.exit(1);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Lazy load execution context wrapper to align with domain initialization
|
|
142
140
|
const { RefactoringEngine } = await import('../src/index.js');
|
|
143
141
|
|
|
144
|
-
if (!RefactoringEngine) {
|
|
145
|
-
console.error(ansis.red('🚨 Architecture Boundary Error: RefactoringEngine could not be resolved from code topology channels.'));
|
|
146
|
-
process.exit(1);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Ensure cwd is always an absolute path regardless of how it was passed (e.g., '--cwd .')
|
|
150
142
|
const engine = new RefactoringEngine({
|
|
151
143
|
cwd: path.resolve(options.cwd),
|
|
152
144
|
autoFix: options.fix,
|
|
@@ -158,7 +150,8 @@ async function bootstrap() {
|
|
|
158
150
|
});
|
|
159
151
|
|
|
160
152
|
await engine.run();
|
|
161
|
-
|
|
153
|
+
|
|
154
|
+
clearTimeout(timeoutTimer);
|
|
162
155
|
console.log(ansis.bold.green('\n✨ Core cycle execution completed successfully. Structural layout is clean.'));
|
|
163
156
|
process.exit(0);
|
|
164
157
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pkg-scaffold",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.2",
|
|
4
4
|
"description": "The Ultimate Enterprise Codebase Janitor. Faster than Knip with OXC integration, type-aware analysis, and self-healing capabilities. Fully standalone - solving what Knip cannot.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
package/src/EngineContext.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ============================================================================
|
|
3
|
-
* 📦 pkg-scaffold v3.
|
|
3
|
+
* 📦 pkg-scaffold v3.3.0: Enterprise In-Memory Codebase State Manifest
|
|
4
4
|
* ============================================================================
|
|
5
5
|
* Implements a high-density, centralized graph database context for tracking
|
|
6
6
|
* software engineering debt, dependencies, types, and vulnerabilities.
|
|
@@ -145,7 +145,7 @@ export class EngineContext {
|
|
|
145
145
|
unusedDependenciesCount: 0
|
|
146
146
|
};
|
|
147
147
|
this.usedExternalPackages = new Set(); // Global set of used npm packages
|
|
148
|
-
this.manifestDependencies = new Map(); // Package.json path -> { dependencies, devDependencies }
|
|
148
|
+
this.manifestDependencies = new Map(); // Package.json path -> { dependencies, devDependencies, peerDependencies, optionalDependencies }
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
/**
|
|
@@ -256,20 +256,17 @@ export class EngineContext {
|
|
|
256
256
|
|
|
257
257
|
const relativePath = path.relative(this.cwd, filePath);
|
|
258
258
|
|
|
259
|
-
// Category A: Completely orphaned components
|
|
260
|
-
// A file with ANY @scaffold-suppress directive is considered intentionally retained and must
|
|
261
|
-
// never be flagged as orphaned, even if no other file imports it directly.
|
|
259
|
+
// Category A: Completely orphaned components
|
|
262
260
|
const fileHasSuppressDirective = node.localSuppressedRules.size > 0;
|
|
263
261
|
if (node.incomingEdges.size === 0 && !node.isLibraryEntry && !node.isFrameworkContract && !fileHasSuppressDirective) {
|
|
264
262
|
summary.structuralIssuesDetected.deadFiles.push(relativePath);
|
|
265
|
-
continue;
|
|
263
|
+
continue;
|
|
266
264
|
}
|
|
267
265
|
|
|
268
|
-
// Category B: Dead Named Exports
|
|
266
|
+
// Category B: Dead Named Exports
|
|
269
267
|
for (const [exportName, meta] of node.internalExports.entries()) {
|
|
270
268
|
this.metrics.totalSymbolsAnalyzed++;
|
|
271
269
|
|
|
272
|
-
// Skip entry configurations, global suppresses, and type-suppressed symbols
|
|
273
270
|
if (exportName === 'default' ||
|
|
274
271
|
this.globallyIgnoredSymbols.has(exportName) ||
|
|
275
272
|
node.localSuppressedRules.has(exportName)) {
|
|
@@ -288,7 +285,7 @@ export class EngineContext {
|
|
|
288
285
|
}
|
|
289
286
|
}
|
|
290
287
|
|
|
291
|
-
// Category C:
|
|
288
|
+
// Category C: Security Vulnerabilities
|
|
292
289
|
if (node.securityThreats && node.securityThreats.length > 0) {
|
|
293
290
|
node.securityThreats.forEach(threat => {
|
|
294
291
|
summary.structuralIssuesDetected.securityThreats.push({
|
|
@@ -302,21 +299,28 @@ export class EngineContext {
|
|
|
302
299
|
}
|
|
303
300
|
}
|
|
304
301
|
|
|
305
|
-
// Category D: Unused Dependencies Audit
|
|
302
|
+
// Category D: Unused Dependencies Audit (Enhanced Classification)
|
|
306
303
|
for (const [manifestPath, manifestData] of this.manifestDependencies.entries()) {
|
|
307
304
|
const relativeManifest = path.relative(this.cwd, manifestPath);
|
|
308
305
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
306
|
+
const checkDeps = (deps, type) => {
|
|
307
|
+
if (!deps) return;
|
|
308
|
+
for (const dep of deps) {
|
|
309
|
+
if (!this.usedExternalPackages.has(dep)) {
|
|
310
|
+
summary.structuralIssuesDetected.unusedDependencies.push({
|
|
311
|
+
manifest: relativeManifest,
|
|
312
|
+
package: dep,
|
|
313
|
+
type: type
|
|
314
|
+
});
|
|
315
|
+
this.metrics.unusedDependenciesCount++;
|
|
316
|
+
}
|
|
318
317
|
}
|
|
319
|
-
}
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
checkDeps(manifestData.dependencies, 'dependency');
|
|
321
|
+
checkDeps(manifestData.devDependencies, 'devDependency');
|
|
322
|
+
checkDeps(manifestData.peerDependencies, 'peerDependency');
|
|
323
|
+
checkDeps(manifestData.optionalDependencies, 'optionalDependency');
|
|
320
324
|
}
|
|
321
325
|
|
|
322
326
|
return summary;
|
package/src/api/HeadlessAPI.js
CHANGED
|
@@ -105,6 +105,17 @@ export class HeadlessAPI extends EventEmitter {
|
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
// Initialize TypeScript program for AST analysis (required before processFile)
|
|
109
|
+
if (sourceCodeFilesList.length > 0) {
|
|
110
|
+
try {
|
|
111
|
+
this.engine.analyzer.initProgram(sourceCodeFilesList);
|
|
112
|
+
} catch (e) {
|
|
113
|
+
if (this.engine.context.verbose) {
|
|
114
|
+
console.warn('Warning: Failed to initialize TypeScript program:', e.message);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
108
119
|
// Parallel processing
|
|
109
120
|
let parallelParseCompleted = false;
|
|
110
121
|
if (sourceCodeFilesList.length > 10) {
|
package/src/ast/ASTAnalyzer.js
CHANGED
|
@@ -40,8 +40,9 @@ export class ASTAnalyzer {
|
|
|
40
40
|
*/
|
|
41
41
|
async processFile(filePath, fileNode) {
|
|
42
42
|
// Fast Path: Use OXC for rapid scanning if type checking is not strictly required for this file
|
|
43
|
-
if (this.context.fastMode) {
|
|
44
|
-
|
|
43
|
+
if (this.context.fastMode && this.oxc.isAvailable) {
|
|
44
|
+
const success = await this.oxc.processFile(filePath, fileNode);
|
|
45
|
+
if (success) return true;
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
if (!this.program) {
|
package/src/ast/OxcAnalyzer.js
CHANGED
|
@@ -1,21 +1,35 @@
|
|
|
1
|
-
|
|
1
|
+
let oxcParser;
|
|
2
|
+
try {
|
|
3
|
+
oxcParser = await import('oxc-parser');
|
|
4
|
+
} catch (e) {
|
|
5
|
+
// OXC is optional; will fall back to TypeScript in ASTAnalyzer
|
|
6
|
+
}
|
|
7
|
+
|
|
2
8
|
import fs from 'fs/promises';
|
|
3
9
|
|
|
4
10
|
/**
|
|
5
11
|
* High-Performance AST Analyzer using OXC (Rust-based)
|
|
6
12
|
* Designed to outpace Knip v6 by utilizing the fastest parser in the JS ecosystem.
|
|
13
|
+
* Includes automatic fallback if OXC is not available in the environment.
|
|
7
14
|
*/
|
|
8
15
|
export class OxcAnalyzer {
|
|
9
16
|
constructor(context) {
|
|
10
17
|
this.context = context;
|
|
18
|
+
this.isAvailable = !!oxcParser;
|
|
11
19
|
}
|
|
12
20
|
|
|
13
21
|
async processFile(filePath, fileNode) {
|
|
22
|
+
if (!this.isAvailable) {
|
|
23
|
+
if (this.context.verbose) {
|
|
24
|
+
console.warn(`[OXC] Library not available, skipping fast-scan for: ${filePath}`);
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
14
29
|
try {
|
|
15
30
|
const sourceText = await fs.readFile(filePath, 'utf8');
|
|
16
31
|
|
|
17
|
-
|
|
18
|
-
const { program } = parseSync(sourceText, {
|
|
32
|
+
const { program } = oxcParser.parseSync(sourceText, {
|
|
19
33
|
sourceFilename: filePath,
|
|
20
34
|
sourceType: filePath.endsWith('.ts') || filePath.endsWith('.tsx') ? 'typescript' : 'script'
|
|
21
35
|
});
|
|
@@ -33,8 +47,6 @@ export class OxcAnalyzer {
|
|
|
33
47
|
walkOxcNode(node, fileNode) {
|
|
34
48
|
if (!node) return;
|
|
35
49
|
|
|
36
|
-
// OXC AST structure is slightly different from TS
|
|
37
|
-
// We focus on imports, exports, and namespace members
|
|
38
50
|
if (node.type === 'ImportDeclaration') {
|
|
39
51
|
const specifier = node.source.value;
|
|
40
52
|
fileNode.explicitImports.add(specifier);
|
|
@@ -64,7 +76,6 @@ export class OxcAnalyzer {
|
|
|
64
76
|
fileNode.internalExports.set('default', { type: 'default-export' });
|
|
65
77
|
}
|
|
66
78
|
|
|
67
|
-
// Phase 3: Namespace Member Tracking
|
|
68
79
|
if (node.type === 'TSModuleDeclaration' && node.id.type === 'Identifier') {
|
|
69
80
|
const namespaceName = node.id.name;
|
|
70
81
|
if (node.body && node.body.type === 'TSModuleBlock') {
|
|
@@ -86,7 +97,6 @@ export class OxcAnalyzer {
|
|
|
86
97
|
}
|
|
87
98
|
}
|
|
88
99
|
|
|
89
|
-
// Recursively walk
|
|
90
100
|
for (const key in node) {
|
|
91
101
|
const child = node[key];
|
|
92
102
|
if (child && typeof child === 'object') {
|