@hatem427/code-guard-ci 3.5.3 → 3.5.5

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