@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.
@@ -116,7 +116,6 @@ ${c.bold('Init Options:')}
116
116
 
117
117
  ${c.bold('Uninstall Options:')}
118
118
  --yes, -y Skip confirmation prompt
119
- --no-backup Don't create backup of AI configs
120
119
 
121
120
  ${c.bold('Examples:')}
122
121
  ${c.dim('# Full automatic setup')}
@@ -134,11 +133,11 @@ ${c.bold('Examples:')}
134
133
  ${c.dim('# Generate docs')}
135
134
  code-guard doc --name="user-card" --type=ui
136
135
 
137
- ${c.dim('# Clean uninstall with backup')}
136
+ ${c.dim('# Clean uninstall')}
138
137
  code-guard uninstall
139
138
 
140
139
  ${c.dim('# Force uninstall without prompts')}
141
- code-guard uninstall --yes --no-backup
140
+ code-guard uninstall --yes
142
141
  `);
143
142
  }
144
143
  function showVersion() {
@@ -159,6 +158,10 @@ function initProject() {
159
158
  const isDryRun = hasFlag('dry-run');
160
159
  const skipAI = hasFlag('skip-ai');
161
160
  const skipHooks = hasFlag('skip-hooks');
161
+ // ── Initialize manifest for tracking ────────────────────────────────────
162
+ const { createManifest, saveManifest, loadManifest, recordCreatedFile, recordModifiedFile, recordBackup, recordAddedScript, recordModifiedScript, } = requireUtil('manifest');
163
+ // Load existing manifest or create new one
164
+ const manifest = loadManifest(cwd) || createManifest();
162
165
  // ── Step 1: Detect Framework ────────────────────────────────────────────
163
166
  console.log(c.bold('📡 Step 1: Detecting project...'));
164
167
  // Dynamic import (handles both dev and built)
@@ -195,9 +198,23 @@ function initProject() {
195
198
  // ── Step 3: Generate ESLint Config ───────────────────────────────────────
196
199
  console.log(c.bold('⚡ Step 3: Configuring ESLint...'));
197
200
  try {
201
+ const eslintFile = 'eslint.config.mjs';
202
+ const eslintPath = path.join(cwd, eslintFile);
203
+ const eslintExisted = fs.existsSync(eslintPath);
198
204
  const { generateEslintConfig } = requireGenerator('eslint-generator');
199
205
  generateEslintConfig(project);
200
- console.log(` ${c.green('✓')} Created .eslintrc.json`);
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
+ }
214
+ else {
215
+ recordCreatedFile(manifest, eslintFile);
216
+ }
217
+ console.log(` ${c.green('✓')} Created eslint.config.mjs`);
201
218
  console.log(` ${c.green('✓')} Created .eslintignore\n`);
202
219
  }
203
220
  catch (error) {
@@ -206,8 +223,31 @@ function initProject() {
206
223
  // ── Step 4: Generate Prettier Config ──────────────────────────────────────
207
224
  console.log(c.bold('🎨 Step 4: Configuring Prettier...'));
208
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);
209
232
  const { generatePrettierConfig } = requireGenerator('prettier-generator');
210
233
  generatePrettierConfig(project);
234
+ // Track: prettier-generator already creates .backup files for existing configs
235
+ if (prettierExisted) {
236
+ recordModifiedFile(manifest, prettierFile, 'prettier');
237
+ // Check for any backed-up prettier files
238
+ const prettierFiles = ['.prettierrc', '.prettierrc.js', '.prettierrc.cjs', '.prettierrc.json', '.prettierrc.yml', '.prettierrc.yaml', '.prettierrc.toml', 'prettier.config.js'];
239
+ for (const pf of prettierFiles) {
240
+ if (fs.existsSync(path.join(cwd, pf + '.backup'))) {
241
+ recordBackup(manifest, pf, pf + '.backup');
242
+ }
243
+ }
244
+ }
245
+ else {
246
+ recordCreatedFile(manifest, prettierFile);
247
+ }
248
+ if (!ignoreExisted && fs.existsSync(ignorePath)) {
249
+ recordCreatedFile(manifest, ignoreFile);
250
+ }
211
251
  console.log(` ${c.green('✓')} Created .prettierrc.json`);
212
252
  console.log(` ${c.green('✓')} Created .prettierignore\n`);
213
253
  }
@@ -218,8 +258,17 @@ function initProject() {
218
258
  if (project.usesTypeScript) {
219
259
  console.log(c.bold('📘 Step 5: Configuring TypeScript...'));
220
260
  try {
261
+ const tsFile = project.existingTooling.hasTypescript ? 'tsconfig.strict.json' : 'tsconfig.json';
262
+ const tsPath = path.join(cwd, tsFile);
263
+ const tsExisted = fs.existsSync(tsPath);
221
264
  const { generateTypescriptConfig } = requireGenerator('typescript-generator');
222
265
  generateTypescriptConfig(project);
266
+ if (tsExisted) {
267
+ recordModifiedFile(manifest, tsFile, 'typescript');
268
+ }
269
+ else {
270
+ recordCreatedFile(manifest, tsFile);
271
+ }
223
272
  if (project.existingTooling.hasTypescript) {
224
273
  console.log(` ${c.green('✓')} Created tsconfig.strict.json (extends existing tsconfig)`);
225
274
  }
@@ -238,18 +287,30 @@ function initProject() {
238
287
  // ── Step 6: Generate lint-staged Config ───────────────────────────────────
239
288
  console.log(c.bold('📋 Step 6: Configuring lint-staged...'));
240
289
  try {
290
+ const lsFile = '.lintstagedrc.json';
291
+ const lsPath = path.join(cwd, lsFile);
292
+ const lsExisted = fs.existsSync(lsPath);
241
293
  const { generateLintStagedConfig } = requireGenerator('lint-staged-generator');
242
294
  generateLintStagedConfig(project);
295
+ if (!lsExisted && fs.existsSync(lsPath)) {
296
+ recordCreatedFile(manifest, lsFile);
297
+ }
243
298
  console.log(` ${c.green('✓')} Created .lintstagedrc.json\n`);
244
299
  }
245
300
  catch (error) {
246
301
  console.warn(c.yellow(` ⚠️ lint-staged config generation failed: ${error.message}\n`));
247
302
  }
248
303
  // ── Step 7: Generate .editorconfig ────────────────────────────────────────
249
- console.log(c.bold('📐 Step 8: Creating .editorconfig...'));
304
+ console.log(c.bold('📐 Step 7: Creating .editorconfig...'));
250
305
  try {
306
+ const ecFile = '.editorconfig';
307
+ const ecPath = path.join(cwd, ecFile);
308
+ const ecExisted = fs.existsSync(ecPath);
251
309
  const { generateEditorConfig } = requireGenerator('editorconfig-generator');
252
310
  generateEditorConfig(project);
311
+ if (!ecExisted && fs.existsSync(ecPath)) {
312
+ recordCreatedFile(manifest, ecFile);
313
+ }
253
314
  console.log(` ${c.green('✓')} Created .editorconfig\n`);
254
315
  }
255
316
  catch (error) {
@@ -258,11 +319,21 @@ function initProject() {
258
319
  // ── Step 8: Generate VS Code Settings ─────────────────────────────────────
259
320
  console.log(c.bold('💻 Step 8: Creating VS Code workspace settings...'));
260
321
  try {
322
+ const vscodeFiles = ['.vscode/settings.json', '.vscode/extensions.json', '.vscode/tasks.json', '.vscode/launch.json'];
323
+ const vscodeExisted = {};
324
+ for (const vf of vscodeFiles) {
325
+ vscodeExisted[vf] = fs.existsSync(path.join(cwd, vf));
326
+ }
261
327
  const { generateVSCodeSettings, generateVSCodeExtensions, generateVSCodeTasks, generateVSCodeLaunch } = requireGenerator('vscode-generator');
262
328
  generateVSCodeSettings(project);
263
329
  generateVSCodeExtensions(project);
264
330
  generateVSCodeTasks(project);
265
331
  generateVSCodeLaunch(project);
332
+ for (const vf of vscodeFiles) {
333
+ if (!vscodeExisted[vf] && fs.existsSync(path.join(cwd, vf))) {
334
+ recordCreatedFile(manifest, vf);
335
+ }
336
+ }
266
337
  console.log(` ${c.green('✓')} Created .vscode/settings.json`);
267
338
  console.log(` ${c.green('✓')} Created .vscode/extensions.json`);
268
339
  console.log(` ${c.green('✓')} Created .vscode/tasks.json`);
@@ -274,7 +345,15 @@ function initProject() {
274
345
  // ── Step 9: Setup Git Hooks ───────────────────────────────────────────────
275
346
  if (!skipHooks) {
276
347
  console.log(c.bold('🐶 Step 9: Setting up Git hooks...'));
348
+ const hookFile = '.husky/pre-commit';
349
+ const hookExisted = fs.existsSync(path.join(cwd, hookFile));
277
350
  setupGitHooks(cwd);
351
+ if (hookExisted) {
352
+ recordModifiedFile(manifest, hookFile, 'husky');
353
+ }
354
+ else if (fs.existsSync(path.join(cwd, hookFile))) {
355
+ recordCreatedFile(manifest, hookFile);
356
+ }
278
357
  console.log('');
279
358
  }
280
359
  else {
@@ -286,16 +365,30 @@ function initProject() {
286
365
  try {
287
366
  const { loadCompanyRules } = requireUtil('custom-rules-loader');
288
367
  const companyRules = loadCompanyRules(cwd);
289
- const { generateAIConfigs } = requireGenerator('ai-config-generator');
290
- generateAIConfigs(project, companyRules.aiGuidelines);
368
+ // Snapshot which AI files exist before generation
291
369
  const { defaultRegistry } = requireUtil('ai-config-registry');
292
370
  const templates = defaultRegistry.getAll();
371
+ const aiExisted = {};
293
372
  for (const t of templates) {
294
373
  const targetDir = t.directory ? path.join(cwd, t.directory) : cwd;
295
374
  const targetPath = path.join(targetDir, t.fileName);
375
+ const relPath = t.directory ? `${t.directory}/${t.fileName}` : t.fileName;
376
+ aiExisted[relPath] = fs.existsSync(targetPath);
377
+ }
378
+ const { generateAIConfigs } = requireGenerator('ai-config-generator');
379
+ generateAIConfigs(project, companyRules.aiGuidelines);
380
+ for (const t of templates) {
381
+ const targetDir = t.directory ? path.join(cwd, t.directory) : cwd;
382
+ const targetPath = path.join(targetDir, t.fileName);
383
+ const relPath = t.directory ? `${t.directory}/${t.fileName}` : t.fileName;
296
384
  if (fs.existsSync(targetPath)) {
297
- const displayPath = t.directory ? `${t.directory}/${t.fileName}` : t.fileName;
298
- console.log(` ${c.green('✓')} ${t.name}: ${displayPath}`);
385
+ if (aiExisted[relPath]) {
386
+ recordModifiedFile(manifest, relPath, t.marker);
387
+ }
388
+ else {
389
+ recordCreatedFile(manifest, relPath);
390
+ }
391
+ console.log(` ${c.green('✓')} ${t.name}: ${relPath}`);
299
392
  }
300
393
  }
301
394
  console.log('');
@@ -310,6 +403,11 @@ function initProject() {
310
403
  // ── Step 11: Custom Rules Template ─────────────────────────────────────────
311
404
  console.log(c.bold('🏢 Step 11: Creating custom rules templates...'));
312
405
  try {
406
+ const cgFiles = ['.code-guardian/custom-rules.json', '.code-guardian/structure-rules.json', '.code-guardian/naming-rules.json'];
407
+ const cgExisted = {};
408
+ for (const cf of cgFiles) {
409
+ cgExisted[cf] = fs.existsSync(path.join(cwd, cf));
410
+ }
313
411
  const { generateCustomRulesTemplate } = requireUtil('custom-rules-loader');
314
412
  generateCustomRulesTemplate(cwd);
315
413
  console.log(` ${c.green('✓')} Created .code-guardian/custom-rules.json`);
@@ -319,18 +417,26 @@ function initProject() {
319
417
  const { generateNamingRulesTemplate } = requireUtil('naming-validator');
320
418
  generateNamingRulesTemplate(cwd);
321
419
  console.log(` ${c.green('✓')} Created .code-guardian/naming-rules.json\n`);
420
+ for (const cf of cgFiles) {
421
+ if (!cgExisted[cf] && fs.existsSync(path.join(cwd, cf))) {
422
+ recordCreatedFile(manifest, cf);
423
+ }
424
+ }
322
425
  }
323
426
  catch (error) {
324
427
  console.warn(c.yellow(` ⚠️ Custom rules template generation failed: ${error.message}\n`));
325
428
  }
326
429
  // ── Step 12: Update package.json ───────────────────────────────────────────
327
430
  console.log(c.bold('📦 Step 12: Updating package.json...'));
328
- updatePackageJson(cwd, project);
431
+ updatePackageJson(cwd, project, manifest);
329
432
  console.log('');
330
433
  // ── Step 13: Copy Code Guardian configs and templates ──────────────────────
331
434
  console.log(c.bold('📁 Step 13: Copying Code Guardian rules and templates...'));
332
- copyCodeGuardianFiles(cwd);
435
+ copyCodeGuardianFiles(cwd, manifest, project.type);
333
436
  console.log('');
437
+ // ── Save manifest ─────────────────────────────────────────────────────────
438
+ saveManifest(cwd, manifest);
439
+ console.log(` ${c.green('✓')} Saved manifest to .code-guardian/manifest.json\n`);
334
440
  // ── Done! ─────────────────────────────────────────────────────────────────
335
441
  console.log(c.bold(c.green('═══════════════════════════════════════════════════════════')));
336
442
  console.log(c.bold(c.green(' ✅ Code Guardian initialized successfully!')));
@@ -364,36 +470,45 @@ function initProject() {
364
470
  // ── Setup Git Hooks ─────────────────────────────────────────────────────────
365
471
  function setupGitHooks(cwd) {
366
472
  try {
367
- // Initialize Husky
473
+ const huskyDir = path.join(cwd, '.husky');
474
+ const preCommitPath = path.join(huskyDir, 'pre-commit');
475
+ // Save existing pre-commit content BEFORE husky init (which overwrites it)
476
+ let existingPreCommit = null;
477
+ if (fs.existsSync(preCommitPath)) {
478
+ existingPreCommit = fs.readFileSync(preCommitPath, 'utf-8');
479
+ }
480
+ // Initialize Husky (creates .husky/ dir and default pre-commit)
368
481
  try {
369
482
  (0, child_process_1.execSync)('npx husky init', { stdio: 'pipe', cwd });
370
483
  }
371
484
  catch {
372
485
  // Fallback: manual setup
373
- const huskyDir = path.join(cwd, '.husky');
374
486
  if (!fs.existsSync(huskyDir)) {
375
487
  fs.mkdirSync(huskyDir, { recursive: true });
376
488
  }
377
489
  }
378
- const huskyDir = path.join(cwd, '.husky');
379
490
  if (!fs.existsSync(huskyDir)) {
380
491
  fs.mkdirSync(huskyDir, { recursive: true });
381
492
  }
382
- // Create or append pre-commit hook (lint-staged + Code Guardian)
493
+ // Restore the original pre-commit content if husky init overwrote it
494
+ if (existingPreCommit !== null) {
495
+ fs.writeFileSync(preCommitPath, existingPreCommit);
496
+ }
497
+ // Create or append pre-commit hook (lint-staged only — Code Guardian checks run via `code-guard check`)
383
498
  const preCommitContent = `
