@hatem427/code-guard-ci 3.5.4 → 3.5.6

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/scripts/cli.ts CHANGED
@@ -73,9 +73,10 @@ function showHelp(): void {
73
73
  code-guard <command> [options]
74
74
 
75
75
  ${c.bold('Commands:')}
76
- ${c.green('init')} Full automatic setup (zero prompts)
77
- ${c.green('check')} Run pre-commit checks manually
78
- ${c.green('report')} Generate a rich report with fix examples (opens in editor)
76
+ ${c.green('init')} Full automatic setup (zero prompts)
77
+ ${c.green('check')} Run pre-commit checks manually
78
+ ${c.green('check-vuln')} Run security scans (npm audit, RetireJS)
79
+ ${c.green('report')} Generate a rich report with fix examples (opens in editor)
79
80
  ${c.green('doc')} Generate feature documentation
80
81
  ${c.green('checklist')} Generate PR checklist
81
82
  ${c.green('validate')} Validate structure & naming conventions
@@ -93,7 +94,6 @@ ${c.bold('Init Options:')}
93
94
 
94
95
  ${c.bold('Uninstall Options:')}
95
96
  --yes, -y Skip confirmation prompt
96
- --no-backup Don't create backup of AI configs
97
97
 
98
98
  ${c.bold('Examples:')}
99
99
  ${c.dim('# Full automatic setup')}
@@ -111,11 +111,11 @@ ${c.bold('Examples:')}
111
111
  ${c.dim('# Generate docs')}
112
112
  code-guard doc --name="user-card" --type=ui
113
113
 
114
- ${c.dim('# Clean uninstall with backup')}
114
+ ${c.dim('# Clean uninstall')}
115
115
  code-guard uninstall
116
116
 
117
117
  ${c.dim('# Force uninstall without prompts')}
118
- code-guard uninstall --yes --no-backup
118
+ code-guard uninstall --yes
119
119
  `);
120
120
  }
121
121
 
@@ -143,6 +143,16 @@ function initProject(): void {
143
143
  const skipAI = hasFlag('skip-ai');
144
144
  const skipHooks = hasFlag('skip-hooks');
145
145
 
146
+ // ── Initialize manifest for tracking ────────────────────────────────────
147
+ const {
148
+ createManifest, saveManifest, loadManifest,
149
+ recordCreatedFile, recordModifiedFile, recordBackup,
150
+ recordAddedScript, recordModifiedScript,
151
+ } = requireUtil('manifest');
152
+
153
+ // Load existing manifest or create new one
154
+ const manifest = loadManifest(cwd) || createManifest();
155
+
146
156
  // ── Step 1: Detect Framework ────────────────────────────────────────────
147
157
  console.log(c.bold('📡 Step 1: Detecting project...'));
148
158
 
@@ -186,9 +196,25 @@ function initProject(): void {
186
196
  // ── Step 3: Generate ESLint Config ───────────────────────────────────────
187
197
  console.log(c.bold('⚡ Step 3: Configuring ESLint...'));
188
198
  try {
199
+ const eslintFile = 'eslint.config.mjs';
200
+ const eslintPath = path.join(cwd, eslintFile);
201
+ const eslintExisted = fs.existsSync(eslintPath);
202
+
189
203
  const { generateEslintConfig } = requireGenerator('eslint-generator');
190
204
  generateEslintConfig(project);
191
- console.log(` ${c.green('✓')} Created .eslintrc.json`);
205
+
206
+ // Track: eslint-generator already creates .backup files for existing configs
207
+ if (eslintExisted) {
208
+ recordModifiedFile(manifest, eslintFile, 'eslint');
209
+ // The generator already backed up the old file — record it
210
+ if (fs.existsSync(eslintPath + '.backup')) {
211
+ recordBackup(manifest, eslintFile, eslintFile + '.backup');
212
+ }
213
+ } else {
214
+ recordCreatedFile(manifest, eslintFile);
215
+ }
216
+
217
+ console.log(` ${c.green('✓')} Created eslint.config.mjs`);
192
218
  console.log(` ${c.green('✓')} Created .eslintignore\n`);
193
219
  } catch (error: any) {
194
220
  console.warn(c.yellow(` ⚠️ ESLint config generation failed: ${error.message}\n`));
@@ -197,8 +223,33 @@ function initProject(): void {
197
223
  // ── Step 4: Generate Prettier Config ──────────────────────────────────────
198
224
  console.log(c.bold('🎨 Step 4: Configuring Prettier...'));
199
225
  try {
226
+ const prettierFile = '.prettierrc.json';
227
+ const prettierPath = path.join(cwd, prettierFile);
228
+ const prettierExisted = fs.existsSync(prettierPath);
229
+ const ignoreFile = '.prettierignore';
230
+ const ignorePath = path.join(cwd, ignoreFile);
231
+ const ignoreExisted = fs.existsSync(ignorePath);
232
+
200
233
  const { generatePrettierConfig } = requireGenerator('prettier-generator');
201
234
  generatePrettierConfig(project);
235
+
236
+ // Track: prettier-generator already creates .backup files for existing configs
237
+ if (prettierExisted) {
238
+ recordModifiedFile(manifest, prettierFile, 'prettier');
239
+ // Check for any backed-up prettier files
240
+ const prettierFiles = ['.prettierrc', '.prettierrc.js', '.prettierrc.cjs', '.prettierrc.json', '.prettierrc.yml', '.prettierrc.yaml', '.prettierrc.toml', 'prettier.config.js'];
241
+ for (const pf of prettierFiles) {
242
+ if (fs.existsSync(path.join(cwd, pf + '.backup'))) {
243
+ recordBackup(manifest, pf, pf + '.backup');
244
+ }
245
+ }
246
+ } else {
247
+ recordCreatedFile(manifest, prettierFile);
248
+ }
249
+ if (!ignoreExisted && fs.existsSync(ignorePath)) {
250
+ recordCreatedFile(manifest, ignoreFile);
251
+ }
252
+
202
253
  console.log(` ${c.green('✓')} Created .prettierrc.json`);
203
254
  console.log(` ${c.green('✓')} Created .prettierignore\n`);
204
255
  } catch (error: any) {
@@ -209,8 +260,19 @@ function initProject(): void {
209
260
  if (project.usesTypeScript) {
210
261
  console.log(c.bold('📘 Step 5: Configuring TypeScript...'));
211
262
  try {
263
+ const tsFile = project.existingTooling.hasTypescript ? 'tsconfig.strict.json' : 'tsconfig.json';
264
+ const tsPath = path.join(cwd, tsFile);
265
+ const tsExisted = fs.existsSync(tsPath);
266
+
212
267
  const { generateTypescriptConfig } = requireGenerator('typescript-generator');
213
268
  generateTypescriptConfig(project);
269
+
270
+ if (tsExisted) {
271
+ recordModifiedFile(manifest, tsFile, 'typescript');
272
+ } else {
273
+ recordCreatedFile(manifest, tsFile);
274
+ }
275
+
214
276
  if (project.existingTooling.hasTypescript) {
215
277
  console.log(` ${c.green('✓')} Created tsconfig.strict.json (extends existing tsconfig)`);
216
278
  } else {
@@ -227,18 +289,34 @@ function initProject(): void {
227
289
  // ── Step 6: Generate lint-staged Config ───────────────────────────────────
228
290
  console.log(c.bold('📋 Step 6: Configuring lint-staged...'));
229
291
  try {
292
+ const lsFile = '.lintstagedrc.json';
293
+ const lsPath = path.join(cwd, lsFile);
294
+ const lsExisted = fs.existsSync(lsPath);
295
+
230
296
  const { generateLintStagedConfig } = requireGenerator('lint-staged-generator');
231
297
  generateLintStagedConfig(project);
298
+
299
+ if (!lsExisted && fs.existsSync(lsPath)) {
300
+ recordCreatedFile(manifest, lsFile);
301
+ }
232
302
  console.log(` ${c.green('✓')} Created .lintstagedrc.json\n`);
233
303
  } catch (error: any) {
234
304
  console.warn(c.yellow(` ⚠️ lint-staged config generation failed: ${error.message}\n`));
235
305
  }
236
306
 
237
307
  // ── Step 7: Generate .editorconfig ────────────────────────────────────────
238
- console.log(c.bold('📐 Step 8: Creating .editorconfig...'));
308
+ console.log(c.bold('📐 Step 7: Creating .editorconfig...'));
239
309
  try {
310
+ const ecFile = '.editorconfig';
311
+ const ecPath = path.join(cwd, ecFile);
312
+ const ecExisted = fs.existsSync(ecPath);
313
+
240
314
  const { generateEditorConfig } = requireGenerator('editorconfig-generator');
241
315
  generateEditorConfig(project);
316
+
317
+ if (!ecExisted && fs.existsSync(ecPath)) {
318
+ recordCreatedFile(manifest, ecFile);
319
+ }
242
320
  console.log(` ${c.green('✓')} Created .editorconfig\n`);
243
321
  } catch (error: any) {
244
322
  console.warn(c.yellow(` ⚠️ EditorConfig generation failed: ${error.message}\n`));
@@ -247,11 +325,24 @@ function initProject(): void {
247
325
  // ── Step 8: Generate VS Code Settings ─────────────────────────────────────
248
326
  console.log(c.bold('💻 Step 8: Creating VS Code workspace settings...'));
249
327
  try {
328
+ const vscodeFiles = ['.vscode/settings.json', '.vscode/extensions.json', '.vscode/tasks.json', '.vscode/launch.json'];
329
+ const vscodeExisted: Record<string, boolean> = {};
330
+ for (const vf of vscodeFiles) {
331
+ vscodeExisted[vf] = fs.existsSync(path.join(cwd, vf));
332
+ }
333
+
250
334
  const { generateVSCodeSettings, generateVSCodeExtensions, generateVSCodeTasks, generateVSCodeLaunch } = requireGenerator('vscode-generator');
251
335
  generateVSCodeSettings(project);
252
336
  generateVSCodeExtensions(project);
253
337
  generateVSCodeTasks(project);
254
338
  generateVSCodeLaunch(project);
339
+
340
+ for (const vf of vscodeFiles) {
341
+ if (!vscodeExisted[vf] && fs.existsSync(path.join(cwd, vf))) {
342
+ recordCreatedFile(manifest, vf);
343
+ }
344
+ }
345
+
255
346
  console.log(` ${c.green('✓')} Created .vscode/settings.json`);
256
347
  console.log(` ${c.green('✓')} Created .vscode/extensions.json`);
257
348
  console.log(` ${c.green('✓')} Created .vscode/tasks.json`);
@@ -263,7 +354,14 @@ function initProject(): void {
263
354
  // ── Step 9: Setup Git Hooks ───────────────────────────────────────────────
264
355
  if (!skipHooks) {
265
356
  console.log(c.bold('🐶 Step 9: Setting up Git hooks...'));
357
+ const hookFile = '.husky/pre-commit';
358
+ const hookExisted = fs.existsSync(path.join(cwd, hookFile));
266
359
  setupGitHooks(cwd);
360
+ if (hookExisted) {
361
+ recordModifiedFile(manifest, hookFile, 'husky');
362
+ } else if (fs.existsSync(path.join(cwd, hookFile))) {
363
+ recordCreatedFile(manifest, hookFile);
364
+ }
267
365
  console.log('');
268
366
  } else {
269
367
  console.log(c.dim('🐶 Step 9: Skipping git hooks (--skip-hooks)\n'));
@@ -275,17 +373,32 @@ function initProject(): void {
275
373
  try {
276
374
  const { loadCompanyRules } = requireUtil('custom-rules-loader');
277
375
  const companyRules = loadCompanyRules(cwd);
278
- const { generateAIConfigs } = requireGenerator('ai-config-generator');
279
- generateAIConfigs(project, companyRules.aiGuidelines);
280
376
 
377
+ // Snapshot which AI files exist before generation
281
378
  const { defaultRegistry } = requireUtil('ai-config-registry');
282
379
  const templates = defaultRegistry.getAll();
380
+ const aiExisted: Record<string, boolean> = {};
381
+ for (const t of templates) {
382
+ const targetDir = t.directory ? path.join(cwd, t.directory) : cwd;
383
+ const targetPath = path.join(targetDir, t.fileName);
384
+ const relPath = t.directory ? `${t.directory}/${t.fileName}` : t.fileName;
385
+ aiExisted[relPath] = fs.existsSync(targetPath);
386
+ }
387
+
388
+ const { generateAIConfigs } = requireGenerator('ai-config-generator');
389
+ generateAIConfigs(project, companyRules.aiGuidelines);
390
+
283
391
  for (const t of templates) {
284
392
  const targetDir = t.directory ? path.join(cwd, t.directory) : cwd;
285
393
  const targetPath = path.join(targetDir, t.fileName);
394
+ const relPath = t.directory ? `${t.directory}/${t.fileName}` : t.fileName;
286
395
  if (fs.existsSync(targetPath)) {
287
- const displayPath = t.directory ? `${t.directory}/${t.fileName}` : t.fileName;
288
- console.log(` ${c.green('✓')} ${t.name}: ${displayPath}`);
396
+ if (aiExisted[relPath]) {
397
+ recordModifiedFile(manifest, relPath, t.marker);
398
+ } else {
399
+ recordCreatedFile(manifest, relPath);
400
+ }
401
+ console.log(` ${c.green('✓')} ${t.name}: ${relPath}`);
289
402
  }
290
403
  }
291
404
  console.log('');
@@ -299,6 +412,12 @@ function initProject(): void {
299
412
  // ── Step 11: Custom Rules Template ─────────────────────────────────────────
300
413
  console.log(c.bold('🏢 Step 11: Creating custom rules templates...'));
301
414
  try {
415
+ const cgFiles = ['.code-guardian/custom-rules.json', '.code-guardian/structure-rules.json', '.code-guardian/naming-rules.json'];
416
+ const cgExisted: Record<string, boolean> = {};
417
+ for (const cf of cgFiles) {
418
+ cgExisted[cf] = fs.existsSync(path.join(cwd, cf));
419
+ }
420
+
302
421
  const { generateCustomRulesTemplate } = requireUtil('custom-rules-loader');
303
422
  generateCustomRulesTemplate(cwd);
304
423
  console.log(` ${c.green('✓')} Created .code-guardian/custom-rules.json`);
@@ -310,20 +429,30 @@ function initProject(): void {
310
429
  const { generateNamingRulesTemplate } = requireUtil('naming-validator');
311
430
  generateNamingRulesTemplate(cwd);
312
431
  console.log(` ${c.green('✓')} Created .code-guardian/naming-rules.json\n`);
432
+
433
+ for (const cf of cgFiles) {
434
+ if (!cgExisted[cf] && fs.existsSync(path.join(cwd, cf))) {
435
+ recordCreatedFile(manifest, cf);
436
+ }
437
+ }
313
438
  } catch (error: any) {
314
439
  console.warn(c.yellow(` ⚠️ Custom rules template generation failed: ${error.message}\n`));
315
440
  }
316
441
 
317
442
  // ── Step 12: Update package.json ───────────────────────────────────────────
318
443
  console.log(c.bold('📦 Step 12: Updating package.json...'));
319
- updatePackageJson(cwd, project);
444
+ updatePackageJson(cwd, project, manifest);
320
445
  console.log('');
321
446
 
322
447
  // ── Step 13: Copy Code Guardian configs and templates ──────────────────────
323
448
  console.log(c.bold('📁 Step 13: Copying Code Guardian rules and templates...'));
324
- copyCodeGuardianFiles(cwd);
449
+ copyCodeGuardianFiles(cwd, manifest, project.type);
325
450
  console.log('');
326
451
 
452
+ // ── Save manifest ─────────────────────────────────────────────────────────
453
+ saveManifest(cwd, manifest);
454
+ console.log(` ${c.green('✓')} Saved manifest to .code-guardian/manifest.json\n`);
455
+
327
456
  // ── Done! ─────────────────────────────────────────────────────────────────
328
457
  console.log(c.bold(c.green('═══════════════════════════════════════════════════════════')));
329
458
  console.log(c.bold(c.green(' ✅ Code Guardian initialized successfully!')));
@@ -358,44 +487,53 @@ function initProject(): void {
358
487
 
359
488
  function setupGitHooks(cwd: string): void {
360
489
  try {
361
- // Initialize Husky
490
+ const huskyDir = path.join(cwd, '.husky');
491
+ const preCommitPath = path.join(huskyDir, 'pre-commit');
492
+
493
+ // Save existing pre-commit content BEFORE husky init (which overwrites it)
494
+ let existingPreCommit: string | null = null;
495
+ if (fs.existsSync(preCommitPath)) {
496
+ existingPreCommit = fs.readFileSync(preCommitPath, 'utf-8');
497
+ }
498
+
499
+ // Initialize Husky (creates .husky/ dir and default pre-commit)
362
500
  try {
363
501
  execSync('npx husky init', { stdio: 'pipe', cwd });
364
502
  } catch {
365
503
  // Fallback: manual setup
366
- const huskyDir = path.join(cwd, '.husky');
367
504
  if (!fs.existsSync(huskyDir)) {
368
505
  fs.mkdirSync(huskyDir, { recursive: true });
369
506
  }
370
507
  }
371
508
 
372
- const huskyDir = path.join(cwd, '.husky');
373
509
  if (!fs.existsSync(huskyDir)) {
374
510
  fs.mkdirSync(huskyDir, { recursive: true });
375
511
  }
376
512
 
377
- // Create or append pre-commit hook (lint-staged + Code Guardian)
378
- const preCommitContent = `
379
- # npm test
513
+ // Restore the original pre-commit content if husky init overwrote it
514
+ if (existingPreCommit !== null) {
515
+ fs.writeFileSync(preCommitPath, existingPreCommit);
516
+ }
380
517
 
518
+ // Create or append pre-commit hook (lint-staged + Code Guardian rule checks)
519
+ const preCommitContent = `
381
520
  # --- code-guardian-hook-start ---
382
521
  echo "🛡️ Code Guardian — Pre-commit checks..."
383
522
 
384
- # Skip lint-staged and all checks when BYPASS_RULES is set
523
+ # Skip all checks when BYPASS_RULES is set
385
524
  if [ "$BYPASS_RULES" = "true" ] || [ "$BYPASS_RULES" = "1" ]; then
386
- echo "⚡ BYPASS_RULES detected — skipping lint-staged and Code Guardian."
525
+ echo "⚡ BYPASS_RULES detected — skipping checks."
387
526
  exit 0
388
527
  fi
389
528
 
390
529
  # Run lint-staged (ESLint + Prettier auto-fix)
391
530
  npx lint-staged
392
531
 
393
- # Run Code Guardian custom rules
532
+ # Run Code Guardian rules
394
533
  npm run precommit-check
395
534
  # --- code-guardian-hook-end ---
396
535
  `;
397
536
 
398
- const preCommitPath = path.join(huskyDir, 'pre-commit');
399
537
  if (fs.existsSync(preCommitPath)) {
400
538
  const existing = fs.readFileSync(preCommitPath, 'utf-8');
401
539
  if (!existing.includes('code-guardian-hook-start')) {
@@ -409,7 +547,7 @@ npm run precommit-check
409
547
  // Create new hook file
410
548
  const preCommitHook = `#!/usr/bin/env sh\n${preCommitContent}`;
411
549
  fs.writeFileSync(preCommitPath, preCommitHook);
412
- console.log(` ${c.green('✓')} Created .husky/pre-commit (lint-staged + Code Guardian)`);
550
+ console.log(` ${c.green('✓')} Created .husky/pre-commit (lint-staged only)`);
413
551
  }
414
552
  try { fs.chmodSync(preCommitPath, '755'); } catch {}
415
553
 
@@ -470,33 +608,50 @@ function buildInstallCommand(packageManager: string, deps: string[]): string {
470
608
 
471
609
  // ── Update package.json ─────────────────────────────────────────────────────
472
610
 
473
- function updatePackageJson(cwd: string, project: any): void {
611
+ function updatePackageJson(cwd: string, project: any, manifest?: any): void {
474
612
  const packageJsonPath = path.join(cwd, 'package.json');
475
613
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
476
614
  const packageName = '@hatem427/code-guard-ci';
477
615
 
478
616
  packageJson.scripts = packageJson.scripts || {};
479
617
 
618
+ // Helper to track script changes in manifest
619
+ const setScript = (name: string, value: string) => {
620
+ if (manifest) {
621
+ if (packageJson.scripts[name] && packageJson.scripts[name] !== value) {
622
+ // Script existed with different value — record original
623
+ const { recordModifiedScript } = requireUtil('manifest');
624
+ recordModifiedScript(manifest, name, packageJson.scripts[name]);
625
+ } else if (!packageJson.scripts[name]) {
626
+ // New script
627
+ const { recordAddedScript } = requireUtil('manifest');
628
+ recordAddedScript(manifest, name);
629
+ }
630
+ }
631
+ packageJson.scripts[name] = value;
632
+ };
633
+
480
634
  // Core Code Guardian scripts
481
- packageJson.scripts['precommit-check'] = 'code-guard check';
482
- packageJson.scripts['generate-doc'] = 'code-guard doc';
483
- packageJson.scripts['generate-pr-checklist'] = 'code-guard checklist';
484
- packageJson.scripts['validate'] = 'code-guard validate';
485
- packageJson.scripts['prepare'] = 'husky';
635
+ setScript('precommit-check', 'code-guard check');
636
+ setScript('check-vuln', 'code-guard check-vuln');
637
+ setScript('generate-doc', 'code-guard doc');
638
+ setScript('generate-pr-checklist', 'code-guard checklist');
639
+ setScript('validate', 'code-guard validate');
640
+ setScript('prepare', 'husky');
486
641
 
487
642
  // Lint and format scripts
488
643
  const extensions = project.usesTypeScript ? '.ts,.tsx,.js,.jsx' : '.js,.jsx';
489
- packageJson.scripts['lint'] = `eslint . --ext ${extensions}`;
490
- packageJson.scripts['lint:fix'] = `eslint . --ext ${extensions} --fix`;
491
- packageJson.scripts['format'] = 'prettier --write .';
492
- packageJson.scripts['format:check'] = 'prettier --check .';
644
+ setScript('lint', `eslint . --ext ${extensions}`);
645
+ setScript('lint:fix', `eslint . --ext ${extensions} --fix`);
646
+ setScript('format', 'prettier --write .');
647
+ setScript('format:check', 'prettier --check .');
493
648
 
494
649
  // Security scripts
495
- packageJson.scripts['set-bypass-password'] = `node node_modules/${packageName}/dist/scripts/set-bypass-password.js`;
496
- packageJson.scripts['set-admin-password'] = `node node_modules/${packageName}/dist/scripts/set-admin-password.js`;
497
- packageJson.scripts['delete-bypass-logs'] = `node node_modules/${packageName}/dist/scripts/delete-bypass-logs.js`;
498
- packageJson.scripts['view-bypass-log'] = `node node_modules/${packageName}/dist/scripts/view-bypass-log.js`;
499
- packageJson.scripts['auto-fix'] = `node node_modules/${packageName}/dist/scripts/auto-fix.js`;
650
+ setScript('set-bypass-password', `node node_modules/${packageName}/dist/scripts/set-bypass-password.js`);
651
+ setScript('set-admin-password', `node node_modules/${packageName}/dist/scripts/set-admin-password.js`);
652
+ setScript('delete-bypass-logs', `node node_modules/${packageName}/dist/scripts/delete-bypass-logs.js`);
653
+ setScript('view-bypass-log', `node node_modules/${packageName}/dist/scripts/view-bypass-log.js`);
654
+ setScript('auto-fix', `node node_modules/${packageName}/dist/scripts/auto-fix.js`);
500
655
 
501
656
  fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
502
657
  console.log(` ${c.green('✓')} Added npm scripts (lint, format, validate, precommit-check, etc.)`);
@@ -504,18 +659,35 @@ function updatePackageJson(cwd: string, project: any): void {
504
659
 
505
660
  // ── Copy Code Guardian files ────────────────────────────────────────────────
506
661
 
507
- function copyCodeGuardianFiles(cwd: string): void {
662
+ function copyCodeGuardianFiles(cwd: string, manifest?: any, projectType?: string): void {
508
663
  // Determine source directories (handles both dev and built package layouts)
509
664
  const distDir = path.join(__dirname, '..');
510
665
  const pkgRootDir = path.join(__dirname, '..', '..');
511
666
 
512
- // Copy config files
667
+ // Copy config files — only guidelines (always) + detected framework config
513
668
  const configDir = path.join(cwd, 'config');
514
669
  if (!fs.existsSync(configDir)) {
515
670
  fs.mkdirSync(configDir, { recursive: true });
516
671
  }
517
672
 
518
- const configFiles = ['guidelines.config.ts', 'angular.config.ts', 'react.config.ts', 'nextjs.config.ts'];
673
+ // Map project type to its config file
674
+ const frameworkConfigMap: Record<string, string> = {
675
+ angular: 'angular.config.ts',
676
+ react: 'react.config.ts',
677
+ nextjs: 'nextjs.config.ts',
678
+ node: 'node.config.ts',
679
+ };
680
+
681
+ // Always include guidelines + detected framework config (+ react for nextjs since it inherits)
682
+ const configFiles = ['guidelines.config.ts'];
683
+ if (projectType && frameworkConfigMap[projectType]) {
684
+ configFiles.push(frameworkConfigMap[projectType]);
685
+ }
686
+ // Next.js inherits React rules, so include react config too
687
+ if (projectType === 'nextjs' && !configFiles.includes('react.config.ts')) {
688
+ configFiles.push('react.config.ts');
689
+ }
690
+
519
691
  const sourceConfigDirs = [path.join(distDir, 'config'), path.join(pkgRootDir, 'config')];
520
692
 
521
693
  for (const file of configFiles) {
@@ -530,6 +702,10 @@ function copyCodeGuardianFiles(cwd: string): void {
530
702
  const dest = path.join(configDir, file);
531
703
  if (src && !fs.existsSync(dest)) {
532
704
  fs.copyFileSync(src, dest);
705
+ if (manifest) {
706
+ const { recordCreatedFile } = requireUtil('manifest');
707
+ recordCreatedFile(manifest, `config/${file}`);
708
+ }
533
709
  console.log(` ${c.green('✓')} ${file}`);
534
710
  }
535
711
  }
@@ -555,6 +731,10 @@ function copyCodeGuardianFiles(cwd: string): void {
555
731
  const dest = path.join(templatesDir, file);
556
732
  if (src && !fs.existsSync(dest)) {
557
733
  fs.copyFileSync(src, dest);
734
+ if (manifest) {
735
+ const { recordCreatedFile } = requireUtil('manifest');
736
+ recordCreatedFile(manifest, `templates/${file}`);
737
+ }
558
738
  console.log(` ${c.green('✓')} ${file}`);
559
739
  }
560
740
  }
@@ -564,6 +744,10 @@ function copyCodeGuardianFiles(cwd: string): void {
564
744
  if (!fs.existsSync(docsDir)) {
565
745
  fs.mkdirSync(docsDir, { recursive: true });
566
746
  fs.writeFileSync(path.join(docsDir, '.gitkeep'), '');
747
+ if (manifest) {
748
+ const { recordCreatedFile } = requireUtil('manifest');
749
+ recordCreatedFile(manifest, 'docs/features/.gitkeep');
750
+ }
567
751
  console.log(` ${c.green('✓')} Created docs/features/`);
568
752
  }
569
753
  }
@@ -931,11 +1115,22 @@ async function uninstallCodeGuard(): Promise<void> {
931
1115
  showBanner();
932
1116
  console.log(c.bold('🗑️ Code Guardian Uninstall\n'));
933
1117
 
934
- const filesToRemove = [
1118
+ const cwd = process.cwd();
1119
+
1120
+ // ── Load manifest for smart uninstall ────────────────────────────────
1121
+ const { loadManifest, removeMarkedContent } = requireUtil('manifest');
1122
+ const manifest = loadManifest(cwd);
1123
+
1124
+ // Hardcoded fallbacks for files that Code Guardian always creates
1125
+ const fallbackFilesToRemove = [
935
1126
  '.husky/_/husky.sh',
1127
+ 'eslint.config.mjs',
936
1128
  'eslint.config.js',
1129
+ '.prettierrc.json',
1130
+ '.prettierignore',
937
1131
  'prettier.config.js',
938
1132
  '.editorconfig',
1133
+ '.lintstagedrc.json',
939
1134
  'lint-staged.config.js',
940
1135
  'tsconfig.strict.json',
941
1136
  ];
@@ -944,11 +1139,11 @@ async function uninstallCodeGuard(): Promise<void> {
944
1139
  'config',
945
1140
  'templates',
946
1141
  'docs',
947
- '.code-guardian',
948
1142
  ];
949
1143
 
950
1144
  const scriptsToRemove = [
951
1145
  'precommit-check',
1146
+ 'check-vuln',
952
1147
  'auto-fix',
953
1148
  'generate-doc',
954
1149
  'generate-pr-checklist',
@@ -956,12 +1151,17 @@ async function uninstallCodeGuard(): Promise<void> {
956
1151
  'set-admin-password',
957
1152
  'view-bypass-log',
958
1153
  'delete-bypass-logs',
1154
+ 'lint',
1155
+ 'lint:fix',
1156
+ 'format',
1157
+ 'format:check',
1158
+ 'validate',
1159
+ 'prepare',
959
1160
  ];
960
1161
 
961
1162
  // ── Detect AI config files via registry ──────────────────────────────
962
1163
  const { defaultRegistry } = requireUtil('ai-config-registry');
963
1164
  const aiTemplates = defaultRegistry.getAll();
964
- const cwd = process.cwd();
965
1165
 
966
1166
  // Classify each AI config: full-delete vs strip-only
967
1167
  interface AIConfigAction {
@@ -969,7 +1169,7 @@ async function uninstallCodeGuard(): Promise<void> {
969
1169
  filePath: string;
970
1170
  relativePath: string;
971
1171
  marker: string;
972
- action: 'delete' | 'strip'; // delete = whole file is ours, strip = remove only our section
1172
+ action: 'delete' | 'strip';
973
1173
  }
974
1174
  const aiActions: AIConfigAction[] = [];
975
1175
 
@@ -981,48 +1181,111 @@ async function uninstallCodeGuard(): Promise<void> {
981
1181
  if (!fs.existsSync(filePath)) continue;
982
1182
 
983
1183
  const content = fs.readFileSync(filePath, 'utf-8');
984
- if (!content.includes(t.marker)) continue; // not ours
985
-
986
- // Determine if the file was created by us or if we appended
987
- // If appended, there will be a --- separator before our marker with user content above
988
- const markerIdx = content.indexOf(t.marker);
989
- const beforeMarker = content.substring(0, markerIdx).trimEnd();
990
-
991
- // Check if there's substantial user content before our section
992
- // When we append we add "\n\n---\n\n" before our content
993
- // When we create, the marker is at or near the top (possibly after frontmatter)
994
- const stripped = beforeMarker.replace(/^---[\s\S]*?---/, '').trim(); // strip MDC frontmatter
995
- const hasUserContent = stripped.length > 0 && !stripped.endsWith('---');
1184
+ if (!content.includes(t.marker)) continue;
996
1185
 
997
- // A more robust check: if content before marker (minus separator) has real text
998
- const withoutTrailingSep = beforeMarker.replace(/\n*---\s*$/, '').trim();
999
- const realUserContent = withoutTrailingSep.replace(/^---[\s\S]*?---/, '').trim(); // strip frontmatter
1000
-
1001
- if (realUserContent.length > 0) {
1002
- aiActions.push({ name: t.name, filePath, relativePath, marker: t.marker, action: 'strip' });
1186
+ // Use manifest to determine action if available
1187
+ if (manifest && manifest.files[relativePath]) {
1188
+ const record = manifest.files[relativePath];
1189
+ if (record.action === 'created') {
1190
+ aiActions.push({ name: t.name, filePath, relativePath, marker: t.marker, action: 'delete' });
1191
+ } else {
1192
+ aiActions.push({ name: t.name, filePath, relativePath, marker: t.marker, action: 'strip' });
1193
+ }
1003
1194
  } else {
1004
- aiActions.push({ name: t.name, filePath, relativePath, marker: t.marker, action: 'delete' });
1195
+ // Fallback: detect by checking for user content before marker
1196
+ const markerIdx = content.indexOf(t.marker);
1197
+ const beforeMarker = content.substring(0, markerIdx).trimEnd();
1198
+ const withoutTrailingSep = beforeMarker.replace(/\n*---\s*$/, '').trim();
1199
+ const realUserContent = withoutTrailingSep.replace(/^---[\s\S]*?---/, '').trim();
1200
+
1201
+ if (realUserContent.length > 0) {
1202
+ aiActions.push({ name: t.name, filePath, relativePath, marker: t.marker, action: 'strip' });
1203
+ } else {
1204
+ aiActions.push({ name: t.name, filePath, relativePath, marker: t.marker, action: 'delete' });
1205
+ }
1005
1206
  }
1006
1207
  }
1007
1208
 
1209
+ // Determine which files to remove (use manifest if available, fallback to hardcoded)
1210
+ let filesToProcess: string[];
1211
+ if (manifest) {
1212
+ // Use manifest — it knows exactly what we created vs modified
1213
+ filesToProcess = Object.keys(manifest.files).filter(f => {
1214
+ // Skip AI files — handled separately
1215
+ return !aiActions.some(a => a.relativePath === f);
1216
+ });
1217
+ } else {
1218
+ filesToProcess = fallbackFilesToRemove;
1219
+ }
1220
+
1008
1221
  // Check what exists
1009
- const existingFiles = filesToRemove.filter(f => fs.existsSync(path.join(cwd, f)));
1222
+ const existingFiles = filesToProcess.filter(f => fs.existsSync(path.join(cwd, f)));
1010
1223
  const existingDirs = dirsToRemove.filter(d => fs.existsSync(path.join(cwd, d)));
1011
1224
 
1012
- if (existingFiles.length === 0 && existingDirs.length === 0 && aiActions.length === 0) {
1225
+ // Also check for backup files that need restoring
1226
+ const backupsToRestore: Array<{ original: string; backup: string }> = [];
1227
+ if (manifest && manifest.backups) {
1228
+ for (const [original, backup] of Object.entries(manifest.backups) as [string, string][]) {
1229
+ if (fs.existsSync(path.join(cwd, backup))) {
1230
+ backupsToRestore.push({ original, backup });
1231
+ }
1232
+ }
1233
+ }
1234
+ // Also find any .backup files from generators (eslint, prettier create them)
1235
+ for (const file of existingFiles) {
1236
+ const backupPath = path.join(cwd, file + '.backup');
1237
+ if (fs.existsSync(backupPath) && !backupsToRestore.some(b => b.backup === file + '.backup')) {
1238
+ backupsToRestore.push({ original: file, backup: file + '.backup' });
1239
+ }
1240
+ }
1241
+ // Check common backup locations from generators
1242
+ const commonBackups = [
1243
+ '.eslintrc.backup', '.eslintrc.js.backup', '.eslintrc.cjs.backup', '.eslintrc.json.backup',
1244
+ 'eslint.config.js.backup', 'eslint.config.mjs.backup', 'eslint.config.cjs.backup',
1245
+ '.prettierrc.backup', '.prettierrc.js.backup', '.prettierrc.cjs.backup', '.prettierrc.json.backup',
1246
+ '.prettierrc.yml.backup', '.prettierrc.yaml.backup', '.prettierrc.toml.backup', 'prettier.config.js.backup',
1247
+ '.eslintignore.backup',
1248
+ ];
1249
+ for (const backup of commonBackups) {
1250
+ if (fs.existsSync(path.join(cwd, backup))) {
1251
+ const original = backup.replace(/\.backup$/, '');
1252
+ if (!backupsToRestore.some(b => b.backup === backup)) {
1253
+ backupsToRestore.push({ original, backup });
1254
+ }
1255
+ }
1256
+ }
1257
+
1258
+ if (existingFiles.length === 0 && existingDirs.length === 0 && aiActions.length === 0 && backupsToRestore.length === 0) {
1013
1259
  console.log(c.yellow('⚠️ No Code Guardian files found to remove.\n'));
1014
1260
  return;
1015
1261
  }
1016
1262
 
1017
- // Show what will be removed
1263
+ // Show what will be done
1018
1264
  console.log(c.bold('The following will be removed:\n'));
1019
-
1020
- if (existingFiles.length > 0) {
1021
- console.log(c.bold('Files:'));
1022
- existingFiles.forEach(f => console.log(` ${c.red('✗')} ${f}`));
1265
+
1266
+ // Separate created vs modified files
1267
+ const createdFiles: string[] = [];
1268
+ const modifiedFiles: string[] = [];
1269
+ for (const f of existingFiles) {
1270
+ if (manifest && manifest.files[f] && manifest.files[f].action === 'modified') {
1271
+ modifiedFiles.push(f);
1272
+ } else {
1273
+ createdFiles.push(f);
1274
+ }
1275
+ }
1276
+
1277
+ if (createdFiles.length > 0) {
1278
+ console.log(c.bold('Files (created by Code Guardian — will be deleted):'));
1279
+ createdFiles.forEach(f => console.log(` ${c.red('✗')} ${f}`));
1023
1280
  console.log('');
1024
1281
  }
1025
-
1282
+
1283
+ if (modifiedFiles.length > 0) {
1284
+ console.log(c.bold('Files (modified — Code Guardian content will be stripped):'));
1285
+ modifiedFiles.forEach(f => console.log(` ${c.yellow('⚠')} ${f} ${c.dim('(your content preserved)')}`));
1286
+ console.log('');
1287
+ }
1288
+
1026
1289
  if (existingDirs.length > 0) {
1027
1290
  console.log(c.bold('Directories:'));
1028
1291
  existingDirs.forEach(d => console.log(` ${c.red('✗')} ${d}/`));
@@ -1044,6 +1307,12 @@ async function uninstallCodeGuard(): Promise<void> {
1044
1307
  console.log('');
1045
1308
  }
1046
1309
 
1310
+ if (backupsToRestore.length > 0) {
1311
+ console.log(c.bold('Backups to restore (original configs):'));
1312
+ backupsToRestore.forEach(b => console.log(` ${c.blue('↩')} ${b.backup} → ${b.original}`));
1313
+ console.log('');
1314
+ }
1315
+
1047
1316
  if (!hasFlag('yes') && !hasFlag('y')) {
1048
1317
  const readline = require('readline');
1049
1318
  const rl = readline.createInterface({
@@ -1067,48 +1336,71 @@ async function uninstallCodeGuard(): Promise<void> {
1067
1336
  let removedCount = 0;
1068
1337
  console.log('');
1069
1338
 
1070
- // Backup AI config files before modifying
1071
- const backupDir = path.join(cwd, '.code-guardian-backup-' + Date.now());
1072
- let hasBackup = false;
1339
+ // ── Phase 1: Handle created files (delete) ──────────────────────────
1340
+ if (createdFiles.length > 0) {
1341
+ console.log(c.bold('Removing created files...\n'));
1342
+ for (const file of createdFiles) {
1343
+ const fullPath = path.join(cwd, file);
1344
+ if (fs.existsSync(fullPath)) {
1345
+ try {
1346
+ fs.unlinkSync(fullPath);
1347
+ console.log(` ${c.green('✓')} Removed ${file}`);
1348
+ removedCount++;
1349
+ } catch (error: any) {
1350
+ console.log(` ${c.red('✗')} Failed to remove ${file}: ${error.message}`);
1351
+ }
1352
+ }
1353
+ }
1354
+ console.log('');
1355
+ }
1073
1356
 
1074
- if (aiActions.length > 0 && !hasFlag('no-backup')) {
1075
- console.log(c.bold('Creating backup...\n'));
1076
- fs.mkdirSync(backupDir, { recursive: true });
1077
-
1078
- for (const action of aiActions) {
1079
- if (fs.existsSync(action.filePath)) {
1357
+ // ── Phase 2: Handle modified files (strip marked content) ───────────
1358
+ if (modifiedFiles.length > 0) {
1359
+ console.log(c.bold('Stripping Code Guardian content from modified files...\n'));
1360
+ for (const file of modifiedFiles) {
1361
+ const fullPath = path.join(cwd, file);
1362
+ if (fs.existsSync(fullPath)) {
1080
1363
  try {
1081
- const destPath = path.join(backupDir, action.relativePath);
1082
- const destDir = path.dirname(destPath);
1083
- if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
1084
- fs.cpSync(action.filePath, destPath);
1085
- console.log(` ${c.blue('📦')} Backed up ${action.relativePath}`);
1086
- hasBackup = true;
1364
+ const record = manifest?.files[file];
1365
+ const stripped = removeMarkedContent(fullPath, record?.marker);
1366
+ if (stripped) {
1367
+ console.log(` ${c.green('✓')} Stripped Code Guardian content from ${file}`);
1368
+ } else {
1369
+ console.log(` ${c.dim('○')} No marked content found in ${file}`);
1370
+ }
1371
+ removedCount++;
1087
1372
  } catch (error: any) {
1088
- console.log(` ${c.yellow('')} Failed to backup ${action.relativePath}: ${error.message}`);
1373
+ console.log(` ${c.red('')} Failed to clean ${file}: ${error.message}`);
1089
1374
  }
1090
1375
  }
1091
1376
  }
1092
1377
  console.log('');
1093
1378
  }
1094
1379
 
1095
- // Remove config files created entirely by Code Guardian
1096
- console.log(c.bold('Removing files...\n'));
1097
- for (const file of filesToRemove) {
1098
- const fullPath = path.join(cwd, file);
1099
- if (fs.existsSync(fullPath)) {
1380
+ // ── Phase 3: Restore backup files ───────────────────────────────────
1381
+ if (backupsToRestore.length > 0) {
1382
+ console.log(c.bold('Restoring original config files...\n'));
1383
+ for (const { original, backup } of backupsToRestore) {
1384
+ const backupFullPath = path.join(cwd, backup);
1385
+ const originalFullPath = path.join(cwd, original);
1100
1386
  try {
1101
- fs.unlinkSync(fullPath);
1102
- console.log(` ${c.green('✓')} Removed ${file}`);
1387
+ // Remove the Code Guardian version first (if it exists)
1388
+ if (fs.existsSync(originalFullPath)) {
1389
+ fs.unlinkSync(originalFullPath);
1390
+ }
1391
+ // Restore the backup as the original
1392
+ fs.renameSync(backupFullPath, originalFullPath);
1393
+ console.log(` ${c.green('✓')} Restored ${original} from ${backup}`);
1103
1394
  removedCount++;
1104
1395
  } catch (error: any) {
1105
- console.log(` ${c.red('✗')} Failed to remove ${file}: ${error.message}`);
1396
+ console.log(` ${c.red('✗')} Failed to restore ${original}: ${error.message}`);
1106
1397
  }
1107
1398
  }
1399
+ console.log('');
1108
1400
  }
1109
1401
 
1110
- // Remove directories
1111
- console.log(c.bold('\nRemoving directories...\n'));
1402
+ // ── Phase 4: Remove directories ─────────────────────────────────────
1403
+ console.log(c.bold('Removing directories...\n'));
1112
1404
  for (const dir of dirsToRemove) {
1113
1405
  const fullPath = path.join(cwd, dir);
1114
1406
  if (fs.existsSync(fullPath)) {
@@ -1122,12 +1414,11 @@ async function uninstallCodeGuard(): Promise<void> {
1122
1414
  }
1123
1415
  }
1124
1416
 
1125
- // Handle AI config files intelligently
1417
+ // ── Phase 5: Handle AI config files intelligently ───────────────────
1126
1418
  console.log(c.bold('\nCleaning AI config files...\n'));
1127
1419
  for (const action of aiActions) {
1128
1420
  try {
1129
1421
  if (action.action === 'delete') {
1130
- // Entire file was created by Code Guardian → delete it
1131
1422
  fs.unlinkSync(action.filePath);
1132
1423
  console.log(` ${c.green('✓')} Removed ${action.relativePath}`);
1133
1424
  removedCount++;
@@ -1139,7 +1430,6 @@ async function uninstallCodeGuard(): Promise<void> {
1139
1430
  const remaining = fs.readdirSync(parentDir);
1140
1431
  if (remaining.length === 0) {
1141
1432
  fs.rmdirSync(parentDir);
1142
- // Check grandparent too (e.g., .windsurf/rules → .windsurf)
1143
1433
  const grandparent = path.dirname(parentDir);
1144
1434
  if (grandparent !== cwd && fs.existsSync(grandparent)) {
1145
1435
  const gpRemaining = fs.readdirSync(grandparent);
@@ -1154,18 +1444,13 @@ async function uninstallCodeGuard(): Promise<void> {
1154
1444
  const markerIdx = content.indexOf(action.marker);
1155
1445
  if (markerIdx === -1) continue;
1156
1446
 
1157
- // Find the separator (--- on its own line) before the marker
1158
- // When we append, we add "\n\n---\n\n" before our content
1159
1447
  let cutStart = markerIdx;
1160
1448
  const beforeMarker = content.substring(0, markerIdx);
1161
-
1162
- // Look for the --- separator we added
1163
1449
  const sepMatch = beforeMarker.match(/\n*\s*---\s*\n*$/);
1164
1450
  if (sepMatch && sepMatch.index !== undefined) {
1165
1451
  cutStart = sepMatch.index;
1166
1452
  }
1167
1453
 
1168
- // Remove from cutStart to end of file (our content is always appended at the end)
1169
1454
  const cleaned = content.substring(0, cutStart).trimEnd() + '\n';
1170
1455
  fs.writeFileSync(action.filePath, cleaned);
1171
1456
  console.log(` ${c.green('✓')} Stripped Code Guardian rules from ${action.relativePath} ${c.dim('(your content preserved)')}`);
@@ -1176,7 +1461,7 @@ async function uninstallCodeGuard(): Promise<void> {
1176
1461
  }
1177
1462
  }
1178
1463
 
1179
- // Clean up .husky hooks smartly
1464
+ // ── Phase 6: Clean up .husky hooks smartly ──────────────────────────
1180
1465
  console.log(c.bold('\nCleaning git hooks...\n'));
1181
1466
  const huskyDir = path.join(cwd, '.husky');
1182
1467
  const huskyHookFiles = ['pre-commit'];
@@ -1198,7 +1483,6 @@ async function uninstallCodeGuard(): Promise<void> {
1198
1483
  const beforeHook = content.substring(0, startIdx).trimEnd();
1199
1484
  const afterHook = content.substring(endIdx + HOOK_END.length).trimEnd();
1200
1485
 
1201
- // Check if there's real user content outside of our markers
1202
1486
  const remaining = (beforeHook.replace(/^#!\/usr\/bin\/env\s+sh\s*/, '').trim() + afterHook.trim()).trim();
1203
1487
 
1204
1488
  if (remaining.length === 0) {
@@ -1229,23 +1513,47 @@ async function uninstallCodeGuard(): Promise<void> {
1229
1513
  } catch {}
1230
1514
  }
1231
1515
 
1232
- // Remove scripts from package.json
1516
+ // ── Phase 7: Clean package.json scripts ─────────────────────────────
1233
1517
  console.log(c.bold('\nCleaning package.json scripts...\n'));
1234
- const packageJsonPath = path.join(cwd, 'package.json'); if (fs.existsSync(packageJsonPath)) {
1518
+ const packageJsonPath = path.join(cwd, 'package.json');
1519
+ if (fs.existsSync(packageJsonPath)) {
1235
1520
  try {
1236
1521
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
1237
- let scriptRemoved = false;
1522
+ let scriptChanged = false;
1523
+
1524
+ if (manifest && manifest.packageJsonScripts) {
1525
+ // Restore modified scripts to original values
1526
+ for (const [scriptName, originalValue] of Object.entries(manifest.packageJsonScripts.modified)) {
1527
+ if (packageJson.scripts && packageJson.scripts[scriptName]) {
1528
+ packageJson.scripts[scriptName] = originalValue;
1529
+ console.log(` ${c.green('✓')} Restored script: ${scriptName} ${c.dim('(original value)')}`);
1530
+ scriptChanged = true;
1531
+ removedCount++;
1532
+ }
1533
+ }
1238
1534
 
1239
- for (const script of scriptsToRemove) {
1240
- if (packageJson.scripts && packageJson.scripts[script]) {
1241
- delete packageJson.scripts[script];
1242
- console.log(` ${c.green('✓')} Removed script: ${script}`);
1243
- scriptRemoved = true;
1244
- removedCount++;
1535
+ // Remove scripts that were added by Code Guardian
1536
+ for (const scriptName of manifest.packageJsonScripts.added) {
1537
+ if (packageJson.scripts && packageJson.scripts[scriptName]) {
1538
+ delete packageJson.scripts[scriptName];
1539
+ console.log(` ${c.green('✓')} Removed script: ${scriptName}`);
1540
+ scriptChanged = true;
1541
+ removedCount++;
1542
+ }
1543
+ }
1544
+ } else {
1545
+ // Fallback: remove known Code Guardian scripts
1546
+ for (const script of scriptsToRemove) {
1547
+ if (packageJson.scripts && packageJson.scripts[script]) {
1548
+ delete packageJson.scripts[script];
1549
+ console.log(` ${c.green('✓')} Removed script: ${script}`);
1550
+ scriptChanged = true;
1551
+ removedCount++;
1552
+ }
1245
1553
  }
1246
1554
  }
1247
1555
 
1248
- if (scriptRemoved) {
1556
+ if (scriptChanged) {
1249
1557
  fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
1250
1558
  console.log(` ${c.green('✓')} Updated package.json`);
1251
1559
  } else {
@@ -1256,19 +1564,69 @@ async function uninstallCodeGuard(): Promise<void> {
1256
1564
  }
1257
1565
  }
1258
1566
 
1567
+ // ── Phase 8: Remove .code-guardian directory (including manifest) ────
1568
+ const codeGuardianDir = path.join(cwd, '.code-guardian');
1569
+ if (fs.existsSync(codeGuardianDir)) {
1570
+ try {
1571
+ fs.rmSync(codeGuardianDir, { recursive: true, force: true });
1572
+ console.log(`\n ${c.green('✓')} Removed .code-guardian/`);
1573
+ removedCount++;
1574
+ } catch (error: any) {
1575
+ console.log(` ${c.red('✗')} Failed to remove .code-guardian/: ${error.message}`);
1576
+ }
1577
+ }
1578
+
1259
1579
  console.log('');
1260
1580
  console.log(c.green(`✅ Cleanup complete! Removed ${removedCount} item(s).`));
1261
-
1262
- if (hasBackup) {
1263
- console.log(c.blue(`📦 Backup saved to: ${path.basename(backupDir)}/`));
1264
- }
1265
-
1266
1581
  console.log('');
1267
1582
  console.log(c.dim('To completely remove the package, run:'));
1268
1583
  console.log(c.dim(' npm uninstall @hatem427/code-guard-ci'));
1269
1584
  console.log('');
1270
1585
  }
1271
1586
 
1587
+ // ── Vulnerability Check ────────────────────────────────────────────────────
1588
+
1589
+ function runVulnerabilityCheck(): void {
1590
+ showBanner();
1591
+ console.log(c.bold('🔍 Code Guardian — Vulnerability Scan\n'));
1592
+
1593
+ const cwd = process.cwd();
1594
+ let hasIssues = false;
1595
+
1596
+ // ── npm audit ──────────────────────────────────────────────────────────────
1597
+ console.log(c.bold('📦 Running npm audit...'));
1598
+ try {
1599
+ execSync('npm audit --audit-level=moderate', { stdio: 'inherit', cwd });
1600
+ console.log(` ${c.green('✓')} No moderate/high vulnerabilities found\n`);
1601
+ } catch {
1602
+ console.log(` ${c.yellow('⚠')} npm audit found vulnerabilities. Run: npm audit fix\n`);
1603
+ hasIssues = true;
1604
+ }
1605
+
1606
+ // ── RetireJS ───────────────────────────────────────────────────────────────
1607
+ console.log(c.bold('🔒 Checking for retired/vulnerable JS libraries (RetireJS)...'));
1608
+ const retireResult = spawnSync('npx', ['retire', '--path', '.', '--outputformat', 'text'], {
1609
+ encoding: 'utf-8',
1610
+ cwd,
1611
+ });
1612
+ if (retireResult.error) {
1613
+ console.log(c.dim(' ○ RetireJS not available — skipping (install with: npm i -g retire)\n'));
1614
+ } else if (retireResult.status !== 0) {
1615
+ console.log(c.yellow(` ⚠ RetireJS found issues:\n${retireResult.stdout || retireResult.stderr}\n`));
1616
+ hasIssues = true;
1617
+ } else {
1618
+ console.log(` ${c.green('✓')} No retired libraries found\n`);
1619
+ }
1620
+
1621
+ // ── Summary ────────────────────────────────────────────────────────────────
1622
+ if (hasIssues) {
1623
+ console.log(c.yellow('⚠️ Vulnerabilities found. Review and fix before deployment.'));
1624
+ process.exit(1);
1625
+ } else {
1626
+ console.log(c.green('✅ No vulnerabilities found!'));
1627
+ }
1628
+ }
1629
+
1272
1630
  // ── Main ────────────────────────────────────────────────────────────────────
1273
1631
 
1274
1632
  (async () => {
@@ -1281,6 +1639,10 @@ async function uninstallCodeGuard(): Promise<void> {
1281
1639
  runCheck();
1282
1640
  break;
1283
1641
 
1642
+ case 'check-vuln':
1643
+ runVulnerabilityCheck();
1644
+ break;
1645
+
1284
1646
  case 'report':
1285
1647
  runReport();
1286
1648
  break;