pkg-scaffold 1.1.1 → 2.0.1

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/index.js CHANGED
@@ -10,24 +10,210 @@ import readline from 'readline/promises';
10
10
  import * as acorn from 'acorn';
11
11
  import * as walk from 'acorn-walk';
12
12
 
13
- const IGNORED_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.turbo', 'coverage', 'out']);
14
- const VALID_EXTENSIONS = new Set(['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs']);
13
+ const IGNORED_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.turbo', 'coverage', 'out', '.next', '.nuxt', '.svelte-kit', 'storybook-static', '.cache']);
14
+ const VALID_EXTENSIONS = new Set(['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.vue', '.svelte']);
15
15
 
16
16
  // --- Refined Target Signature Dictionaries ---
17
17
  const REGEX_PATTERNS = {
18
18
  env: /(?:process\.env|import\.meta\.env)\.([A-Z_][A-Z0-9_]*)/g,
19
- testFile: /\.(test|spec)\.(js|ts|jsx|tsx)$/i,
20
-
19
+ testFile: /\.(test|spec)\.(js|ts|jsx|tsx|mjs|cjs)$/i,
20
+ configFile: /^(vite|webpack|rollup|babel|jest|vitest|tailwind|postcss|next|nuxt|svelte|astro)\.config\./i,
21
+
21
22
  // Modern Quality & Structural Code Smell Monitors
22
23
  legacyVar: /\bvar\s+[a-zA-Z_]/g,
23
24
  dangerousEval: /\beval\s*\(/g,
24
25
  syncFsCalls: /\.readFileSync|\.writeFileSync|\.mkdirSync|\.existsSync/g,
25
-
26
+
26
27
  // Cryptographic Risk & Hardcoded Keyholes
27
28
  secretKeys: /\b(secret|passwd|password|token|api_?key|private_?key)\s*=\s*['"`]([a-zA-Z0-9_\-\.]{8,})['"`]/gi
28
29
  };
29
30
 
30
- const COMMON_EXTERNAL_TOKENS = new Set(['axios', 'lodash', 'dotenv', 'cors', 'zod', 'mongoose', 'jsonwebtoken', 'chalk', 'helmet', 'prisma', 'redis', 'pg']);
31
+ // ============================================================
32
+ // COMPREHENSIVE BINARY-TO-PACKAGE MAPPING (Knip-style)
33
+ // Maps CLI binary names → npm package names
34
+ // ============================================================
35
+ const BINARY_TO_PACKAGE_MAP = {
36
+ // TypeScript / JavaScript compilers & runtimes
37
+ 'tsc': 'typescript',
38
+ 'ts-node': 'ts-node',
39
+ 'tsx': 'tsx',
40
+ 'tsup': 'tsup',
41
+ 'esbuild': 'esbuild',
42
+ 'swc': '@swc/cli',
43
+
44
+ // Test runners
45
+ 'jest': 'jest',
46
+ 'vitest': 'vitest',
47
+ 'mocha': 'mocha',
48
+ 'jasmine': 'jasmine',
49
+ 'ava': 'ava',
50
+ 'tap': 'tap',
51
+ 'c8': 'c8',
52
+ 'nyc': 'nyc',
53
+
54
+ // Linters & formatters
55
+ 'eslint': 'eslint',
56
+ 'prettier': 'prettier',
57
+ 'biome': '@biomejs/biome',
58
+ 'oxlint': 'oxlint',
59
+ 'tslint': 'tslint',
60
+ 'xo': 'xo',
61
+ 'standard': 'standard',
62
+
63
+ // Bundlers & dev servers
64
+ 'vite': 'vite',
65
+ 'webpack': 'webpack',
66
+ 'rollup': 'rollup',
67
+ 'parcel': 'parcel',
68
+ 'turbo': 'turbo',
69
+ 'nx': 'nx',
70
+
71
+ // Process managers & watchers
72
+ 'nodemon': 'nodemon',
73
+ 'pm2': 'pm2',
74
+ 'concurrently': 'concurrently',
75
+ 'cross-env': 'cross-env',
76
+ 'dotenv-cli': 'dotenv-cli',
77
+ 'env-cmd': 'env-cmd',
78
+
79
+ // Code generation & scaffolding
80
+ 'hygen': 'hygen',
81
+ 'plop': 'plop',
82
+ 'prisma': 'prisma',
83
+ 'drizzle-kit': 'drizzle-kit',
84
+ 'typeorm': 'typeorm',
85
+ 'sequelize': 'sequelize-cli',
86
+ 'knex': 'knex',
87
+ 'mikro-orm': '@mikro-orm/cli',
88
+
89
+ // Build & deployment tools
90
+ 'rimraf': 'rimraf',
91
+ 'copyfiles': 'copyfiles',
92
+ 'mkdirp': 'mkdirp',
93
+ 'shx': 'shx',
94
+ 'ncp': 'ncp',
95
+ 'cpx': 'cpx',
96
+ 'npm-run-all': 'npm-run-all',
97
+ 'run-s': 'npm-run-all',
98
+ 'run-p': 'npm-run-all',
99
+
100
+ // Documentation
101
+ 'typedoc': 'typedoc',
102
+ 'jsdoc': 'jsdoc',
103
+ 'storybook': 'storybook',
104
+ 'sb': 'storybook',
105
+
106
+ // Misc
107
+ 'husky': 'husky',
108
+ 'lint-staged': 'lint-staged',
109
+ 'commitlint': '@commitlint/cli',
110
+ 'release-it': 'release-it',
111
+ 'semantic-release': 'semantic-release',
112
+ 'changeset': '@changesets/cli',
113
+ 'changesets': '@changesets/cli',
114
+ 'np': 'np',
115
+ 'bumpp': 'bumpp',
116
+ };
117
+
118
+ // ============================================================
119
+ // EXTENDED DEV TOOLING ECOSYSTEM (never flagged as unused)
120
+ // ============================================================
121
+ const DEV_TOOLING_ECOSYSTEM = new Set([
122
+ // Linters & formatters
123
+ 'eslint', 'prettier', 'biome', '@biomejs/biome', 'oxlint', 'tslint', 'xo', 'standard',
124
+ // TypeScript
125
+ 'typescript', 'typescript-eslint', '@eslint/js', 'ts-node', 'tsx', 'tsup', 'esbuild', '@swc/cli',
126
+ // Test runners
127
+ 'jest', 'vitest', 'mocha', 'jasmine', 'ava', 'tap', 'c8', 'nyc',
128
+ // Bundlers
129
+ 'vite', 'webpack', 'rollup', 'parcel', 'turbo', 'nx',
130
+ // Process managers
131
+ 'nodemon', 'pm2', 'concurrently', 'cross-env', 'dotenv-cli', 'env-cmd',
132
+ // Build helpers
133
+ 'rimraf', 'copyfiles', 'mkdirp', 'shx', 'ncp', 'cpx', 'npm-run-all',
134
+ // Docs
135
+ 'typedoc', 'jsdoc', 'storybook',
136
+ // Release
137
+ 'husky', 'lint-staged', '@commitlint/cli', 'release-it', 'semantic-release', '@changesets/cli', 'np', 'bumpp',
138
+ // ORM CLI tools
139
+ 'prisma', 'drizzle-kit', 'typeorm', 'sequelize-cli', 'knex', '@mikro-orm/cli',
140
+ // Scaffolding
141
+ 'hygen', 'plop',
142
+ ]);
143
+
144
+ // ============================================================
145
+ // KNOWN PACKAGE ALIASES (package name → common import name)
146
+ // e.g. "lodash" is imported as "_", "express" as "app", etc.
147
+ // This helps avoid false positives in unused detection
148
+ // ============================================================
149
+ const PACKAGE_IMPORT_ALIASES = {
150
+ 'lodash': ['_', 'lodash'],
151
+ 'lodash-es': ['_', 'lodash'],
152
+ 'underscore': ['_'],
153
+ 'jquery': ['$', 'jQuery'],
154
+ 'moment': ['moment'],
155
+ 'dayjs': ['dayjs'],
156
+ 'date-fns': ['dateFns'],
157
+ 'ramda': ['R'],
158
+ 'rxjs': ['Rx'],
159
+ 'three': ['THREE'],
160
+ 'chart.js': ['Chart'],
161
+ 'socket.io': ['io', 'Server'],
162
+ 'socket.io-client': ['io'],
163
+ 'mongoose': ['mongoose'],
164
+ 'sequelize': ['Sequelize'],
165
+ 'typeorm': ['typeorm'],
166
+ 'prisma': ['prisma', 'PrismaClient'],
167
+ '@prisma/client': ['prisma', 'PrismaClient'],
168
+ 'knex': ['knex'],
169
+ 'redis': ['redis', 'createClient'],
170
+ 'ioredis': ['Redis'],
171
+ 'pg': ['Pool', 'Client', 'pg'],
172
+ 'mysql2': ['mysql', 'createConnection', 'createPool'],
173
+ 'sqlite3': ['sqlite3'],
174
+ 'express': ['app', 'express', 'router'],
175
+ 'fastify': ['fastify'],
176
+ 'koa': ['Koa', 'koa'],
177
+ 'hapi': ['Hapi'],
178
+ 'axios': ['axios'],
179
+ 'node-fetch': ['fetch'],
180
+ 'got': ['got'],
181
+ 'superagent': ['request'],
182
+ 'chalk': ['chalk'],
183
+ 'ora': ['ora'],
184
+ 'inquirer': ['inquirer'],
185
+ 'commander': ['program', 'Command'],
186
+ 'yargs': ['yargs'],
187
+ 'minimist': ['argv'],
188
+ 'dotenv': ['dotenv'],
189
+ 'winston': ['winston', 'logger'],
190
+ 'pino': ['pino', 'logger'],
191
+ 'morgan': ['morgan'],
192
+ 'helmet': ['helmet'],
193
+ 'cors': ['cors'],
194
+ 'compression': ['compression'],
195
+ 'body-parser': ['bodyParser'],
196
+ 'multer': ['multer', 'upload'],
197
+ 'passport': ['passport'],
198
+ 'jsonwebtoken': ['jwt'],
199
+ 'bcrypt': ['bcrypt'],
200
+ 'bcryptjs': ['bcrypt'],
201
+ 'crypto-js': ['CryptoJS'],
202
+ 'uuid': ['uuid', 'v4', 'uuidv4'],
203
+ 'nanoid': ['nanoid'],
204
+ 'zod': ['z', 'zod'],
205
+ 'joi': ['Joi'],
206
+ 'yup': ['yup'],
207
+ 'valibot': ['v'],
208
+ 'class-validator': ['IsEmail', 'IsString', 'IsNumber'],
209
+ 'react': ['React'],
210
+ 'react-dom': ['ReactDOM'],
211
+ 'vue': ['Vue', 'createApp'],
212
+ 'svelte': ['svelte'],
213
+ '@angular/core': ['Component', 'NgModule'],
214
+ 'next': ['next'],
215
+ 'nuxt': ['nuxt'],
216
+ };
31
217
 
32
218
  function getGitIdentity() {
33
219
  const identity = { name: "Developer", author: "Developer", repository: "" };
@@ -38,6 +224,10 @@ function getGitIdentity() {
38
224
  identity.name = name;
39
225
  identity.author = email ? `${name} <${email}>` : name;
40
226
  }
227
+ try {
228
+ const remoteUrl = execSync('git remote get-url origin', { encoding: 'utf8', stdio: 'pipe' }).trim();
229
+ identity.repository = remoteUrl.replace(/\.git$/, '');
230
+ } catch (e) {}
41
231
  } catch (e) {}
42
232
  return identity;
43
233
  }
@@ -47,11 +237,13 @@ function detectPackageManager(targetDir, stats = null) {
47
237
  if (fs.existsSync(path.join(targetDir, 'pnpm-lock.yaml'))) detectedLockfiles.push('pnpm-lock.yaml');
48
238
  if (fs.existsSync(path.join(targetDir, 'yarn.lock'))) detectedLockfiles.push('yarn.lock');
49
239
  if (fs.existsSync(path.join(targetDir, 'package-lock.json'))) detectedLockfiles.push('package-lock.json');
240
+ if (fs.existsSync(path.join(targetDir, 'bun.lockb')) || fs.existsSync(path.join(targetDir, 'bun.lock'))) detectedLockfiles.push('bun.lock');
50
241
 
51
242
  if (detectedLockfiles.length > 1 && stats) {
52
243
  stats.conflictingLockfiles = detectedLockfiles;
53
244
  }
54
245
 
246
+ if (detectedLockfiles.some(l => l.startsWith('bun'))) return 'bun';
55
247
  if (detectedLockfiles.includes('pnpm-lock.yaml')) return 'pnpm';
56
248
  if (detectedLockfiles.includes('yarn.lock')) return 'yarn';
57
249
  if (detectedLockfiles.includes('package-lock.json')) return 'npm';
@@ -83,6 +275,22 @@ function analyzeCodeStyle(content, stats) {
83
275
  if (REGEX_PATTERNS.syncFsCalls.test(content)) stats.quality.syncFsCount += (content.match(REGEX_PATTERNS.syncFsCalls) || []).length;
84
276
  }
85
277
 
278
+ function getBinariesFromPackageJson(packageJsonContent) {
279
+ const binaries = new Set();
280
+ if (packageJsonContent && packageJsonContent.scripts) {
281
+ for (const script of Object.values(packageJsonContent.scripts)) {
282
+ const commands = String(script).split(/\s*&&\s*|\s*;\s*|\s*\|\|\s*/);
283
+ for (const cmd of commands) {
284
+ const firstWord = cmd.trim().split(/\s+/)[0];
285
+ if (firstWord && !['npm', 'yarn', 'pnpm', 'bun', 'node', 'npx', 'bunx', 'echo', 'exit', 'cd', 'mkdir', 'rm', 'cp', 'mv', 'cat', 'grep', 'sed', 'awk', 'find', 'sh', 'bash', 'zsh'].includes(firstWord)) {
286
+ binaries.add(firstWord);
287
+ }
288
+ }
289
+ }
290
+ }
291
+ return Array.from(binaries);
292
+ }
293
+
86
294
  function cleanPackageName(importString) {
87
295
  if (!importString || /^[./~\\]/.test(importString)) return null;
88
296
  if (importString.startsWith('@')) return importString.split('/').slice(0, 2).join('/');
@@ -92,7 +300,7 @@ function cleanPackageName(importString) {
92
300
  function smartPrepend(originalCode, declarationBlock) {
93
301
  const lines = originalCode.split(/\r?\n/);
94
302
  let insertIdx = 0;
95
-
303
+
96
304
  while (insertIdx < lines.length) {
97
305
  const line = lines[insertIdx].trim();
98
306
  if (line.startsWith('#!') || line === '"use strict";' || line === "'use strict';" || line === '`use strict`;') {
@@ -103,7 +311,7 @@ function smartPrepend(originalCode, declarationBlock) {
103
311
  break;
104
312
  }
105
313
  }
106
-
314
+
107
315
  lines.splice(insertIdx, 0, declarationBlock);
108
316
  return lines.join('\n');
109
317
  }
@@ -111,16 +319,16 @@ function smartPrepend(originalCode, declarationBlock) {
111
319
  async function inspectNpmPackage(pkgName) {
112
320
  try {
113
321
  const response = await fetch(`https://registry.npmjs.org/${pkgName}/latest`, {
114
- headers: { 'User-Agent': 'pkg-scaffold-dx-client/1.1' },
322
+ headers: { 'User-Agent': 'pkg-scaffold-dx-client/2.0' },
115
323
  signal: AbortSignal.timeout(4000)
116
324
  });
117
325
  if (response.status === 200) {
118
326
  const data = await response.json();
119
- return { version: data.version, error: null };
327
+ return { version: data.version, deprecated: data.deprecated || null, error: null };
120
328
  }
121
- if (response.status === 404) return { version: null, error: 'NOT_FOUND' };
329
+ if (response.status === 404) return { version: null, deprecated: null, error: 'NOT_FOUND' };
122
330
  } catch (e) {
123
- return { version: 'latest', error: 'NETWORK_FAIL' };
331
+ return { version: 'latest', deprecated: null, error: 'NETWORK_FAIL' };
124
332
  }
125
333
  return null;
126
334
  }
@@ -128,7 +336,7 @@ async function inspectNpmPackage(pkgName) {
128
336
  async function fetchRemoteLicense(licenseKey) {
129
337
  try {
130
338
  const response = await fetch(`https://api.github.com/licenses/${licenseKey.toLowerCase()}`, {
131
- headers: { 'User-Agent': 'pkg-scaffold-dx-client/1.1' },
339
+ headers: { 'User-Agent': 'pkg-scaffold-dx-client/2.0' },
132
340
  signal: AbortSignal.timeout(5000)
133
341
  });
134
342
  if (response.status === 200) {
@@ -151,12 +359,12 @@ function buildAsciiTree(dir, prefix = '') {
151
359
  try {
152
360
  const files = fs.readdirSync(dir);
153
361
  const filtered = files.filter(f => !IGNORED_DIRS.has(f) && !f.startsWith('.'));
154
-
362
+
155
363
  filtered.forEach((file, index) => {
156
364
  const isLast = index === filtered.length - 1;
157
365
  const marker = isLast ? '└── ' : 'ā”œā”€ā”€ ';
158
366
  results.push(`${prefix}${marker}${file}`);
159
-
367
+
160
368
  const fullPath = path.join(dir, file);
161
369
  if (fs.statSync(fullPath).isDirectory()) {
162
370
  const newPrefix = prefix + (isLast ? ' ' : '│ ');
@@ -167,7 +375,295 @@ function buildAsciiTree(dir, prefix = '') {
167
375
  return results;
168
376
  }
169
377
 
170
- // --- High Performance AST Workspace Parsing Engine ---
378
+ // ============================================================
379
+ // IMPROVED IMPORT EXTRACTION: handles TypeScript generics,
380
+ // type-only imports, re-exports, and dynamic imports
381
+ // ============================================================
382
+ function extractImportsFromAST(ast, fileRawDeps, importedIdentifiers, importedLocations) {
383
+ walk.simple(ast, {
384
+ ImportDeclaration(node) {
385
+ const pkg = cleanPackageName(node.source.value);
386
+ if (pkg && !builtinModules.includes(pkg)) {
387
+ fileRawDeps.add(pkg);
388
+ if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
389
+ if (!importedLocations.has(pkg)) importedLocations.set(pkg, []);
390
+ importedLocations.get(pkg).push(node.loc?.start?.line ?? 0);
391
+
392
+ node.specifiers.forEach(spec => {
393
+ if (spec.type === 'ImportDefaultSpecifier' || spec.type === 'ImportNamespaceSpecifier') {
394
+ importedIdentifiers.get(pkg).add(spec.local.name);
395
+ } else if (spec.type === 'ImportSpecifier') {
396
+ importedIdentifiers.get(pkg).add(spec.local.name);
397
+ // Also track the imported name (before 'as') for side-effect detection
398
+ if (spec.imported && spec.imported.name !== spec.local.name) {
399
+ importedIdentifiers.get(pkg).add(spec.imported.name);
400
+ }
401
+ }
402
+ });
403
+
404
+ // Side-effect only import: import 'pkg' — always considered "used"
405
+ if (node.specifiers.length === 0) {
406
+ importedIdentifiers.get(pkg).add('__SIDE_EFFECT__');
407
+ }
408
+ }
409
+ },
410
+ VariableDeclarator(node) {
411
+ if (node.init && node.init.type === 'CallExpression' &&
412
+ node.init.callee.type === 'Identifier' && node.init.callee.name === 'require') {
413
+ const arg = node.init.arguments[0];
414
+ if (arg && arg.type === 'Literal' && typeof arg.value === 'string') {
415
+ const pkg = cleanPackageName(arg.value);
416
+ if (pkg && !builtinModules.includes(pkg)) {
417
+ fileRawDeps.add(pkg);
418
+ if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
419
+ if (!importedLocations.has(pkg)) importedLocations.set(pkg, []);
420
+ importedLocations.get(pkg).push(node.loc?.start?.line ?? 0);
421
+
422
+ const extractBindings = (idNode) => {
423
+ if (idNode.type === 'Identifier') {
424
+ importedIdentifiers.get(pkg).add(idNode.name);
425
+ } else if (idNode.type === 'ObjectPattern') {
426
+ idNode.properties.forEach(p => {
427
+ if (p.value && p.value.type === 'Identifier') importedIdentifiers.get(pkg).add(p.value.name);
428
+ if (p.key && p.key.type === 'Identifier') importedIdentifiers.get(pkg).add(p.key.name);
429
+ });
430
+ }
431
+ };
432
+ extractBindings(node.id);
433
+ }
434
+ }
435
+ }
436
+ },
437
+ ImportExpression(node) {
438
+ if (node.source.type === 'Literal' && typeof node.source.value === 'string') {
439
+ const pkg = cleanPackageName(node.source.value);
440
+ if (pkg && !builtinModules.includes(pkg)) {
441
+ fileRawDeps.add(pkg);
442
+ if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
443
+ importedIdentifiers.get(pkg).add('__DYNAMIC__');
444
+ }
445
+ }
446
+ },
447
+ ExportNamedDeclaration(node) {
448
+ if (node.source && node.source.type === 'Literal' && typeof node.source.value === 'string') {
449
+ const pkg = cleanPackageName(node.source.value);
450
+ if (pkg && !builtinModules.includes(pkg)) {
451
+ fileRawDeps.add(pkg);
452
+ if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
453
+ importedIdentifiers.get(pkg).add('__REEXPORT__');
454
+ }
455
+ }
456
+ },
457
+ ExportAllDeclaration(node) {
458
+ if (node.source && node.source.type === 'Literal' && typeof node.source.value === 'string') {
459
+ const pkg = cleanPackageName(node.source.value);
460
+ if (pkg && !builtinModules.includes(pkg)) {
461
+ fileRawDeps.add(pkg);
462
+ if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
463
+ importedIdentifiers.get(pkg).add('__REEXPORT__');
464
+ }
465
+ }
466
+ }
467
+ });
468
+ }
469
+
470
+ // ============================================================
471
+ // REGEX FALLBACK: handles TypeScript files that acorn can't parse
472
+ // ============================================================
473
+ function extractImportsFromText(codeLines, fileRawDeps, importedIdentifiers, importedLocations) {
474
+ codeLines.forEach((line, lineIdx) => {
475
+ const lineNum = lineIdx + 1;
476
+
477
+ // import type { ... } from '...' — type-only, mark as side-effect
478
+ const typeImportMatch = line.match(/\bimport\s+type\s+\{[^}]*\}\s+from\s+['"]([^'"]+)['"]/);
479
+ if (typeImportMatch) {
480
+ const pkg = cleanPackageName(typeImportMatch[1]);
481
+ if (pkg && !builtinModules.includes(pkg)) {
482
+ fileRawDeps.add(pkg);
483
+ if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
484
+ importedIdentifiers.get(pkg).add('__TYPE_ONLY__');
485
+ if (!importedLocations.has(pkg)) importedLocations.set(pkg, []);
486
+ importedLocations.get(pkg).push(lineNum);
487
+ }
488
+ return;
489
+ }
490
+
491
+ // import DefaultExport from '...'
492
+ // import * as Namespace from '...'
493
+ const esmDefaultMatch = line.match(/\bimport\s+(?:\*\s+as\s+)?([a-zA-Z0-9_$]+)\s+from\s+['"]([^'"]+)['"]/);
494
+ if (esmDefaultMatch) {
495
+ const id = esmDefaultMatch[1];
496
+ const pkg = cleanPackageName(esmDefaultMatch[2]);
497
+ if (pkg && !builtinModules.includes(pkg)) {
498
+ fileRawDeps.add(pkg);
499
+ if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
500
+ importedIdentifiers.get(pkg).add(id);
501
+ if (!importedLocations.has(pkg)) importedLocations.set(pkg, []);
502
+ importedLocations.get(pkg).push(lineNum);
503
+ }
504
+ return;
505
+ }
506
+
507
+ // import { named, exports } from '...'
508
+ const esmNamedMatch = line.match(/\bimport\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/);
509
+ if (esmNamedMatch) {
510
+ const pkg = cleanPackageName(esmNamedMatch[2]);
511
+ if (pkg && !builtinModules.includes(pkg)) {
512
+ if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
513
+ fileRawDeps.add(pkg);
514
+ esmNamedMatch[1].split(',').forEach(part => {
515
+ const chunk = part.trim();
516
+ if (!chunk) return;
517
+ const id = chunk.includes(' as ') ? chunk.split(' as ')[1].trim() : chunk;
518
+ importedIdentifiers.get(pkg).add(id);
519
+ // Also add the original name
520
+ if (chunk.includes(' as ')) importedIdentifiers.get(pkg).add(chunk.split(' as ')[0].trim());
521
+ });
522
+ if (!importedLocations.has(pkg)) importedLocations.set(pkg, []);
523
+ importedLocations.get(pkg).push(lineNum);
524
+ }
525
+ return;
526
+ }
527
+
528
+ // Side-effect only: import '...'
529
+ const sideEffectMatch = line.match(/\bimport\s+['"]([^'"]+)['"]/);
530
+ if (sideEffectMatch) {
531
+ const pkg = cleanPackageName(sideEffectMatch[1]);
532
+ if (pkg && !builtinModules.includes(pkg)) {
533
+ fileRawDeps.add(pkg);
534
+ if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
535
+ importedIdentifiers.get(pkg).add('__SIDE_EFFECT__');
536
+ if (!importedLocations.has(pkg)) importedLocations.set(pkg, []);
537
+ importedLocations.get(pkg).push(lineNum);
538
+ }
539
+ return;
540
+ }
541
+
542
+ // const x = require('...')
543
+ const cjsMatch = line.match(/\b(?:const|let|var)\s+([a-zA-Z0-9_$]+)\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/);
544
+ if (cjsMatch) {
545
+ const id = cjsMatch[1];
546
+ const pkg = cleanPackageName(cjsMatch[2]);
547
+ if (pkg && !builtinModules.includes(pkg)) {
548
+ fileRawDeps.add(pkg);
549
+ if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
550
+ importedIdentifiers.get(pkg).add(id);
551
+ if (!importedLocations.has(pkg)) importedLocations.set(pkg, []);
552
+ importedLocations.get(pkg).push(lineNum);
553
+ }
554
+ return;
555
+ }
556
+
557
+ // const { a, b } = require('...')
558
+ const cjsDestructMatch = line.match(/\b(?:const|let|var)\s*\{([^}]+)\}\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/);
559
+ if (cjsDestructMatch) {
560
+ const pkg = cleanPackageName(cjsDestructMatch[2]);
561
+ if (pkg && !builtinModules.includes(pkg)) {
562
+ if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
563
+ fileRawDeps.add(pkg);
564
+ cjsDestructMatch[1].split(',').forEach(part => {
565
+ const chunk = part.trim();
566
+ if (!chunk) return;
567
+ const id = chunk.includes(':') ? chunk.split(':')[1].trim() : chunk;
568
+ importedIdentifiers.get(pkg).add(id);
569
+ });
570
+ if (!importedLocations.has(pkg)) importedLocations.set(pkg, []);
571
+ importedLocations.get(pkg).push(lineNum);
572
+ }
573
+ return;
574
+ }
575
+
576
+ // Dynamic import: import('...')
577
+ const dynamicMatch = line.match(/\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/);
578
+ if (dynamicMatch) {
579
+ const pkg = cleanPackageName(dynamicMatch[1]);
580
+ if (pkg && !builtinModules.includes(pkg)) {
581
+ fileRawDeps.add(pkg);
582
+ if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
583
+ importedIdentifiers.get(pkg).add('__DYNAMIC__');
584
+ if (!importedLocations.has(pkg)) importedLocations.set(pkg, []);
585
+ importedLocations.get(pkg).push(lineNum);
586
+ }
587
+ }
588
+ });
589
+ }
590
+
591
+ // ============================================================
592
+ // USAGE ANALYSIS: determines if imported identifiers are
593
+ // actually referenced in the non-import code body
594
+ // ============================================================
595
+ function analyzeIdentifierUsage(pkg, identifiers, executionCode) {
596
+ // Always-used markers: side-effect, dynamic, re-export, type-only
597
+ const autoUsedMarkers = new Set(['__SIDE_EFFECT__', '__DYNAMIC__', '__REEXPORT__', '__TYPE_ONLY__']);
598
+ for (const id of identifiers) {
599
+ if (autoUsedMarkers.has(id)) return true;
600
+ }
601
+
602
+ // Check known aliases for this package
603
+ const knownAliases = PACKAGE_IMPORT_ALIASES[pkg] || [];
604
+
605
+ for (const identifier of identifiers) {
606
+ if (!identifier || identifier.startsWith('__')) continue;
607
+ // Escape special regex chars in identifier
608
+ const escaped = identifier.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
609
+ const usagePattern = new RegExp(`\\b${escaped}\\b`);
610
+ if (usagePattern.test(executionCode)) return true;
611
+ }
612
+
613
+ // Check if any known alias for this package appears in the code
614
+ for (const alias of knownAliases) {
615
+ const escaped = alias.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
616
+ const aliasPattern = new RegExp(`\\b${escaped}\\b`);
617
+ if (aliasPattern.test(executionCode)) return true;
618
+ }
619
+
620
+ return false;
621
+ }
622
+
623
+ // ============================================================
624
+ // GHOST DEPENDENCY DETECTOR
625
+ // Finds packages used in code but NOT declared in package.json
626
+ // This is the most critical error: will fail at runtime/deploy
627
+ // ============================================================
628
+ function detectGhostDependencies(allImportedPackages, declaredDeps, declaredDevDeps) {
629
+ const allDeclared = new Set([...declaredDeps, ...declaredDevDeps]);
630
+ const ghosts = new Set();
631
+
632
+ for (const pkg of allImportedPackages) {
633
+ if (!allDeclared.has(pkg) && !builtinModules.includes(pkg)) {
634
+ ghosts.add(pkg);
635
+ }
636
+ }
637
+ return ghosts;
638
+ }
639
+
640
+ // ============================================================
641
+ // ORPHANED DEPENDENCY DETECTOR
642
+ // Finds packages in package.json that are never imported anywhere
643
+ // in the codebase (candidates for removal)
644
+ // ============================================================
645
+ function detectOrphanedDependencies(declaredDeps, allImportedPackages, binariesUsed, devTooling) {
646
+ const orphans = new Set();
647
+
648
+ for (const dep of declaredDeps) {
649
+ // Skip dev tooling — they're used via CLI, not imports
650
+ if (devTooling.has(dep) || dep.startsWith('@types/')) continue;
651
+
652
+ // Check if it's used as a binary
653
+ const binaryPkg = Object.values(BINARY_TO_PACKAGE_MAP).find(p => p === dep);
654
+ if (binaryPkg && binariesUsed.has(dep)) continue;
655
+
656
+ // Check if it's imported anywhere
657
+ if (!allImportedPackages.has(dep)) {
658
+ orphans.add(dep);
659
+ }
660
+ }
661
+ return orphans;
662
+ }
663
+
664
+ // ============================================================
665
+ // HIGH PERFORMANCE AST WORKSPACE PARSING ENGINE
666
+ // ============================================================
171
667
  function scanWorkspace(dir, stats, rootNamespace) {
172
668
  const files = fs.readdirSync(dir);
173
669
 
@@ -176,21 +672,26 @@ function scanWorkspace(dir, stats, rootNamespace) {
176
672
  const stat = fs.statSync(fullPath);
177
673
 
178
674
  if (stat.isDirectory()) {
179
- if (!IGNORED_DIRS.has(file) && !file.startsWith('.')) scanWorkspace(fullPath, stats, rootNamespace);
675
+ if (!IGNORED_DIRS.has(file) && !file.startsWith('.')) {
676
+ scanWorkspace(fullPath, stats, rootNamespace);
677
+ }
180
678
  } else {
181
679
  const ext = path.extname(file);
182
-
183
- if (file === 'index.html' || file.startsWith('vite.config.')) stats.hasHtml = true;
680
+
681
+ if (file === 'index.html' || REGEX_PATTERNS.configFile.test(file)) stats.hasHtml = true;
184
682
  if (REGEX_PATTERNS.testFile.test(file)) stats.hasTests = true;
185
683
  if (ext === '.ts' || ext === '.tsx') stats.tsFiles++;
186
684
  if (ext === '.js' || ext === '.jsx' || ext === '.mjs') stats.jsFiles++;
187
685
 
188
686
  if (VALID_EXTENSIONS.has(ext)) {
687
+ stats.scannedFiles++;
189
688
  const rawContent = readFileSyncNormalized(fullPath);
190
- const content = rawContent.replace(/[^\x09\x0A\x0D\x20-\x7E]/g, '');
191
-
689
+ // Strip non-printable chars but keep Unicode letters (important for identifiers)
690
+ const content = rawContent.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '');
691
+
192
692
  const codeLines = content.split(/\r?\n/);
193
- const importedIdentifiers = new Map();
693
+ const importedIdentifiers = new Map();
694
+ const importedLocations = new Map();
194
695
  const fileRawDeps = new Set();
195
696
 
196
697
  analyzeCodeStyle(content, stats);
@@ -206,7 +707,7 @@ function scanWorkspace(dir, stats, rootNamespace) {
206
707
  stats.envVars.add(envVarName);
207
708
  }
208
709
 
209
- // --- Global Regex Environmental Extraction Module ---
710
+ // Global Regex Environmental Extraction Module
210
711
  let fileHasEnv = false;
211
712
  let envMatch;
212
713
  REGEX_PATTERNS.env.lastIndex = 0;
@@ -214,164 +715,51 @@ function scanWorkspace(dir, stats, rootNamespace) {
214
715
  stats.envVars.add(envMatch[1]);
215
716
  fileHasEnv = true;
216
717
  }
217
- if (fileHasEnv) {
218
- stats.filesWithEnvVars.add(fullPath);
219
- }
718
+ if (fileHasEnv) stats.filesWithEnvVars.add(fullPath);
220
719
 
221
720
  if (content.includes('import ') || content.includes('export ')) stats.usesEsm = true;
222
721
 
223
- // --- Abstract Syntax Tree Engine Execution Block ---
722
+ // --- AST Parsing (preferred) ---
224
723
  let ast = null;
225
724
  try {
226
- ast = acorn.parse(content, { ecmaVersion: 'latest', sourceType: 'module', allowHashBang: true });
725
+ ast = acorn.parse(content, { ecmaVersion: 'latest', sourceType: 'module', allowHashBang: true, locations: true });
227
726
  } catch (e) {
228
727
  try {
229
- ast = acorn.parse(content, { ecmaVersion: 'latest', sourceType: 'script', allowHashBang: true });
728
+ ast = acorn.parse(content, { ecmaVersion: 'latest', sourceType: 'script', allowHashBang: true, locations: true });
230
729
  } catch (err) {}
231
730
  }
232
731
 
233
732
  if (ast) {
234
- walk.simple(ast, {
235
- ImportDeclaration(node) {
236
- const pkg = cleanPackageName(node.source.value);
237
- if (pkg && !builtinModules.includes(pkg)) {
238
- fileRawDeps.add(pkg);
239
- if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
240
- node.specifiers.forEach(spec => importedIdentifiers.get(pkg).add(spec.local.name));
241
- }
242
- },
243
- VariableDeclarator(node) {
244
- if (node.init && node.init.type === 'CallExpression' &&
245
- node.init.callee.type === 'Identifier' && node.init.callee.name === 'require') {
246
- const arg = node.init.arguments[0];
247
- if (arg && arg.type === 'Literal' && typeof arg.value === 'string') {
248
- const pkg = cleanPackageName(arg.value);
249
- if (pkg && !builtinModules.includes(pkg)) {
250
- fileRawDeps.add(pkg);
251
- if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
252
-
253
- const extractBindings = (idNode) => {
254
- if (idNode.type === 'Identifier') {
255
- importedIdentifiers.get(pkg).add(idNode.name);
256
- } else if (idNode.type === 'ObjectPattern') {
257
- idNode.properties.forEach(p => {
258
- if (p.value && p.value.type === 'Identifier') importedIdentifiers.get(pkg).add(p.value.name);
259
- });
260
- }
261
- };
262
- extractBindings(node.id);
263
- }
264
- }
265
- }
266
- },
267
- ImportExpression(node) {
268
- if (node.source.type === 'Literal' && typeof node.source.value === 'string') {
269
- const pkg = cleanPackageName(node.source.value);
270
- if (pkg && !builtinModules.includes(pkg)) fileRawDeps.add(pkg);
271
- }
272
- },
273
- ExportNamedDeclaration(node) {
274
- if (node.source && node.source.type === 'Literal' && typeof node.source.value === 'string') {
275
- const pkg = cleanPackageName(node.source.value);
276
- if (pkg && !builtinModules.includes(pkg)) fileRawDeps.add(pkg);
277
- }
278
- },
279
- ExportAllDeclaration(node) {
280
- if (node.source && node.source.type === 'Literal' && typeof node.source.value === 'string') {
281
- const pkg = cleanPackageName(node.source.value);
282
- if (pkg && !builtinModules.includes(pkg)) fileRawDeps.add(pkg);
283
- }
284
- }
285
- });
733
+ extractImportsFromAST(ast, fileRawDeps, importedIdentifiers, importedLocations);
286
734
  } else {
287
- // --- Text-Isolated Fallback Track (TypeScript/TSX Support Matrix) ---
288
- for (const line of codeLines) {
289
- let esmMatch = line.match(/\bimport\s+(?:\*+\s+as\s+)?([a-zA-Z0-9_]+)\s+from\s+['"]([^'"]+)['"]/);
290
- if (esmMatch) {
291
- const id = esmMatch[1];
292
- const pkg = cleanPackageName(esmMatch[2]);
293
- if (pkg && !builtinModules.includes(pkg)) {
294
- fileRawDeps.add(pkg);
295
- if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
296
- importedIdentifiers.get(pkg).add(id);
297
- }
298
- continue;
299
- }
300
-
301
- let esmNamedMatch = line.match(/\bimport\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/);
302
- if (esmNamedMatch) {
303
- const pkg = cleanPackageName(esmNamedMatch[2]);
304
- if (pkg && !builtinModules.includes(pkg)) {
305
- if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
306
- fileRawDeps.add(pkg);
307
- esmNamedMatch[1].split(',').forEach(part => {
308
- const chunk = part.trim();
309
- if (!chunk) return;
310
- const id = chunk.includes(' as ') ? chunk.split(' as ')[1].trim() : chunk;
311
- importedIdentifiers.get(pkg).add(id);
312
- });
313
- }
314
- continue;
315
- }
316
-
317
- let cjsMatch = line.match(/\b(?:const|let|var)\s+([a-zA-Z0-9_]+)\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/);
318
- if (cjsMatch) {
319
- const id = cjsMatch[1];
320
- const pkg = cleanPackageName(cjsMatch[2]);
321
- if (pkg && !builtinModules.includes(pkg)) {
322
- fileRawDeps.add(pkg);
323
- if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
324
- importedIdentifiers.get(pkg).add(id);
325
- }
326
- continue;
327
- }
328
-
329
- let cjsDestructMatch = line.match(/\b(?:const|let|var)\s*\{([^}]+)\}\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/);
330
- if (cjsDestructMatch) {
331
- const pkg = cleanPackageName(cjsDestructMatch[2]);
332
- if (pkg && !builtinModules.includes(pkg)) {
333
- if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
334
- fileRawDeps.add(pkg);
335
- cjsDestructMatch[1].split(',').forEach(part => {
336
- const chunk = part.trim();
337
- if (!chunk) return;
338
- const id = chunk.includes(':') ? chunk.split(':')[1].trim() : chunk;
339
- importedIdentifiers.get(pkg).add(id);
340
- });
341
- }
342
- continue;
343
- }
344
- }
735
+ // Regex fallback for TypeScript generics / decorators / etc.
736
+ extractImportsFromText(codeLines, fileRawDeps, importedIdentifiers, importedLocations);
345
737
  }
346
738
 
739
+ // Register all deps found in this file
740
+ fileRawDeps.forEach(dep => stats.allImportedPackages.add(dep));
347
741
  fileRawDeps.forEach(dep => stats.rawDeps.add(dep));
348
742
 
349
- const functionalExecutionCodeOnly = codeLines
350
- .filter(l => !/\bimport\b/.test(l) && !/\brequire\s*\(/.test(l))
743
+ // --- Per-file usage analysis ---
744
+ // Strip import/require lines to get only execution code
745
+ const executionCode = codeLines
746
+ .filter(l => {
747
+ const t = l.trim();
748
+ return !t.startsWith('import ') && !/\brequire\s*\(/.test(t);
749
+ })
351
750
  .join('\n');
352
751
 
353
752
  for (const [pkg, identifiers] of importedIdentifiers.entries()) {
354
- let tokenReferenced = false;
355
- for (const identifier of identifiers) {
356
- const usagePattern = new RegExp(`\\b${identifier}\\b`);
357
- if (usagePattern.test(functionalExecutionCodeOnly)) {
358
- tokenReferenced = true;
359
- break;
753
+ const isUsed = analyzeIdentifierUsage(pkg, identifiers, executionCode);
754
+ if (!isUsed && identifiers.size > 0) {
755
+ if (!stats.unusedImportsPerFile.has(fullPath)) {
756
+ stats.unusedImportsPerFile.set(fullPath, new Map());
360
757
  }
361
- }
362
- if (!tokenReferenced && identifiers.size > 0) {
758
+ const lines = importedLocations.get(pkg) || [];
759
+ stats.unusedImportsPerFile.get(fullPath).set(pkg, lines);
363
760
  stats.unusedDepsInCode.add(pkg);
364
761
  }
365
762
  }
366
-
367
- COMMON_EXTERNAL_TOKENS.forEach(token => {
368
- const tokenPattern = new RegExp(`\\b${token}\\b`);
369
- if (tokenPattern.test(functionalExecutionCodeOnly) && !importedIdentifiers.has(token)) {
370
- stats.rawDeps.add(token);
371
- if (!stats.phantomInjections.has(fullPath)) stats.phantomInjections.set(fullPath, new Set());
372
- stats.phantomInjections.get(fullPath).add(token);
373
- }
374
- });
375
763
  }
376
764
  }
377
765
  }
@@ -381,12 +769,21 @@ async function main() {
381
769
  const targetDir = process.cwd();
382
770
  const folderName = path.basename(targetDir);
383
771
  const gitInfo = getGitIdentity();
384
-
772
+
385
773
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
386
-
774
+ let rlClosed = false;
775
+ rl.on('close', () => { rlClosed = true; });
776
+ const safeQuestion = async (prompt) => {
777
+ if (rlClosed || !process.stdin.readable) return '';
778
+ try { return await safeQuestion(prompt); } catch { return ''; }
779
+ };
780
+
387
781
  const stats = {
388
782
  tsFiles: 0, jsFiles: 0, usesEsm: false, hasHtml: false, hasTests: false,
389
- rawDeps: new Set(), envVars: new Set(),
783
+ scannedFiles: 0,
784
+ rawDeps: new Set(),
785
+ allImportedPackages: new Set(),
786
+ envVars: new Set(),
390
787
  style: { semiCount: 0, noSemiCount: 0, tabCount: 0, space2Count: 0, space4Count: 0 },
391
788
  quality: { varCount: 0, hasEval: false, syncFsCount: 0 },
392
789
  phantomInjections: new Map(),
@@ -394,20 +791,28 @@ async function main() {
394
791
  subWorkspaces: [],
395
792
  conflictingLockfiles: [],
396
793
  unusedDepsInCode: new Set(),
794
+ unusedImportsPerFile: new Map(),
397
795
  filesWithEnvVars: new Set(),
398
796
  injectDotenvEngine: false,
399
- bootstrapEslintSuite: false
797
+ bootstrapEslintSuite: false,
798
+ // New tracking structures
799
+ ghostDependencies: new Set(), // used in code, missing from package.json
800
+ orphanedDependencies: new Set(), // in package.json, never imported
801
+ deprecatedPackages: new Map(), // pkg -> deprecation message
400
802
  };
401
803
 
402
804
  const activePkgManager = detectPackageManager(targetDir, stats);
403
805
  const pkgPath = path.join(targetDir, 'package.json');
404
806
  let preExistingLicense = null;
405
807
  let preExistingDeps = [];
808
+ let preExistingDevDeps = [];
809
+ let existingPackageJson = null;
406
810
 
407
- console.log(`\n===================================================================`);
408
- console.log(`šŸš€ pkg-scaffold v1.1: Deep Intelligence Workspace Diagnostic Run`);
409
- console.log(`===================================================================\n`);
811
+ console.log(`\n${'═'.repeat(67)}`);
812
+ console.log(`šŸš€ pkg-scaffold v2.0: Advanced Dependency Intelligence Engine`);
813
+ console.log(`${'═'.repeat(67)}\n`);
410
814
 
815
+ // --- Sub-workspace detection ---
411
816
  const topLevelItems = fs.readdirSync(targetDir);
412
817
  const potentialSubModules = [];
413
818
  for (const item of topLevelItems) {
@@ -415,45 +820,54 @@ async function main() {
415
820
  if (!IGNORED_DIRS.has(item) && !item.startsWith('.') && fs.statSync(fullPath).isDirectory()) {
416
821
  let containsSourceCode = false;
417
822
  const examineDirectory = (d) => {
418
- const subEntries = fs.readdirSync(d);
419
- for (const entry of subEntries) {
420
- const entryPath = path.join(d, entry);
421
- if (fs.statSync(entryPath).isDirectory()) {
422
- if (!IGNORED_DIRS.has(entry) && !entry.startsWith('.')) examineDirectory(entryPath);
423
- } else if (VALID_EXTENSIONS.has(path.extname(entry))) {
424
- containsSourceCode = true;
425
- break;
823
+ try {
824
+ const subEntries = fs.readdirSync(d);
825
+ for (const entry of subEntries) {
826
+ const entryPath = path.join(d, entry);
827
+ if (fs.statSync(entryPath).isDirectory()) {
828
+ if (!IGNORED_DIRS.has(entry) && !entry.startsWith('.')) examineDirectory(entryPath);
829
+ } else if (VALID_EXTENSIONS.has(path.extname(entry))) {
830
+ containsSourceCode = true;
831
+ }
426
832
  }
427
- }
833
+ } catch {}
428
834
  };
429
- try { examineDirectory(fullPath); } catch {}
835
+ examineDirectory(fullPath);
430
836
  if (containsSourceCode) potentialSubModules.push(item);
431
837
  }
432
838
  }
433
- if (potentialSubModules.length > 1) {
434
- stats.subWorkspaces = potentialSubModules;
435
- }
839
+ if (potentialSubModules.length > 1) stats.subWorkspaces = potentialSubModules;
436
840
 
841
+ // --- Existing package.json analysis ---
437
842
  if (fs.existsSync(pkgPath)) {
438
843
  console.log(`āš ļø An existing package.json was found in this working directory.`);
439
844
  console.log(`šŸ“” Analyzing existing installation arrays for invalid metrics...`);
440
845
  try {
441
- const existingData = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
442
- if (existingData.license && typeof existingData.license === 'string' && existingData.license.toLowerCase() !== 'none') {
443
- preExistingLicense = existingData.license;
846
+ existingPackageJson = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
847
+ if (existingPackageJson.license && typeof existingPackageJson.license === 'string' && existingPackageJson.license.toLowerCase() !== 'none') {
848
+ preExistingLicense = existingPackageJson.license;
444
849
  }
445
- if (existingData.dependencies) preExistingDeps.push(...Object.keys(existingData.dependencies));
446
- if (existingData.devDependencies) preExistingDeps.push(...Object.keys(existingData.devDependencies));
850
+ if (existingPackageJson.dependencies) preExistingDeps = Object.keys(existingPackageJson.dependencies);
851
+ if (existingPackageJson.devDependencies) preExistingDevDeps = Object.keys(existingPackageJson.devDependencies);
447
852
 
448
- const combinedDeps = Object.keys({ ...existingData.dependencies, ...existingData.devDependencies });
853
+ const combinedDeps = [...preExistingDeps, ...preExistingDevDeps];
449
854
  let brokenEcosystem = combinedDeps.length === 0;
450
- for (const dep of combinedDeps) {
451
- const check = await inspectNpmPackage(dep);
452
- if (check && check.error === 'NOT_FOUND') {
453
- brokenEcosystem = true;
454
- console.log(` āŒ Identified non-existent package on registry tracks: "${dep}"`);
855
+
856
+ // Check for non-existent AND deprecated packages
857
+ if (combinedDeps.length > 0) {
858
+ console.log(` šŸ” Validating ${combinedDeps.length} declared package(s) against npm registry...`);
859
+ for (const dep of combinedDeps) {
860
+ const check = await inspectNpmPackage(dep);
861
+ if (check && check.error === 'NOT_FOUND') {
862
+ brokenEcosystem = true;
863
+ console.log(` āŒ Non-existent package on registry: "${dep}"`);
864
+ } else if (check && check.deprecated) {
865
+ stats.deprecatedPackages.set(dep, check.deprecated);
866
+ console.log(` āš ļø Deprecated package detected: "${dep}" — ${check.deprecated}`);
867
+ }
455
868
  }
456
869
  }
870
+
457
871
  if (brokenEcosystem) {
458
872
  console.log(`\nšŸ›‘ CRITICAL COMPLIANCE BREAK: Your current package.json is empty or contains non-existent packages.`);
459
873
  console.log(`šŸ‘‰ Action Required: Please remove or backup the existing 'package.json' from this folder.\n`);
@@ -467,54 +881,255 @@ async function main() {
467
881
  }
468
882
  }
469
883
 
884
+ // --- Workspace scan ---
885
+ console.log(`\nšŸ”¬ Scanning workspace source files...`);
470
886
  scanWorkspace(targetDir, stats, folderName);
887
+ console.log(` āœ… Scanned ${stats.scannedFiles} source file(s) | TS: ${stats.tsFiles} | JS: ${stats.jsFiles}`);
888
+
889
+ // --- Binary-to-package resolution ---
890
+ const binariesInScripts = existingPackageJson ? getBinariesFromPackageJson(existingPackageJson) : [];
891
+ const resolvedBinaryPackages = new Set();
892
+ for (const binary of binariesInScripts) {
893
+ const pkgName = BINARY_TO_PACKAGE_MAP[binary] || binary;
894
+ resolvedBinaryPackages.add(pkgName);
895
+ stats.rawDeps.add(pkgName);
896
+ stats.allImportedPackages.add(pkgName); // treat as "used"
897
+ }
898
+
899
+ // ============================================================
900
+ // GHOST DEPENDENCY ANALYSIS
901
+ // Packages imported in code but missing from package.json
902
+ // ============================================================
903
+ if (preExistingDeps.length > 0 || preExistingDevDeps.length > 0) {
904
+ stats.ghostDependencies = detectGhostDependencies(
905
+ stats.allImportedPackages,
906
+ preExistingDeps,
907
+ preExistingDevDeps
908
+ );
909
+ // Remove dev tooling from ghost list (they may be globally installed)
910
+ for (const dep of stats.ghostDependencies) {
911
+ if (DEV_TOOLING_ECOSYSTEM.has(dep) || dep.startsWith('@types/')) {
912
+ stats.ghostDependencies.delete(dep);
913
+ }
914
+ }
915
+ }
471
916
 
472
- // --- Unused Dependency Filtration Matrix ---
473
- const allDiscoveredUnused = new Set([...stats.unusedDepsInCode]);
917
+ // ============================================================
918
+ // ORPHANED DEPENDENCY ANALYSIS
919
+ // Packages in package.json that are never imported
920
+ // ============================================================
474
921
  if (preExistingDeps.length > 0) {
475
- preExistingDeps.forEach(dep => { if (!stats.rawDeps.has(dep)) allDiscoveredUnused.add(dep); });
922
+ stats.orphanedDependencies = detectOrphanedDependencies(
923
+ preExistingDeps,
924
+ stats.allImportedPackages,
925
+ resolvedBinaryPackages,
926
+ DEV_TOOLING_ECOSYSTEM
927
+ );
928
+ }
929
+
930
+ // ============================================================
931
+ // UNUSED IMPORTS ANALYSIS (cross-file aggregation)
932
+ // A package is only truly "unused" if it's never used in ANY file
933
+ // ============================================================
934
+ // Build a set of packages that ARE used in at least one file
935
+ const usedInAtLeastOneFile = new Set();
936
+ for (const [, fileImports] of stats.unusedImportsPerFile.entries()) {
937
+ // If a package appears in unusedImportsPerFile for this file,
938
+ // it might still be used in another file — check allImportedPackages
939
+ }
940
+ // Refine: unusedDepsInCode should only include packages that are
941
+ // imported but never referenced across the entire codebase
942
+ const trulyUnusedImports = new Set();
943
+ for (const pkg of stats.unusedDepsInCode) {
944
+ // If the package is used (identifier found) in ANY file, remove from unused
945
+ let foundUsedElsewhere = false;
946
+ for (const [filePath, fileUnused] of stats.unusedImportsPerFile.entries()) {
947
+ if (!fileUnused.has(pkg)) {
948
+ // This file imports pkg and DOES use it
949
+ if (stats.allImportedPackages.has(pkg)) {
950
+ foundUsedElsewhere = true;
951
+ break;
952
+ }
953
+ }
954
+ }
955
+ if (!foundUsedElsewhere) trulyUnusedImports.add(pkg);
956
+ }
957
+
958
+ // ============================================================
959
+ // DISPLAY: GHOST DEPENDENCIES (critical — will break at runtime)
960
+ // ============================================================
961
+ if (stats.ghostDependencies.size > 0) {
962
+ console.log(`\n${'─'.repeat(67)}`);
963
+ console.log(`🚨 GHOST DEPENDENCIES DETECTED (CRITICAL — Runtime/Deploy will FAIL)`);
964
+ console.log(`${'─'.repeat(67)}`);
965
+ console.log(` These packages are USED in your code but NOT listed in package.json.`);
966
+ console.log(` They may work locally (if globally installed) but WILL FAIL in CI/CD.\n`);
967
+ for (const pkg of stats.ghostDependencies) {
968
+ console.log(` āŒ \x1b[31m"${pkg}"\x1b[0m — imported in code, missing from package.json`);
969
+ }
970
+ console.log(`${'─'.repeat(67)}`);
971
+ const addGhosts = await safeQuestion(`ā“ Add these missing packages to package.json automatically? (Y/n): `);
972
+ if (addGhosts.trim().toLowerCase() !== 'n' && addGhosts.trim().toLowerCase() !== 'no') {
973
+ for (const pkg of stats.ghostDependencies) stats.rawDeps.add(pkg);
974
+ console.log(` āœ… Ghost dependencies queued for package.json registration.`);
975
+ }
976
+ }
977
+
978
+ // ============================================================
979
+ // DISPLAY: ORPHANED DEPENDENCIES (in package.json, never used)
980
+ // ============================================================
981
+ if (stats.orphanedDependencies.size > 0) {
982
+ console.log(`\n${'─'.repeat(67)}`);
983
+ console.log(`šŸ“¦ ORPHANED DEPENDENCIES DETECTED (in package.json, never imported)`);
984
+ console.log(`${'─'.repeat(67)}`);
985
+ console.log(` These packages are declared in package.json but never imported`);
986
+ console.log(` anywhere in your source code. Safe to remove.\n`);
987
+ for (const pkg of stats.orphanedDependencies) {
988
+ console.log(` šŸ—‘ļø \x1b[33m"${pkg}"\x1b[0m — declared but never imported`);
989
+ }
990
+ console.log(`${'─'.repeat(67)}`);
991
+ const pruneOrphans = await safeQuestion(`ā“ Remove these orphaned packages from package.json? (y/N): `);
992
+ if (pruneOrphans.trim().toLowerCase() === 'y' || pruneOrphans.trim().toLowerCase() === 'yes') {
993
+ if (existingPackageJson) {
994
+ for (const pkg of stats.orphanedDependencies) {
995
+ delete existingPackageJson.dependencies?.[pkg];
996
+ }
997
+ fs.writeFileSync(pkgPath, JSON.stringify(existingPackageJson, null, 2));
998
+ console.log(` šŸ—‘ļø Orphaned dependencies removed from package.json.`);
999
+ }
1000
+ }
476
1001
  }
477
1002
 
478
- const devToolingEcosystem = new Set([
479
- 'eslint', 'prettier', 'typescript', 'typescript-eslint', '@eslint/js',
480
- 'nodemon', 'ts-node', 'tsup', 'vite', 'vitest', 'jest'
481
- ]);
1003
+ // ============================================================
1004
+ // DISPLAY: UNUSED IMPORTS (imported but never referenced in code)
1005
+ // ============================================================
1006
+ const allDiscoveredUnused = new Set([...trulyUnusedImports]);
1007
+ // Also add packages in package.json not found in code at all
1008
+ if (preExistingDeps.length > 0) {
1009
+ preExistingDeps.forEach(dep => {
1010
+ if (!stats.rawDeps.has(dep) && !DEV_TOOLING_ECOSYSTEM.has(dep) && !dep.startsWith('@types/')) {
1011
+ allDiscoveredUnused.add(dep);
1012
+ }
1013
+ });
1014
+ }
1015
+ // Remove dev tooling from unused list
482
1016
  for (const dep of allDiscoveredUnused) {
483
- if (devToolingEcosystem.has(dep) || dep.startsWith('@types/')) {
1017
+ if (DEV_TOOLING_ECOSYSTEM.has(dep) || dep.startsWith('@types/')) {
484
1018
  allDiscoveredUnused.delete(dep);
485
1019
  }
486
1020
  }
487
1021
 
488
1022
  if (allDiscoveredUnused.size > 0) {
489
- console.log(`\nšŸ“¦ UNUSED WORKSPACE DEPENDENCIES DETECTED`);
490
- console.log(`───────────────────────────────────────────────────────────────────`);
491
- console.log(` The following modules are imported or installed but never invoked inside executable code paths:`);
492
- console.log(` ${Array.from(allDiscoveredUnused).map(d => `\x1b[33m"${d}"\x1b[0m`).join(', ')}`);
493
- console.log(`───────────────────────────────────────────────────────────────────`);
494
-
495
- const pruneChoice = await rl.question(`ā“ Exclude these unused dependencies from your package.json setup? (y/N): `);
1023
+ console.log(`\n${'─'.repeat(67)}`);
1024
+ console.log(`āš ļø UNUSED IMPORTS DETECTED (imported but never referenced in code)`);
1025
+ console.log(`${'─'.repeat(67)}`);
1026
+ console.log(` These modules are imported but their identifiers are never used`);
1027
+ console.log(` in executable code paths.\n`);
1028
+
1029
+ for (const dep of allDiscoveredUnused) {
1030
+ // Show which files have this unused import
1031
+ const filesWithUnused = [];
1032
+ for (const [filePath, fileUnused] of stats.unusedImportsPerFile.entries()) {
1033
+ if (fileUnused.has(dep)) {
1034
+ const lines = fileUnused.get(dep);
1035
+ const lineStr = lines.length > 0 ? `:${lines[0]}` : '';
1036
+ filesWithUnused.push(`${path.relative(targetDir, filePath)}${lineStr}`);
1037
+ }
1038
+ }
1039
+ if (filesWithUnused.length > 0) {
1040
+ console.log(` ⚔ \x1b[33m"${dep}"\x1b[0m`);
1041
+ filesWithUnused.forEach(f => console.log(` └─ ${f}`));
1042
+ } else {
1043
+ console.log(` ⚔ \x1b[33m"${dep}"\x1b[0m`);
1044
+ }
1045
+ }
1046
+ console.log(`${'─'.repeat(67)}`);
1047
+
1048
+ const pruneChoice = await safeQuestion(`ā“ Exclude these unused imports from your package.json setup? (y/N): `);
496
1049
  if (pruneChoice.trim().toLowerCase() === 'y' || pruneChoice.trim().toLowerCase() === 'yes') {
497
1050
  for (const deadDep of allDiscoveredUnused) stats.rawDeps.delete(deadDep);
498
- console.log(` šŸ—‘ļø Pruned unused dependencies from your configuration blueprint.`);
1051
+ console.log(` šŸ—‘ļø Pruned unused imports from configuration blueprint.`);
1052
+ }
1053
+ }
1054
+
1055
+ // ============================================================
1056
+ // DISPLAY: DEPRECATED PACKAGES
1057
+ // ============================================================
1058
+ if (stats.deprecatedPackages.size > 0) {
1059
+ console.log(`\n${'─'.repeat(67)}`);
1060
+ console.log(`āš ļø DEPRECATED PACKAGES DETECTED`);
1061
+ console.log(`${'─'.repeat(67)}`);
1062
+ for (const [pkg, msg] of stats.deprecatedPackages.entries()) {
1063
+ console.log(` šŸ“› \x1b[33m"${pkg}"\x1b[0m — ${msg}`);
1064
+ }
1065
+ console.log(`${'─'.repeat(67)}`);
1066
+ }
1067
+
1068
+ // ============================================================
1069
+ // PHANTOM INJECTION DETECTION
1070
+ // Packages used in code (by identifier) but never imported
1071
+ // ============================================================
1072
+ // Build phantom detection from ALL declared packages
1073
+ const allDeclaredForPhantom = new Set([...preExistingDeps, ...preExistingDevDeps]);
1074
+ for (const [filePath] of stats.unusedImportsPerFile.entries()) {
1075
+ // Already handled above
1076
+ }
1077
+
1078
+ // Scan for identifiers used without import (using declared package names as hints)
1079
+ const phantomScanContent = new Map();
1080
+ function collectExecutionContent(dir) {
1081
+ try {
1082
+ for (const file of fs.readdirSync(dir)) {
1083
+ const fullPath = path.join(dir, file);
1084
+ const stat = fs.statSync(fullPath);
1085
+ if (stat.isDirectory() && !IGNORED_DIRS.has(file) && !file.startsWith('.')) {
1086
+ collectExecutionContent(fullPath);
1087
+ } else if (VALID_EXTENSIONS.has(path.extname(file))) {
1088
+ try {
1089
+ const content = readFileSyncNormalized(fullPath);
1090
+ const execCode = content.split(/\r?\n/)
1091
+ .filter(l => {
1092
+ const t = l.trim();
1093
+ return !t.startsWith('import ') && !/\brequire\s*\(/.test(t);
1094
+ })
1095
+ .join('\n');
1096
+ phantomScanContent.set(fullPath, execCode);
1097
+ } catch {}
1098
+ }
1099
+ }
1100
+ } catch {}
1101
+ }
1102
+ collectExecutionContent(targetDir);
1103
+
1104
+ for (const [filePath, execCode] of phantomScanContent.entries()) {
1105
+ for (const token of allDeclaredForPhantom) {
1106
+ const escaped = token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1107
+ const tokenPattern = new RegExp(`\\b${escaped}\\b`);
1108
+ if (tokenPattern.test(execCode) && !stats.allImportedPackages.has(token)) {
1109
+ stats.rawDeps.add(token);
1110
+ if (!stats.phantomInjections.has(filePath)) stats.phantomInjections.set(filePath, new Set());
1111
+ stats.phantomInjections.get(filePath).add(token);
1112
+ }
499
1113
  }
500
1114
  }
501
1115
 
502
1116
  const isTypeScript = stats.tsFiles > stats.jsFiles;
503
- const isFrontendWeb = stats.hasHtml || stats.rawDeps.has('react') || stats.rawDeps.has('vue') || stats.rawDeps.has('vite');
1117
+ const isFrontendWeb = stats.hasHtml || stats.rawDeps.has('react') || stats.rawDeps.has('vue') || stats.rawDeps.has('vite') || stats.rawDeps.has('svelte') || stats.rawDeps.has('next') || stats.rawDeps.has('nuxt');
504
1118
 
1119
+ // --- dotenv suggestion ---
505
1120
  if (stats.envVars.size > 0 && !stats.rawDeps.has('dotenv') && !isFrontendWeb) {
506
1121
  console.log(`\nšŸ“” CONFIGURATION COMPLIANCE GAP: UNMANAGED ENVIRONMENT VARIABLES`);
507
- console.log(`───────────────────────────────────────────────────────────────────`);
1122
+ console.log(`${'─'.repeat(67)}`);
508
1123
  console.log(` Workspace utilizes 'process.env' variables but 'dotenv' is missing.`);
509
- console.log(`───────────────────────────────────────────────────────────────────`);
510
- const choiceEnv = await rl.question(`ā“ Add 'dotenv' and automatically wire initialization hooks into your files? (Y/n): `);
511
-
1124
+ console.log(`${'─'.repeat(67)}`);
1125
+ const choiceEnv = await safeQuestion(`ā“ Add 'dotenv' and automatically wire initialization hooks into your files? (Y/n): `);
512
1126
  if (choiceEnv.trim().toLowerCase() !== 'n' && choiceEnv.trim().toLowerCase() !== 'no') {
513
1127
  stats.rawDeps.add('dotenv');
514
1128
  stats.injectDotenvEngine = true;
515
1129
  }
516
1130
  }
517
1131
 
1132
+ // --- Build package.json ---
518
1133
  const packageJson = {
519
1134
  name: folderName.toLowerCase().replace(/[^a-z0-9-_]/g, '-'),
520
1135
  version: '1.0.0',
@@ -527,16 +1142,16 @@ async function main() {
527
1142
  devDependencies: {}
528
1143
  };
529
1144
 
1145
+ // --- ESLint suggestion ---
530
1146
  const eslintConfigFile = path.join(targetDir, 'eslint.config.js');
531
1147
  const linterPresent = fs.existsSync(eslintConfigFile) || fs.existsSync(path.join(targetDir, '.eslintrc.json')) || fs.existsSync(path.join(targetDir, '.eslintrc.js'));
532
-
1148
+
533
1149
  if (!linterPresent && (stats.quality.varCount > 0 || stats.quality.hasEval || stats.phantomInjections.size > 0)) {
534
1150
  console.log(`\nšŸŽØ QUALITY LAYER AUDITOR: SYNTAX VALIDATION SYSTEM REQUIRED`);
535
- console.log(`───────────────────────────────────────────────────────────────────`);
536
- console.log(` Code anomalies (legacy 'var' choices or 'eval()') require static linter guards.`);
537
- console.log(`───────────────────────────────────────────────────────────────────`);
538
- const choiceLintSetup = await rl.question(`ā“ Bootstrap standard ESLint flat verification rules into workspace? (Y/n): `);
539
-
1151
+ console.log(`${'─'.repeat(67)}`);
1152
+ console.log(` Code anomalies (legacy 'var' or 'eval()') require static linter guards.`);
1153
+ console.log(`${'─'.repeat(67)}`);
1154
+ const choiceLintSetup = await safeQuestion(`ā“ Bootstrap standard ESLint flat verification rules into workspace? (Y/n): `);
540
1155
  if (choiceLintSetup.trim().toLowerCase() !== 'n' && choiceLintSetup.trim().toLowerCase() !== 'no') {
541
1156
  stats.bootstrapEslintSuite = true;
542
1157
  stats.rawDeps.add('eslint');
@@ -566,6 +1181,7 @@ async function main() {
566
1181
  if (!isFrontendWeb) packageJson.devDependencies['@types/node'] = '^20.11.0';
567
1182
  }
568
1183
 
1184
+ // --- Resolve package versions from npm ---
569
1185
  if (stats.rawDeps.size > 0) {
570
1186
  console.log(`\nšŸ“” Resolving baseline package registry definitions...`);
571
1187
  for (const pkg of stats.rawDeps) {
@@ -574,110 +1190,111 @@ async function main() {
574
1190
  const check = await inspectNpmPackage(cleaned);
575
1191
  if (check && check.error !== 'NOT_FOUND') {
576
1192
  const version = check.version || 'latest';
577
-
1193
+
578
1194
  const isDevDep = [
579
- 'vite', 'vitest', 'typescript', 'eslint', 'typescript-eslint',
580
- '@eslint/js', 'prettier', 'jest', 'nodemon', 'ts-node', 'tsup'
1195
+ 'vite', 'vitest', 'typescript', 'eslint', 'typescript-eslint',
1196
+ '@eslint/js', 'prettier', 'jest', 'nodemon', 'ts-node', 'tsup',
1197
+ 'esbuild', '@swc/cli', 'tsx', 'rimraf', 'copyfiles', 'mkdirp',
1198
+ 'husky', 'lint-staged', '@commitlint/cli', 'typedoc', 'c8', 'nyc',
1199
+ 'mocha', 'ava', 'tap', 'jasmine', 'storybook', 'turbo', 'nx',
1200
+ 'biome', '@biomejs/biome', 'oxlint', 'xo', 'standard',
581
1201
  ].includes(cleaned) || cleaned.startsWith('@types/');
582
1202
 
583
1203
  if (isDevDep) packageJson.devDependencies[cleaned] = `^${version}`;
584
1204
  else packageJson.dependencies[cleaned] = `^${version}`;
585
- console.log(` ¼ Synced verified package parameters: ${cleaned}@^${version}`);
1205
+ console.log(` āœ” Synced: ${cleaned}@^${version}${check.deprecated ? ' \x1b[33m[DEPRECATED]\x1b[0m' : ''}`);
586
1206
  }
587
1207
  }
588
1208
  }
589
1209
  }
590
1210
 
1211
+ // --- Phantom injection report ---
591
1212
  if (stats.phantomInjections.size > 0) {
592
- console.log(`\nšŸ‘» PHANTOM STRUCTURE ALERT: UNIMPORTED EXECUTIONS DETECTED`);
593
- console.log(`───────────────────────────────────────────────────────────────────`);
1213
+ console.log(`\n${'─'.repeat(67)}`);
1214
+ console.log(`šŸ‘» PHANTOM STRUCTURE ALERT: UNIMPORTED EXECUTIONS DETECTED`);
1215
+ console.log(`${'─'.repeat(67)}`);
594
1216
  for (const [filePath, missingModules] of stats.phantomInjections.entries()) {
595
1217
  console.log(`šŸ“‚ File: ${path.relative(targetDir, filePath)}`);
596
1218
  console.log(` āŒ Used but never imported: ${Array.from(missingModules).map(m => `"${m}"`).join(', ')}`);
597
1219
  }
598
- console.log(`───────────────────────────────────────────────────────────────────`);
1220
+ console.log(`${'─'.repeat(67)}`);
599
1221
  }
600
1222
 
1223
+ // --- Code quality warnings ---
601
1224
  if (stats.quality.varCount > 0 || stats.quality.hasEval || stats.quality.syncFsCount > 0) {
602
1225
  console.log(`\nāš ļø CODE ARCHITECTURE & MODERNIZATION COMPLIANCE WARNINGS:`);
603
- console.log(`───────────────────────────────────────────────────────────────────`);
604
- if (stats.quality.varCount > 0) console.log(` ⚔ Found ${stats.quality.varCount} instances of legacy 'var' statements. Transition to blocks ('let' / 'const').`);
605
- if (stats.quality.hasEval) console.log(` šŸ”„ DANGER: 'eval()' invocation structures detected! Refactor to mitigate critical remote code execution vectors.`);
606
- if (stats.quality.syncFsCount > 0) console.log(` šŸ“‰ Performance Alert: Found ${stats.quality.syncFsCount} block-level Sync filesystem configurations inside threads. Transition to promises ('fs/promises').`);
607
- console.log(`───────────────────────────────────────────────────────────────────`);
1226
+ console.log(`${'─'.repeat(67)}`);
1227
+ if (stats.quality.varCount > 0) console.log(` ⚔ Found ${stats.quality.varCount} instances of legacy 'var'. Transition to 'let' / 'const'.`);
1228
+ if (stats.quality.hasEval) console.log(` šŸ”„ DANGER: 'eval()' detected! Refactor to mitigate remote code execution vectors.`);
1229
+ if (stats.quality.syncFsCount > 0) console.log(` šŸ“‰ Performance: Found ${stats.quality.syncFsCount} synchronous fs calls. Transition to 'fs/promises'.`);
1230
+ console.log(`${'─'.repeat(67)}`);
608
1231
  }
609
1232
 
1233
+ // --- Security: hardcoded secrets ---
610
1234
  if (stats.discoveredSecrets.length > 0) {
611
1235
  console.log(`\n🚨 CRITICAL SECURITY COMPLIANCE ALERT: HARDCODED CREDENTIALS DETECTED`);
612
- console.log(`───────────────────────────────────────────────────────────────────`);
1236
+ console.log(`${'─'.repeat(67)}`);
613
1237
  for (const secretMeta of stats.discoveredSecrets) {
614
1238
  console.log(`šŸ“‚ File: ${path.relative(targetDir, secretMeta.filePath)}`);
615
- console.log(` āš ļø Hardcoded raw credential instance found mapping to signature value [${secretMeta.keyName}]`);
1239
+ console.log(` āš ļø Hardcoded credential found: [${secretMeta.keyName}]`);
616
1240
  }
617
- console.log(`───────────────────────────────────────────────────────────────────`);
618
-
619
- const fixSecrets = await rl.question(`ā“ Automatically extract credentials into environment mappings safely? (y/N): `);
1241
+ console.log(`${'─'.repeat(67)}`);
620
1242
 
1243
+ const fixSecrets = await safeQuestion(`ā“ Automatically extract credentials into environment mappings safely? (y/N): `);
621
1244
  if (fixSecrets.trim().toLowerCase() === 'y' || fixSecrets.trim().toLowerCase() === 'yes') {
622
1245
  const envPath = path.join(targetDir, '.env');
623
1246
  let envBuffer = fs.existsSync(envPath) ? readFileSyncNormalized(envPath) : '';
624
-
1247
+
625
1248
  for (const secretMeta of stats.discoveredSecrets) {
626
1249
  let currentCodeContent = readFileSyncNormalized(secretMeta.filePath);
627
1250
  const envAccessor = isFrontendWeb ? `import.meta.env.${secretMeta.envVarName}` : `process.env.${secretMeta.envVarName}`;
628
-
629
1251
  const exactLiteralPattern = new RegExp(`\\b${secretMeta.keyName}\\s*=\\s*['"\`]${secretMeta.secretValue.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')}['"\`]`, 'g');
630
1252
  currentCodeContent = currentCodeContent.replace(exactLiteralPattern, `${secretMeta.keyName} = ${envAccessor}`);
631
1253
  fs.writeFileSync(secretMeta.filePath, currentCodeContent);
632
-
633
1254
  if (!envBuffer.includes(`${secretMeta.envVarName}=`)) envBuffer += `${secretMeta.envVarName}=${secretMeta.secretValue}\n`;
634
- console.log(` šŸ”’ Safely isolated credential string -> ${envAccessor} inside ${path.relative(targetDir, secretMeta.filePath)}`);
1255
+ console.log(` šŸ”’ Isolated: ${secretMeta.keyName} → ${envAccessor}`);
635
1256
  }
636
1257
  fs.writeFileSync(envPath, envBuffer);
637
1258
  }
638
1259
  }
639
1260
 
1261
+ // --- Monorepo detection ---
640
1262
  if (stats.subWorkspaces && stats.subWorkspaces.length > 1) {
641
1263
  console.log(`\nšŸ“‚ MULTI-WORKSPACE SEGMENTATION DETECTED`);
642
- console.log(` Identified independent sub-module paths: ${stats.subWorkspaces.map(w => `/${w}`).join(', ')}`);
643
-
644
- const setupWorkspace = await rl.question(`ā“ Setup layout architecture as a multi-package Monorepo Workspace layout? (y/N): `);
645
-
1264
+ console.log(` Identified sub-module paths: ${stats.subWorkspaces.map(w => `/${w}`).join(', ')}`);
1265
+ const setupWorkspace = await safeQuestion(`ā“ Setup as a multi-package Monorepo Workspace layout? (y/N): `);
646
1266
  if (setupWorkspace.trim().toLowerCase() === 'y' || setupWorkspace.trim().toLowerCase() === 'yes') {
647
1267
  if (activePkgManager === 'pnpm') {
648
1268
  const workspaceYamlPath = path.join(targetDir, 'pnpm-workspace.yaml');
649
- const workspaceYamlTemplate = `packages:\n${stats.subWorkspaces.map(w => ` - '${w}'`).join('\n')}\n`;
650
- fs.writeFileSync(workspaceYamlPath, workspaceYamlTemplate);
651
- console.log(` šŸ—ļø Generated monorepo configuration layer: pnpm-workspace.yaml`);
1269
+ fs.writeFileSync(workspaceYamlPath, `packages:\n${stats.subWorkspaces.map(w => ` - '${w}'`).join('\n')}\n`);
1270
+ console.log(` šŸ—ļø Generated: pnpm-workspace.yaml`);
652
1271
  } else {
653
- packageJson.workspaces = stats.subWorkspaces.map(w => `${w}`);
654
- console.log(` šŸ—ļø Injected 'workspaces' definitions directly into root layout blueprint.`);
1272
+ packageJson.workspaces = stats.subWorkspaces;
1273
+ console.log(` šŸ—ļø Injected 'workspaces' into root package.json.`);
655
1274
  }
656
1275
  }
657
1276
  }
658
1277
 
1278
+ // --- License ---
659
1279
  const licensePath = path.join(targetDir, 'LICENSE');
660
1280
  let chosenLicenseType = preExistingLicense || 'None';
661
-
1281
+
662
1282
  if (!fs.existsSync(licensePath) && !preExistingLicense) {
663
1283
  console.log(`\nāš–ļø Legal Compliance Auditor: No LICENSE file located.`);
664
- const licInput = await rl.question(`ā“ Enter Open Source License to register (e.g. MIT, Apache-2.0, ISC, BSD-3-Clause, skip): `);
665
-
1284
+ const licInput = await safeQuestion(`ā“ Enter Open Source License (e.g. MIT, Apache-2.0, ISC, BSD-3-Clause, skip): `);
666
1285
  const cleanedInput = licInput.trim();
667
1286
  if (cleanedInput.toLowerCase() !== 'skip' && cleanedInput.toLowerCase() !== 'none' && cleanedInput !== '') {
668
- console.log(` šŸ“” Querying GitHub Legal Databases for "${cleanedInput.toUpperCase()}" template...`);
1287
+ console.log(` šŸ“” Querying GitHub Legal Databases for "${cleanedInput.toUpperCase()}"...`);
669
1288
  const rawTemplate = await fetchRemoteLicense(cleanedInput);
670
-
671
1289
  if (rawTemplate) {
672
1290
  const parsedText = rawTemplate
673
- .replace(/\[year\]|<year>/gi, '2026')
1291
+ .replace(/\[year\]|<year>/gi, new Date().getFullYear().toString())
674
1292
  .replace(/\[fullname\]|\[name of copyright owner\]|<copyright holders>|<name of author>/gi, gitInfo.name);
675
-
676
1293
  fs.writeFileSync(licensePath, parsedText);
677
1294
  chosenLicenseType = cleanedInput.toUpperCase();
678
- console.log(` āš–ļø Successfully provisioned legal asset: LICENSE`);
1295
+ console.log(` āš–ļø Provisioned: LICENSE`);
679
1296
  } else {
680
- console.log(` āš ļø License model "${cleanedInput}" not indexed on GitHub database registers. Saving custom structural label configuration.`);
1297
+ console.log(` āš ļø License "${cleanedInput}" not found. Saving custom label.`);
681
1298
  chosenLicenseType = cleanedInput;
682
1299
  }
683
1300
  packageJson.license = chosenLicenseType;
@@ -689,7 +1306,7 @@ async function main() {
689
1306
  const rawTemplate = await fetchRemoteLicense(preExistingLicense);
690
1307
  if (rawTemplate) {
691
1308
  const parsedText = rawTemplate
692
- .replace(/\[year\]|<year>/gi, '2026')
1309
+ .replace(/\[year\]|<year>/gi, new Date().getFullYear().toString())
693
1310
  .replace(/\[fullname\]|\[name of copyright owner\]|<copyright holders>|<name of author>/gi, gitInfo.name);
694
1311
  fs.writeFileSync(licensePath, parsedText);
695
1312
  }
@@ -700,33 +1317,31 @@ async function main() {
700
1317
  if (currentLicenseContent.includes('MIT')) chosenLicenseType = 'MIT';
701
1318
  else if (currentLicenseContent.includes('Apache')) chosenLicenseType = 'Apache-2.0';
702
1319
  else chosenLicenseType = 'Custom';
703
- } catch(e) {}
1320
+ } catch (e) {}
704
1321
  }
705
1322
  packageJson.license = chosenLicenseType;
706
1323
  }
707
1324
 
1325
+ // --- Test scaffolding ---
708
1326
  if (!stats.hasTests) {
709
- const bootstrapTest = await rl.question(`\nā“ No test files detected. Scaffold a zero-bloat testing harness via Node native test runner? (y/N): `);
710
-
1327
+ const bootstrapTest = await safeQuestion(`\nā“ No test files detected. Scaffold a zero-bloat testing harness via Node native test runner? (y/N): `);
711
1328
  if (bootstrapTest.trim().toLowerCase() === 'y' || bootstrapTest.trim().toLowerCase() === 'yes') {
712
1329
  const isEsm = packageJson.type === 'module';
713
1330
  const testExt = isTypeScript ? '.test.ts' : '.test.js';
714
- const targetTestFile = `index${testExt}`;
715
- const testFilePath = path.join(targetDir, targetTestFile);
716
-
1331
+ const testFilePath = path.join(targetDir, `index${testExt}`);
717
1332
  const testTemplate = isEsm
718
1333
  ? `import { test, describe } from 'node:test';\nimport assert from 'node:assert';\n\ndescribe('Core Architecture Testing Suite', () => {\n test('should verify systemic environmental execution health', () => {\n assert.strictEqual(1, 1);\n });\n});\n`
719
1334
  : `const { test, describe } = require('node:test');\nconst assert = require('node:assert');\n\ndescribe('Core Architecture Testing Suite', () => {\n test('should verify systemic environmental execution health', () => {\n assert.strictEqual(1, 1);\n });\n});\n`;
720
-
721
1335
  fs.writeFileSync(testFilePath, testTemplate);
722
1336
  packageJson.scripts.test = 'node --test';
723
1337
  stats.hasTests = true;
724
- console.log(` 🧪 Generated native functional testing fixture: ${targetTestFile}`);
1338
+ console.log(` 🧪 Generated: index${testExt}`);
725
1339
  }
726
1340
  }
727
1341
 
728
1342
  console.log(`\nāš™ļø Writing ecosystem configuration artifacts...`);
729
1343
 
1344
+ // --- ESLint config ---
730
1345
  if (stats.bootstrapEslintSuite) {
731
1346
  packageJson.scripts.lint = 'eslint .';
732
1347
  let eslintConfigContent = '';
@@ -740,33 +1355,38 @@ async function main() {
740
1355
  }
741
1356
  }
742
1357
  fs.writeFileSync(eslintConfigFile, eslintConfigContent);
743
- console.log(` šŸŽØ Provisioned automated static syntax layout: eslint.config.js`);
1358
+ console.log(` šŸŽØ Provisioned: eslint.config.js`);
744
1359
  }
745
1360
 
1361
+ // --- Write / merge package.json ---
746
1362
  if (fs.existsSync(pkgPath)) {
747
1363
  try {
748
1364
  const currentPackageJson = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
749
1365
  currentPackageJson.dependencies = { ...packageJson.dependencies, ...currentPackageJson.dependencies };
750
1366
  currentPackageJson.devDependencies = { ...packageJson.devDependencies, ...currentPackageJson.devDependencies };
751
- if (packageJson.scripts.lint && !currentPackageJson.scripts.lint) currentPackageJson.scripts.lint = packageJson.scripts.lint;
752
-
1367
+ if (packageJson.scripts.lint && !currentPackageJson.scripts?.lint) {
1368
+ currentPackageJson.scripts = currentPackageJson.scripts || {};
1369
+ currentPackageJson.scripts.lint = packageJson.scripts.lint;
1370
+ }
753
1371
  fs.writeFileSync(pkgPath, JSON.stringify(currentPackageJson, null, 2));
754
1372
  console.log(` šŸ”„ Safely merged discovered dependencies into existing package.json`);
755
1373
  } catch (e) {}
756
1374
  } else {
757
- fs.writeFileSync(pkgPath, JSON.stringify(packageJson, null, 2));
758
- console.log(` šŸ“ Injected: package.json`);
1375
+ fs.writeFileSync(pkgPath, JSON.stringify(packageJson, null, 2));
1376
+ console.log(` šŸ“ Generated: package.json`);
759
1377
  }
760
1378
 
1379
+ // --- Prettier config ---
761
1380
  const prettierPath = path.join(targetDir, '.prettierrc');
762
1381
  if (!fs.existsSync(prettierPath)) {
763
1382
  const useTabs = stats.style.tabCount > (stats.style.space2Count + stats.style.space4Count);
764
1383
  const useSemi = stats.style.semiCount >= stats.style.noSemiCount;
765
1384
  const tabWidth = stats.style.space4Count > stats.style.space2Count ? 4 : 2;
766
- fs.writeFileSync(prettierPath, JSON.stringify({ semi: useSemi, useTabs: useTabs, tabWidth: tabWidth, singleQuote: true, trailingComma: "es5" }, null, 2));
1385
+ fs.writeFileSync(prettierPath, JSON.stringify({ semi: useSemi, useTabs, tabWidth, singleQuote: true, trailingComma: "es5" }, null, 2));
767
1386
  console.log(` šŸŽØ Code formatting mirror locked: .prettierrc`);
768
1387
  }
769
1388
 
1389
+ // --- .env.example ---
770
1390
  if (stats.envVars.size > 0) {
771
1391
  const envExamplePath = path.join(targetDir, '.env.example');
772
1392
  if (!fs.existsSync(envExamplePath)) {
@@ -775,20 +1395,26 @@ async function main() {
775
1395
  }
776
1396
  }
777
1397
 
1398
+ // --- .gitignore ---
778
1399
  const gitignorePath = path.join(targetDir, '.gitignore');
779
- if (!fs.existsSync(gitignorePath)) {
780
- fs.writeFileSync(gitignorePath, `node_modules/\ndist/\nbuild/\n.env\n.env.local\n.DS_Store\n`);
781
- console.log(` āš™ļø Structural default configurations locked: .gitignore`);
1400
+ if (!fs.existsSync(gitignorePath)) {
1401
+ fs.writeFileSync(gitignorePath, `node_modules/\ndist/\nbuild/\n.env\n.env.local\n.DS_Store\n*.log\n`);
1402
+ console.log(` āš™ļø Generated: .gitignore`);
782
1403
  }
783
1404
 
1405
+ // --- tsconfig.json ---
784
1406
  if (isTypeScript) {
785
1407
  const tsconfigPath = path.join(targetDir, 'tsconfig.json');
786
1408
  if (!fs.existsSync(tsconfigPath)) {
787
- fs.writeFileSync(tsconfigPath, JSON.stringify({ compilerOptions: { target: "ES2022", module: "NodeNext", moduleResolution: "NodeNext", esModuleInterop: true, strict: true, skipLibCheck: true, outDir: "./dist" }, include: ["src/**/*", "**/*.ts"] }, null, 2));
788
- console.log(` āš™ļø Structural default configurations locked: tsconfig.json`);
1409
+ fs.writeFileSync(tsconfigPath, JSON.stringify({
1410
+ compilerOptions: { target: "ES2022", module: "NodeNext", moduleResolution: "NodeNext", esModuleInterop: true, strict: true, skipLibCheck: true, outDir: "./dist" },
1411
+ include: ["src/**/*", "**/*.ts"]
1412
+ }, null, 2));
1413
+ console.log(` āš™ļø Generated: tsconfig.json`);
789
1414
  }
790
1415
  }
791
1416
 
1417
+ // --- README ---
792
1418
  const readmePath = path.join(targetDir, 'README.md');
793
1419
  if (!fs.existsSync(readmePath)) {
794
1420
  const pName = packageJson.name;
@@ -797,7 +1423,7 @@ async function main() {
797
1423
  const displayDevDeps = Object.keys(packageJson.devDependencies).map(d => `* \`${d}\``).join('\n') || '* None extracted';
798
1424
  const licenseBadgeParam = encodeURIComponent(chosenLicenseType.replace(/-/g, '_'));
799
1425
 
800
- const documentationTemplate =
1426
+ const documentationTemplate =
801
1427
  `# ${pName}
802
1428
 
803
1429
  ![Workspace Engine](https://img.shields.io/badge/engine-node-${packageJson.type === 'module' ? 'green' : 'blue'}?style=flat)
@@ -814,10 +1440,6 @@ ${displayDeps}
814
1440
  ### System Tooling Engines (\`devDependencies\`)
815
1441
  ${displayDevDeps}
816
1442
 
817
- ### Underlying Tooling Architecture
818
- This project environment layout maps out core metadata elements dynamically using:
819
- * \`npm-deprecated-check\` (Bundled internal core validation system for dependency deprecation checking routines)
820
-
821
1443
  ---
822
1444
 
823
1445
  ## Project Architecture Layout
@@ -825,24 +1447,24 @@ This project environment layout maps out core metadata elements dynamically usin
825
1447
  ${layoutTree}
826
1448
  \`\`\`
827
1449
 
828
- ## Installation & Launch Procedures
829
- Initialize the workspace tracking structures via your active system package engine:
1450
+ ## Installation
830
1451
 
831
1452
  \`\`\`bash
832
1453
  ${activePkgManager} install
833
1454
  \`\`\`
834
1455
  `;
835
1456
  fs.writeFileSync(readmePath, documentationTemplate);
836
- console.log(` šŸ“– Auto-generated system asset metrics: README.md`);
1457
+ console.log(` šŸ“– Generated: README.md`);
837
1458
  }
838
1459
 
1460
+ // --- Phantom injection fix ---
839
1461
  if (stats.phantomInjections.size > 0 || (stats.injectDotenvEngine && stats.filesWithEnvVars.size > 0)) {
840
1462
  console.log(`\nšŸ’” Source Code Modification Subsystem:`);
841
- const injectChoice = await rl.question(`ā“ Found phantom modules or unmanaged env components. Mutate file headers cleanly now? (y/N): `);
1463
+ const injectChoice = await safeQuestion(`ā“ Found phantom modules or unmanaged env components. Mutate file headers cleanly now? (y/N): `);
842
1464
 
843
1465
  if (injectChoice.trim().toLowerCase() === 'y' || injectChoice.trim().toLowerCase() === 'yes') {
844
1466
  const allTargets = new Set([...stats.phantomInjections.keys(), ...stats.filesWithEnvVars]);
845
-
1467
+
846
1468
  for (const filePath of allTargets) {
847
1469
  const originalCode = readFileSyncNormalized(filePath);
848
1470
  let declarationBlock = '';
@@ -859,62 +1481,85 @@ ${activePkgManager} install
859
1481
  if (packageJson.type === 'module') declarationBlock += `import 'dotenv/config';\n`;
860
1482
  else declarationBlock += `require('dotenv').config();\n`;
861
1483
  }
862
-
1484
+
863
1485
  if (declarationBlock !== '') {
864
1486
  fs.writeFileSync(filePath, smartPrepend(originalCode, declarationBlock));
865
- console.log(` ⚔ Injected contextual runtime headers safely: ${path.relative(targetDir, filePath)}`);
1487
+ console.log(` ⚔ Injected headers: ${path.relative(targetDir, filePath)}`);
866
1488
  }
867
1489
  }
868
1490
  }
869
1491
  }
870
1492
 
1493
+ // --- Deprecation scan via npm-deprecated-check ---
871
1494
  console.log(`\nšŸ›‘ INITIALIZING LIVE ECOSYSTEM DEPRECATION SECURITY SCAN...`);
872
- console.log(` Running integrated npm-deprecated-check validation algorithms:\n`);
1495
+ console.log(` Running integrated npm-deprecated-check validation:\n`);
873
1496
  try {
874
1497
  const localRequire = createRequire(import.meta.url);
875
1498
  const dependencyPkgJsonPath = localRequire.resolve('npm-deprecated-check/package.json');
876
1499
  const dependencyPkgJson = JSON.parse(fs.readFileSync(dependencyPkgJsonPath, 'utf8'));
877
- const binRelativeMapping = typeof dependencyPkgJson.bin === 'string' ? dependencyPkgJson.bin : (dependencyPkgJson.bin['npm-deprecated-check'] || dependencyPkgJson.bin['ndc']);
1500
+ const binRelativeMapping = typeof dependencyPkgJson.bin === 'string'
1501
+ ? dependencyPkgJson.bin
1502
+ : (dependencyPkgJson.bin['npm-deprecated-check'] || dependencyPkgJson.bin['ndc']);
878
1503
  const absoluteExecutablePath = path.join(path.dirname(dependencyPkgJsonPath), binRelativeMapping);
879
1504
  execSync(`node "${absoluteExecutablePath}" current`, { stdio: 'inherit', cwd: targetDir });
880
1505
  } catch (err) {}
881
1506
 
1507
+ // --- Conflicting lockfiles ---
882
1508
  if (stats.conflictingLockfiles.length > 1) {
883
- console.log(`\nāš ļø CONFLICTING ACCUMULATED LOCKFILES DETECTED: [${stats.conflictingLockfiles.join(', ')}]`);
884
- const cleanLocks = await rl.question(`ā“ Purge legacy/mismatched lockfiles to protect systemic package integrity? (y/N): `);
885
-
1509
+ console.log(`\nāš ļø CONFLICTING LOCKFILES DETECTED: [${stats.conflictingLockfiles.join(', ')}]`);
1510
+ const cleanLocks = await safeQuestion(`ā“ Purge legacy/mismatched lockfiles to protect package integrity? (y/N): `);
886
1511
  if (cleanLocks.trim().toLowerCase() === 'y' || cleanLocks.trim().toLowerCase() === 'yes') {
887
- const packageEngineLockmap = { npm: 'package-lock.json', pnpm: 'pnpm-lock.yaml', yarn: 'yarn.lock' };
1512
+ const packageEngineLockmap = { npm: 'package-lock.json', pnpm: 'pnpm-lock.yaml', yarn: 'yarn.lock', bun: 'bun.lockb' };
888
1513
  const operationalLockfile = packageEngineLockmap[activePkgManager];
889
1514
  for (const lockfile of stats.conflictingLockfiles) {
890
1515
  if (lockfile !== operationalLockfile) {
891
1516
  try {
892
1517
  fs.unlinkSync(path.join(targetDir, lockfile));
893
- console.log(` šŸ—‘ļø Cleaned up duplicate lockfile artifact: ${lockfile}`);
1518
+ console.log(` šŸ—‘ļø Cleaned: ${lockfile}`);
894
1519
  } catch (e) {}
895
1520
  }
896
1521
  }
897
1522
  }
898
1523
  }
899
1524
 
1525
+ // --- Final install prompt ---
900
1526
  console.log(`\nšŸ“¦ Auto-scaffolding pipeline complete!`);
901
- const userPromptChoice = await rl.question(`ā“ Detected system default manager: "${activePkgManager}". Run "${activePkgManager} install" automatically now? (y/N): `);
902
1527
 
1528
+ // Summary report
1529
+ console.log(`\n${'═'.repeat(67)}`);
1530
+ console.log(`šŸ“Š DEPENDENCY INTELLIGENCE SUMMARY`);
1531
+ console.log(`${'═'.repeat(67)}`);
1532
+ console.log(` šŸ“ Files scanned: ${stats.scannedFiles}`);
1533
+ console.log(` šŸ“¦ Packages imported: ${stats.allImportedPackages.size}`);
1534
+ if (stats.ghostDependencies.size > 0)
1535
+ console.log(` 🚨 Ghost deps (missing): ${stats.ghostDependencies.size} — \x1b[31mCRITICAL\x1b[0m`);
1536
+ if (stats.orphanedDependencies.size > 0)
1537
+ console.log(` šŸ—‘ļø Orphaned deps (unused): ${stats.orphanedDependencies.size}`);
1538
+ if (allDiscoveredUnused.size > 0)
1539
+ console.log(` ⚔ Unused imports: ${allDiscoveredUnused.size}`);
1540
+ if (stats.deprecatedPackages.size > 0)
1541
+ console.log(` šŸ“› Deprecated packages: ${stats.deprecatedPackages.size}`);
1542
+ if (stats.phantomInjections.size > 0)
1543
+ console.log(` šŸ‘» Phantom injections: ${stats.phantomInjections.size} file(s)`);
1544
+ if (stats.discoveredSecrets.length > 0)
1545
+ console.log(` šŸ” Hardcoded secrets: ${stats.discoveredSecrets.length} — \x1b[31mSECURITY RISK\x1b[0m`);
1546
+ console.log(`${'═'.repeat(67)}`);
1547
+
1548
+ const userPromptChoice = await safeQuestion(`ā“ Detected package manager: "${activePkgManager}". Run "${activePkgManager} install" now? (y/N): `);
903
1549
  rl.close();
904
1550
 
905
1551
  const normalizedAnswer = userPromptChoice.trim().toLowerCase();
906
1552
  if (normalizedAnswer === 'y' || normalizedAnswer === 'yes') {
907
- console.log(`\nā³ Executing automated asset installations via background child processes...`);
1553
+ console.log(`\nā³ Executing automated asset installations...`);
908
1554
  try {
909
- console.log(` Running: "${activePkgManager} install" inside current folder...`);
910
1555
  execSync(`${activePkgManager} install`, { stdio: 'inherit', cwd: targetDir });
911
- console.log(`\nšŸŽ‰ Project fully mapped, configurations customized, and environments installed successfully!`);
1556
+ console.log(`\nšŸŽ‰ Project fully mapped, configured, and installed successfully!`);
912
1557
  } catch (err) {
913
- console.error(`\nāŒ Automatic package extraction successful, but target installation shell returned an issue.`);
1558
+ console.error(`\nāŒ Installation returned an issue. Please run "${activePkgManager} install" manually.`);
914
1559
  }
915
1560
  } else {
916
- console.log(`\nā–¶ļø Skipping automated setup execution. Workspace configured! Run "${activePkgManager} install" manually whenever you're ready.`);
1561
+ console.log(`\nā–¶ļø Skipping install. Run "${activePkgManager} install" manually when ready.`);
917
1562
  }
918
1563
  }
919
1564
 
920
- main();
1565
+ main();