384
- # npm test
385
-
386
499
  # --- code-guardian-hook-start ---
387
- echo "🛡️ Code Guardian — Pre-commit checks..."
500
+ echo "🛡️ Code Guardian — Pre-commit lint & format..."
501
+
502
+ # Skip lint-staged when BYPASS_RULES is set
503
+ if [ "$BYPASS_RULES" = "true" ] || [ "$BYPASS_RULES" = "1" ]; then
504
+ echo "⚡ BYPASS_RULES detected — skipping lint-staged."
505
+ exit 0
506
+ fi
388
507
 
389
508
  # Run lint-staged (ESLint + Prettier auto-fix)
390
509
  npx lint-staged
391
-
392
- # Run Code Guardian custom rules
393
- npm run precommit-check
394
510
  # --- code-guardian-hook-end ---
395
511
  `;
396
- const preCommitPath = path.join(huskyDir, 'pre-commit');
397
512
  if (fs.existsSync(preCommitPath)) {
398
513
  const existing = fs.readFileSync(preCommitPath, 'utf-8');
399
514
  if (!existing.includes('code-guardian-hook-start')) {
@@ -409,7 +524,7 @@ npm run precommit-check
409
524
  // Create new hook file
410
525
  const preCommitHook = `#!/usr/bin/env sh\n${preCommitContent}`;
