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 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 [2026] [name of copyright owner]
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 original code was from the lovely DreamLong"
210
+ "The Original Code was made by DreamLongYT"
211
211
  ============================================================================
package/NOTICE CHANGED
@@ -1,7 +1,7 @@
1
1
  pkg-scaffold
2
2
  Copyright 2026 DreamLongYT
3
3
 
4
- The original code was from the lovely DreamLong
4
+ The Original Code was made by DreamLongYT
5
5
 
6
6
  This product includes software developed at
7
7
  pkg-scaffold (https://github.com/DreamLongYT/pkg-scaffold).
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.0.0');
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
- // 1. Ask to install script
62
- if (pkgJson && !pkgJson.scripts?.['pkg-scaffold:run']) {
63
- const answer = await rl.question(ansis.bold.yellow('❓ No "pkg-scaffold:run" script found in package.json. Install it? (y/n): '));
64
- if (answer.toLowerCase() === 'y') {
65
- pkgJson.scripts = pkgJson.scripts || {};
66
- pkgJson.scripts['pkg-scaffold:run'] = 'pkg-scaffold --fix';
67
- await fs.writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
68
- console.log(ansis.green('✅ "pkg-scaffold:run" script added to package.json.'));
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
- // 2. Ask to install config folder
73
- let configInstalled = false;
74
- try {
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
- 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`));
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
- console.log(ansis.bold.green(`\n📦 pkg-scaffold v${packageJsonContent.version || '3.1.0'} Engine Activation`));
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.0",
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",
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * ============================================================================
3
- * 📦 pkg-scaffold v3.0.0: Enterprise In-Memory Codebase State Manifest
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 (no references, not library/framework entries)
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; // An orphaned file implies all internal sub-exports are dead; skip sub-checks
263
+ continue;
266
264
  }
267
265
 
268
- // Category B: Dead Named Exports inside active files
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: High-Entropy Password / Key Hardcode Vulnerabilities
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
- // We only audit 'dependencies' for now as devDependencies are harder to track (test files, tools, etc.)
310
- for (const dep of manifestData.dependencies) {
311
- if (!this.usedExternalPackages.has(dep)) {
312
- summary.structuralIssuesDetected.unusedDependencies.push({
313
- manifest: relativeManifest,
314
- package: dep,
315
- type: 'dependency'
316
- });
317
- this.metrics.unusedDependenciesCount++;
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;
@@ -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) {
@@ -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
- return await this.oxc.processFile(filePath, fileNode);
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) {
@@ -1,21 +1,35 @@
1
- import { parseSync } from 'oxc-parser';
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
- // OXC is significantly faster than TypeScript for single-pass analysis
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') {