411
526
  fs.writeFileSync(preCommitPath, preCommitHook);
412
- console.log(` ${c.green('✓')} Created .husky/pre-commit (lint-staged + Code Guardian)`);
527
+ console.log(` ${c.green('✓')} Created .husky/pre-commit (lint-staged only)`);
413
528
  }
414
529
  try {
415
530
  fs.chmodSync(preCommitPath, '755');
@@ -462,43 +577,74 @@ function buildInstallCommand(packageManager, deps) {
462
577
  }
463
578
  }
464
579
  // ── Update package.json ─────────────────────────────────────────────────────
465
- function updatePackageJson(cwd, project) {
580
+ function updatePackageJson(cwd, project, manifest) {
466
581
  const packageJsonPath = path.join(cwd, 'package.json');
467
582
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
468
583
  const packageName = '@hatem427/code-guard-ci';
469
584
  packageJson.scripts = packageJson.scripts || {};
585
+ // Helper to track script changes in manifest
586
+ const setScript = (name, value) => {
587
+ if (manifest) {
588
+ if (packageJson.scripts[name] && packageJson.scripts[name] !== value) {
589
+ // Script existed with different value — record original
590
+ const { recordModifiedScript } = requireUtil('manifest');
591
+ recordModifiedScript(manifest, name, packageJson.scripts[name]);
592
+ }
593
+ else if (!packageJson.scripts[name]) {
594
+ // New script
595
+ const { recordAddedScript } = requireUtil('manifest');
596
+ recordAddedScript(manifest, name);
597
+ }
598
+ }
599
+ packageJson.scripts[name] = value;
600
+ };
470
601
  // Core Code Guardian scripts
471
- packageJson.scripts['precommit-check'] = 'code-guard check';
472
- packageJson.scripts['generate-doc'] = 'code-guard doc';
473
- packageJson.scripts['generate-pr-checklist'] = 'code-guard checklist';
474
- packageJson.scripts['validate'] = 'code-guard validate';
475
- packageJson.scripts['prepare'] = 'husky';
602
+ setScript('precommit-check', 'code-guard check');
603
+ setScript('generate-doc', 'code-guard doc');
604
+ setScript('generate-pr-checklist', 'code-guard checklist');
605
+ setScript('validate', 'code-guard validate');
606
+ setScript('prepare', 'husky');
476
607
  // Lint and format scripts
477
608
  const extensions = project.usesTypeScript ? '.ts,.tsx,.js,.jsx' : '.js,.jsx';
478
- packageJson.scripts['lint'] = `eslint . --ext ${extensions}`;
479
- packageJson.scripts['lint:fix'] = `eslint . --ext ${extensions} --fix`;
480
- packageJson.scripts['format'] = 'prettier --write .';
481
- packageJson.scripts['format:check'] = 'prettier --check .';
609
+ setScript('lint', `eslint . --ext ${extensions}`);
610
+ setScript('lint:fix', `eslint . --ext ${extensions} --fix`);
611
+ setScript('format', 'prettier --write .');
612
+ setScript('format:check', 'prettier --check .');
482
613
  // Security scripts
483
- packageJson.scripts['set-bypass-password'] = `node node_modules/${packageName}/dist/scripts/set-bypass-password.js`;
484
- packageJson.scripts['set-admin-password'] = `node node_modules/${packageName}/dist/scripts/set-admin-password.js`;
485
- packageJson.scripts['delete-bypass-logs'] = `node node_modules/${packageName}/dist/scripts/delete-bypass-logs.js`;
486
- packageJson.scripts['view-bypass-log'] = `node node_modules/${packageName}/dist/scripts/view-bypass-log.js`;
487
- packageJson.scripts['auto-fix'] = `node node_modules/${packageName}/dist/scripts/auto-fix.js`;
614
+ setScript('set-bypass-password', `node node_modules/${packageName}/dist/scripts/set-bypass-password.js`);
615
+ setScript('set-admin-password', `node node_modules/${packageName}/dist/scripts/set-admin-password.js`);
616
+ setScript('delete-bypass-logs', `node node_modules/${packageName}/dist/scripts/delete-bypass-logs.js`);
617
+ setScript('view-bypass-log', `node node_modules/${packageName}/dist/scripts/view-bypass-log.js`);
618
+ setScript('auto-fix', `node node_modules/${packageName}/dist/scripts/auto-fix.js`);
488
619
  fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
489
620
  console.log(` ${c.green('✓')} Added npm scripts (lint, format, validate, precommit-check, etc.)`);
490
621
  }
491
622
  // ── Copy Code Guardian files ────────────────────────────────────────────────
492
- function copyCodeGuardianFiles(cwd) {
623
+ function copyCodeGuardianFiles(cwd, manifest, projectType) {
493
624
  // Determine source directories (handles both dev and built package layouts)
494
625
  const distDir = path.join(__dirname, '..');
495
626
  const pkgRootDir = path.join(__dirname, '..', '..');
496
- // Copy config files
627
+ // Copy config files — only guidelines (always) + detected framework config
497
628
  const configDir = path.join(cwd, 'config');
498
629
  if (!fs.existsSync(configDir)) {
499
630
  fs.mkdirSync(configDir, { recursive: true });
500
631
  }
501
- const configFiles = ['guidelines.config.ts', 'angular.config.ts', 'react.config.ts', 'nextjs.config.ts'];
632
+ // Map project type to its config file
633
+ const frameworkConfigMap = {
634
+ angular: 'angular.config.ts',
635
+ react: 'react.config.ts',
636
+ nextjs: 'nextjs.config.ts',
637
+ node: 'node.config.ts',
638
+ };
639
+ // Always include guidelines + detected framework config (+ react for nextjs since it inherits)
640
+ const configFiles = ['guidelines.config.ts'];
641
+ if (projectType && frameworkConfigMap[projectType]) {
642
+ configFiles.push(frameworkConfigMap[projectType]);
643
+ }
644
+ // Next.js inherits React rules, so include react config too
645
+ if (projectType === 'nextjs' && !configFiles.includes('react.config.ts')) {
646
+ configFiles.push('react.config.ts');
647
+ }
502
648
  const sourceConfigDirs = [path.join(distDir, 'config'), path.join(pkgRootDir, 'config')];
503
649
  for (const file of configFiles) {
504
650
  let src = null;
@@ -512,6 +658,10 @@ function copyCodeGuardianFiles(cwd) {
512
658
  const dest = path.join(configDir, file);
513
659
  if (src && !fs.existsSync(dest)) {
514
660
  fs.copyFileSync(src, dest);
661
+ if (manifest) {
662
+ const { recordCreatedFile } = requireUtil('manifest');
663
+ recordCreatedFile(manifest, `config/${file}`);
664
+ }
515
665
  console.log(` ${c.green('✓')} ${file}`);
516
666
  }
517
667
  }
@@ -534,6 +684,10 @@ function copyCodeGuardianFiles(cwd) {
534
684
  const dest = path.join(templatesDir, file);
535
685
  if (src && !fs.existsSync(dest)) {
536
686
  fs.copyFileSync(src, dest);
687
+ if (manifest) {
688
+ const { recordCreatedFile } = requireUtil('manifest');
689
+ recordCreatedFile(manifest, `templates/${file}`);
690
+ }
537
691
  console.log(` ${c.green('✓')} ${file}`);
538
692
  }
539
693
  }
@@ -542,6 +696,10 @@ function copyCodeGuardianFiles(cwd) {
542
696
  if (!fs.existsSync(docsDir)) {
543
697
  fs.mkdirSync(docsDir, { recursive: true });
544
698
  fs.writeFileSync(path.join(docsDir, '.gitkeep'), '');
699
+ if (manifest) {
700
+ const { recordCreatedFile } = requireUtil('manifest');
701
+ recordCreatedFile(manifest, 'docs/features/.gitkeep');
702
+ }
545
703
  console.log(` ${c.green('✓')} Created docs/features/`);
546
704
  }
547
705
  }
@@ -877,11 +1035,20 @@ function promptYesNo(question) {
877
1035
  async function uninstallCodeGuard() {
878
1036
  showBanner();
879
1037
  console.log(c.bold('🗑️ Code Guardian Uninstall\n'));
880
- const filesToRemove = [
1038
+ const cwd = process.cwd();
1039
+ // ── Load manifest for smart uninstall ────────────────────────────────
1040
+ const { loadManifest, removeMarkedContent } = requireUtil('manifest');
1041
+ const manifest = loadManifest(cwd);
1042
+ // Hardcoded fallbacks for files that Code Guardian always creates
1043
+ const fallbackFilesToRemove = [
881
1044
  '.husky/_/husky.sh',
1045
+ 'eslint.config.mjs',
882
1046
  'eslint.config.js',
1047
+ '.prettierrc.json',
1048
+ '.prettierignore',
883
1049
  'prettier.config.js',
884
1050
  '.editorconfig',
1051
+ '.lintstagedrc.json',
885
1052
  'lint-staged.config.js',
886
1053
  'tsconfig.strict.json',
887
1054
  ];
@@ -889,7 +1056,6 @@ async function uninstallCodeGuard() {
889
1056
  'config',
890
1057
  'templates',
891
1058
  'docs',
892
- '.code-guardian',
893
1059
  ];
894
1060
  const scriptsToRemove = [
895
1061
  'precommit-check',
@@ -900,11 +1066,16 @@ async function uninstallCodeGuard() {
900
1066
  'set-admin-password',
901
1067
  'view-bypass-log',
902
1068
  'delete-bypass-logs',
1069
+ 'lint',
1070
+ 'lint:fix',
1071
+ 'format',
1072
+ 'format:check',
1073
+ 'validate',
1074
+ 'prepare',
903
1075
  ];
904
1076
  // ── Detect AI config files via registry ──────────────────────────────
905
1077
  const { defaultRegistry } = requireUtil('ai-config-registry');
906
1078
  const aiTemplates = defaultRegistry.getAll();
907
- const cwd = process.cwd();
908
1079
  const aiActions = [];
909
1080
  for (const t of aiTemplates) {
910
1081
  const dir = t.directory ? path.join(cwd, t.directory) : cwd;
@@ -914,38 +1085,103 @@ async function uninstallCodeGuard() {
914
1085
  continue;
915
1086
  const content = fs.readFileSync(filePath, 'utf-8');
916
1087
  if (!content.includes(t.marker))
917
- continue; // not ours
918
- // Determine if the file was created by us or if we appended
919
- // If appended, there will be a --- separator before our marker with user content above
920
- const markerIdx = content.indexOf(t.marker);
921
- const beforeMarker = content.substring(0, markerIdx).trimEnd();
922
- // Check if there's substantial user content before our section
923
- // When we append we add "\n\n---\n\n" before our content
924
- // When we create, the marker is at or near the top (possibly after frontmatter)
925
- const stripped = beforeMarker.replace(/^---[\s\S]*?---/, '').trim(); // strip MDC frontmatter
926
- const hasUserContent = stripped.length > 0 && !stripped.endsWith('---');
927
- // A more robust check: if content before marker (minus separator) has real text
928
- const withoutTrailingSep = beforeMarker.replace(/\n*---\s*$/, '').trim();
929
- const realUserContent = withoutTrailingSep.replace(/^---[\s\S]*?---/, '').trim(); // strip frontmatter
930
- if (realUserContent.length > 0) {
931
- aiActions.push({ name: t.name, filePath, relativePath, marker: t.marker, action: 'strip' });
1088
+ continue;
1089
+ // Use manifest to determine action if available
1090
+ if (manifest && manifest.files[relativePath]) {
1091
+ const record = manifest.files[relativePath];
1092
+ if (record.action === 'created') {
1093
+ aiActions.push({ name: t.name, filePath, relativePath, marker: t.marker, action: 'delete' });
1094
+ }
1095
+ else {
1096
+ aiActions.push({ name: t.name, filePath, relativePath, marker: t.marker, action: 'strip' });
1097
+ }
932
1098
  }
933
1099
  else {
934
- aiActions.push({ name: t.name, filePath, relativePath, marker: t.marker, action: 'delete' });
1100
+ // Fallback: detect by checking for user content before marker
1101
+ const markerIdx = content.indexOf(t.marker);
1102
+ const beforeMarker = content.substring(0, markerIdx).trimEnd();
1103
+ const withoutTrailingSep = beforeMarker.replace(/\n*---\s*$/, '').trim();
1104
+ const realUserContent = withoutTrailingSep.replace(/^---[\s\S]*?---/, '').trim();
1105
+ if (realUserContent.length > 0) {
1106
+ aiActions.push({ name: t.name, filePath, relativePath, marker: t.marker, action: 'strip' });
1107
+ }
1108
+ else {
1109
+ aiActions.push({ name: t.name, filePath, relativePath, marker: t.marker, action: 'delete' });
1110
+ }
935
1111
  }
936
1112
  }
1113
+ // Determine which files to remove (use manifest if available, fallback to hardcoded)
1114
+ let filesToProcess;
1115
+ if (manifest) {
1116
+ // Use manifest — it knows exactly what we created vs modified
1117
+ filesToProcess = Object.keys(manifest.files).filter(f => {
1118
+ // Skip AI files — handled separately
1119
+ return !aiActions.some(a => a.relativePath === f);
1120
+ });
1121
+ }
1122
+ else {
1123
+ filesToProcess = fallbackFilesToRemove;
1124
+ }
937
1125
  // Check what exists
938
- const existingFiles = filesToRemove.filter(f => fs.existsSync(path.join(cwd, f)));
1126
+ const existingFiles = filesToProcess.filter(f => fs.existsSync(path.join(cwd, f)));
939
1127
  const existingDirs = dirsToRemove.filter(d => fs.existsSync(path.join(cwd, d)));
940
- if (existingFiles.length === 0 && existingDirs.length === 0 && aiActions.length === 0) {
1128
+ // Also check for backup files that need restoring
1129
+ const backupsToRestore = [];
1130
+ if (manifest && manifest.backups) {
1131
+ for (const [original, backup] of Object.entries(manifest.backups)) {
1132
+ if (fs.existsSync(path.join(cwd, backup))) {
1133
+ backupsToRestore.push({ original, backup });
1134
+ }
1135
+ }
1136
+ }
1137
+ // Also find any .backup files from generators (eslint, prettier create them)
1138
+ for (const file of existingFiles) {
1139
+ const backupPath = path.join(cwd, file + '.backup');
1140
+ if (fs.existsSync(backupPath) && !backupsToRestore.some(b => b.backup === file + '.backup')) {
1141
+ backupsToRestore.push({ original: file, backup: file + '.backup' });
1142
+ }
1143
+ }
1144
+ // Check common backup locations from generators
1145
+ const commonBackups = [
1146
+ '.eslintrc.backup', '.eslintrc.js.backup', '.eslintrc.cjs.backup', '.eslintrc.json.backup',
1147
+ 'eslint.config.js.backup', 'eslint.config.mjs.backup', 'eslint.config.cjs.backup',
1148
+ '.prettierrc.backup', '.prettierrc.js.backup', '.prettierrc.cjs.backup', '.prettierrc.json.backup',
1149
+ '.prettierrc.yml.backup', '.prettierrc.yaml.backup', '.prettierrc.toml.backup', 'prettier.config.js.backup',
1150
+ '.eslintignore.backup',
1151
+ ];
1152
+ for (const backup of commonBackups) {
1153
+ if (fs.existsSync(path.join(cwd, backup))) {
1154
+ const original = backup.replace(/\.backup$/, '');
1155
+ if (!backupsToRestore.some(b => b.backup === backup)) {
1156
+ backupsToRestore.push({ original, backup });
1157
+ }
1158
+ }
1159
+ }
1160
+ if (existingFiles.length === 0 && existingDirs.length === 0 && aiActions.length === 0 && backupsToRestore.length === 0) {
941
1161
  console.log(c.yellow('⚠️ No Code Guardian files found to remove.\n'));
942
1162
  return;
943
1163
  }
944
- // Show what will be removed
1164
+ // Show what will be done
945
1165
  console.log(c.bold('The following will be removed:\n'));
946
- if (existingFiles.length > 0) {
947
- console.log(c.bold('Files:'));
948
- existingFiles.forEach(f => console.log(` ${c.red('✗')} ${f}`));
1166
+ // Separate created vs modified files
1167
+ const createdFiles = [];
1168
+ const modifiedFiles = [];
1169
+ for (const f of existingFiles) {
1170
+ if (manifest && manifest.files[f] && manifest.files[f].action === 'modified') {
1171
+ modifiedFiles.push(f);
1172
+ }
1173
+ else {
1174
+ createdFiles.push(f);
1175
+ }
1176
+ }
1177
+ if (createdFiles.length > 0) {
1178
+ console.log(c.bold('Files (created by Code Guardian — will be deleted):'));
1179
+ createdFiles.forEach(f => console.log(` ${c.red('✗')} ${f}`));
1180
+ console.log('');
1181
+ }
1182
+ if (modifiedFiles.length > 0) {
1183
+ console.log(c.bold('Files (modified — Code Guardian content will be stripped):'));
1184
+ modifiedFiles.forEach(f => console.log(` ${c.yellow('⚠')} ${f} ${c.dim('(your content preserved)')}`));
949
1185
  console.log('');
950
1186
  }
951
1187
  if (existingDirs.length > 0) {
@@ -965,6 +1201,11 @@ async function uninstallCodeGuard() {
965
1201
  aiStrips.forEach(a => console.log(` ${c.yellow('⚠')} ${a.relativePath} ${c.dim('(keeping your original content)')}`));
966
1202
  console.log('');
967
1203
  }
1204
+ if (backupsToRestore.length > 0) {
1205
+ console.log(c.bold('Backups to restore (original configs):'));
1206
+ backupsToRestore.forEach(b => console.log(` ${c.blue('↩')} ${b.backup} → ${b.original}`));
1207
+ console.log('');
1208
+ }
968
1209
  if (!hasFlag('yes') && !hasFlag('y')) {
969
1210
  const readline = require('readline');
970
1211
  const rl = readline.createInterface({
@@ -984,47 +1225,72 @@ async function uninstallCodeGuard() {
984
1225
  }
985
1226
  let removedCount = 0;
986
1227
  console.log('');
987
- // Backup AI config files before modifying
988
- const backupDir = path.join(cwd, '.code-guardian-backup-' + Date.now());
989
- let hasBackup = false;
990
- if (aiActions.length > 0 && !hasFlag('no-backup')) {
991
- console.log(c.bold('Creating backup...\n'));
992
- fs.mkdirSync(backupDir, { recursive: true });
993
- for (const action of aiActions) {
994
- if (fs.existsSync(action.filePath)) {
1228
+ // ── Phase 1: Handle created files (delete) ──────────────────────────
1229
+ if (createdFiles.length > 0) {
1230
+ console.log(c.bold('Removing created files...\n'));
1231
+ for (const file of createdFiles) {
1232
+ const fullPath = path.join(cwd, file);
1233
+ if (fs.existsSync(fullPath)) {
995
1234
  try {
996
- const destPath = path.join(backupDir, action.relativePath);
997
- const destDir = path.dirname(destPath);
998
- if (!fs.existsSync(destDir))
999
- fs.mkdirSync(destDir, { recursive: true });
1000
- fs.cpSync(action.filePath, destPath);
1001
- console.log(` ${c.blue('📦')} Backed up ${action.relativePath}`);
1002
- hasBackup = true;
1235
+ fs.unlinkSync(fullPath);
1236
+ console.log(` ${c.green('✓')} Removed ${file}`);
1237
+ removedCount++;
1003
1238
  }
1004
1239
  catch (error) {
1005
- console.log(` ${c.yellow('')} Failed to backup ${action.relativePath}: ${error.message}`);
1240
+ console.log(` ${c.red('')} Failed to remove ${file}: ${error.message}`);
1006
1241
  }
1007
1242
  }
1008
1243
  }
1009
1244
  console.log('');
1010
1245
  }
1011
- // Remove config files created entirely by Code Guardian
1012
- console.log(c.bold('Removing files...\n'));
1013
- for (const file of filesToRemove) {
1014
- const fullPath = path.join(cwd, file);
1015
- if (fs.existsSync(fullPath)) {
1246
+ // ── Phase 2: Handle modified files (strip marked content) ───────────
1247
+ if (modifiedFiles.length > 0) {
1248
+ console.log(c.bold('Stripping Code Guardian content from modified files...\n'));
1249
+ for (const file of modifiedFiles) {
1250
+ const fullPath = path.join(cwd, file);
1251
+ if (fs.existsSync(fullPath)) {
1252
+ try {
1253
+ const record = manifest?.files[file];
1254
+ const stripped = removeMarkedContent(fullPath, record?.marker);
1255
+ if (stripped) {
1256
+ console.log(` ${c.green('✓')} Stripped Code Guardian content from ${file}`);
1257
+ }
1258
+ else {
1259
+ console.log(` ${c.dim('○')} No marked content found in ${file}`);
1260
+ }
1261
+ removedCount++;
1262
+ }
1263
+ catch (error) {
1264
+ console.log(` ${c.red('✗')} Failed to clean ${file}: ${error.message}`);
1265
+ }
1266
+ }
1267
+ }
1268
+ console.log('');
1269
+ }
1270
+ // ── Phase 3: Restore backup files ───────────────────────────────────
1271
+ if (backupsToRestore.length > 0) {
1272
+ console.log(c.bold('Restoring original config files...\n'));
1273
+ for (const { original, backup } of backupsToRestore) {
1274
+ const backupFullPath = path.join(cwd, backup);
1275
+ const originalFullPath = path.join(cwd, original);
1016
1276
  try {
1017
- fs.unlinkSync(fullPath);
1018
- console.log(` ${c.green('✓')} Removed ${file}`);
1277
+ // Remove the Code Guardian version first (if it exists)
1278
+ if (fs.existsSync(originalFullPath)) {
1279
+ fs.unlinkSync(originalFullPath);
1280
+ }
1281
+ // Restore the backup as the original
1282
+ fs.renameSync(backupFullPath, originalFullPath);
1283
+ console.log(` ${c.green('✓')} Restored ${original} from ${backup}`);
1019
1284
  removedCount++;
1020
1285
  }
1021
1286
  catch (error) {
1022
- console.log(` ${c.red('✗')} Failed to remove ${file}: ${error.message}`);
1287
+ console.log(` ${c.red('✗')} Failed to restore ${original}: ${error.message}`);
1023
1288
  }
1024
1289
  }
1290
+ console.log('');
1025
1291
  }
1026
- // Remove directories
1027
- console.log(c.bold('\nRemoving directories...\n'));
1292
+ // ── Phase 4: Remove directories ─────────────────────────────────────
1293
+ console.log(c.bold('Removing directories...\n'));
1028
1294
  for (const dir of dirsToRemove) {
1029
1295
  const fullPath = path.join(cwd, dir);
1030
1296
  if (fs.existsSync(fullPath)) {
@@ -1038,12 +1304,11 @@ async function uninstallCodeGuard() {
1038
1304
  }
1039
1305
  }
1040
1306
  }
1041
- // Handle AI config files intelligently
1307
+ // ── Phase 5: Handle AI config files intelligently ───────────────────
1042
1308
  console.log(c.bold('\nCleaning AI config files...\n'));
1043
1309
  for (const action of aiActions) {
1044
1310
  try {
1045
1311
  if (action.action === 'delete') {
1046
- // Entire file was created by Code Guardian → delete it
1047
1312
  fs.unlinkSync(action.filePath);
1048
1313
  console.log(` ${c.green('✓')} Removed ${action.relativePath}`);
1049
1314
  removedCount++;
@@ -1054,7 +1319,6 @@ async function uninstallCodeGuard() {
1054
1319
  const remaining = fs.readdirSync(parentDir);
1055
1320
  if (remaining.length === 0) {
1056
1321
  fs.rmdirSync(parentDir);
1057
- // Check grandparent too (e.g., .windsurf/rules → .windsurf)
1058
1322
  const grandparent = path.dirname(parentDir);
1059
1323
  if (grandparent !== cwd && fs.existsSync(grandparent)) {
1060
1324
  const gpRemaining = fs.readdirSync(grandparent);
@@ -1072,16 +1336,12 @@ async function uninstallCodeGuard() {
1072
1336
  const markerIdx = content.indexOf(action.marker);
1073
1337
  if (markerIdx === -1)
1074
1338
  continue;
1075
- // Find the separator (--- on its own line) before the marker
1076
- // When we append, we add "\n\n---\n\n" before our content
1077
1339
  let cutStart = markerIdx;
1078
1340
  const beforeMarker = content.substring(0, markerIdx);
1079
- // Look for the --- separator we added
1080
1341
  const sepMatch = beforeMarker.match(/\n*\s*---\s*\n*$/);
1081
1342
  if (sepMatch && sepMatch.index !== undefined) {
1082
1343
  cutStart = sepMatch.index;
1083
1344
  }
1084
- // Remove from cutStart to end of file (our content is always appended at the end)
1085
1345
  const cleaned = content.substring(0, cutStart).trimEnd() + '\n';
1086
1346
  fs.writeFileSync(action.filePath, cleaned);
1087
1347
  console.log(` ${c.green('✓')} Stripped Code Guardian rules from ${action.relativePath} ${c.dim('(your content preserved)')}`);
@@ -1092,7 +1352,7 @@ async function uninstallCodeGuard() {
1092
1352
  console.log(` ${c.red('✗')} Failed to clean ${action.relativePath}: ${error.message}`);
1093
1353
  }
1094
1354
  }
1095
- // Clean up .husky hooks smartly
1355
+ // ── Phase 6: Clean up .husky hooks smartly ──────────────────────────
1096
1356
  console.log(c.bold('\nCleaning git hooks...\n'));
1097
1357
  const huskyDir = path.join(cwd, '.husky');
1098
1358
  const huskyHookFiles = ['pre-commit'];
@@ -1111,7 +1371,6 @@ async function uninstallCodeGuard() {
1111
1371
  continue;
1112
1372
  const beforeHook = content.substring(0, startIdx).trimEnd();
1113
1373
  const afterHook = content.substring(endIdx + HOOK_END.length).trimEnd();
1114
- // Check if there's real user content outside of our markers
1115
1374
  const remaining = (beforeHook.replace(/^#!\/usr\/bin\/env\s+sh\s*/, '').trim() + afterHook.trim()).trim();
1116
1375
  if (remaining.length === 0) {
1117
1376
  // Entire file is ours → delete it
@@ -1145,22 +1404,45 @@ async function uninstallCodeGuard() {
1145
1404
  }
1146
1405
  catch { }
1147
1406
  }
1148
- // Remove scripts from package.json
1407
+ // ── Phase 7: Clean package.json scripts ─────────────────────────────
1149
1408
  console.log(c.bold('\nCleaning package.json scripts...\n'));
1150
1409
  const packageJsonPath = path.join(cwd, 'package.json');
1151
1410
  if (fs.existsSync(packageJsonPath)) {
1152
1411
  try {
1153
1412
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
1154
- let scriptRemoved = false;
1155
- for (const script of scriptsToRemove) {
1156
- if (packageJson.scripts && packageJson.scripts[script]) {
1157
- delete packageJson.scripts[script];
1158
- console.log(` ${c.green('✓')} Removed script: ${script}`);
1159
- scriptRemoved = true;
1160
- removedCount++;
1413
+ let scriptChanged = false;
1414
+ if (manifest && manifest.packageJsonScripts) {
1415
+ // Restore modified scripts to original values
1416
+ for (const [scriptName, originalValue] of Object.entries(manifest.packageJsonScripts.modified)) {
1417
+ if (packageJson.scripts && packageJson.scripts[scriptName]) {
1418
+ packageJson.scripts[scriptName] = originalValue;
1419
+ console.log(` ${c.green('✓')} Restored script: ${scriptName} ${c.dim('(original value)')}`);
1420
+ scriptChanged = true;
1421
+ removedCount++;
1422
+ }
1423
+ }
1424
+ // Remove scripts that were added by Code Guardian
1425
+ for (const scriptName of manifest.packageJsonScripts.added) {
1426
+ if (packageJson.scripts && packageJson.scripts[scriptName]) {
1427
+ delete packageJson.scripts[scriptName];
1428
+ console.log(` ${c.green('✓')} Removed script: ${scriptName}`);
1429
+ scriptChanged = true;
1430
+ removedCount++;
1431
+ }
1432
+ }
1433
+ }
1434
+ else {
1435
+ // Fallback: remove known Code Guardian scripts
1436
+ for (const script of scriptsToRemove) {
1437
+ if (packageJson.scripts && packageJson.scripts[script]) {
1438
+ delete packageJson.scripts[script];
1439
+ console.log(` ${c.green('✓')} Removed script: ${script}`);
1440
+ scriptChanged = true;
1441
+ removedCount++;
1442
+ }
1161
1443
  }
1162
1444
  }
1163
- if (scriptRemoved) {
1445
+ if (scriptChanged) {
1164
1446
  fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
1165
1447
  console.log(` ${c.green('✓')} Updated package.json`);
1166
1448
  }
@@ -1172,11 +1454,20 @@ async function uninstallCodeGuard() {
1172
1454
  console.log(` ${c.red('✗')} Failed to clean package.json: ${error.message}`);
1173
1455
  }
1174
1456
  }
1457
+ // ── Phase 8: Remove .code-guardian directory (including manifest) ────
1458
+ const codeGuardianDir = path.join(cwd, '.code-guardian');
1459
+ if (fs.existsSync(codeGuardianDir)) {
1460
+ try {
1461
+ fs.rmSync(codeGuardianDir, { recursive: true, force: true });
1462
+ console.log(`\n ${c.green('✓')} Removed .code-guardian/`);
1463
+ removedCount++;
1464
+ }
1465
+ catch (error) {
1466
+ console.log(` ${c.red('✗')} Failed to remove .code-guardian/: ${error.message}`);
1467
+ }
1468
+ }
1175
1469
  console.log('');
1176
1470
  console.log(c.green(`✅ Cleanup complete! Removed ${removedCount} item(s).`));
1177
- if (hasBackup) {
1178
- console.log(c.blue(`📦 Backup saved to: ${path.basename(backupDir)}/`));
1179
- }
1180
1471
  console.log('');
1181
1472
  console.log(c.dim('To completely remove the package, run:'));
1182
1473
  console.log(c.dim(' npm uninstall @hatem427/code-guard-ci'));