@vibe-validate/cli 0.10.3 → 0.12.0

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.
Files changed (108) hide show
  1. package/README.md +84 -92
  2. package/dist/bin.js +137 -20
  3. package/dist/bin.js.map +1 -1
  4. package/dist/commands/cleanup.d.ts +4 -0
  5. package/dist/commands/cleanup.d.ts.map +1 -1
  6. package/dist/commands/cleanup.js +96 -15
  7. package/dist/commands/cleanup.js.map +1 -1
  8. package/dist/commands/config.d.ts +4 -0
  9. package/dist/commands/config.d.ts.map +1 -1
  10. package/dist/commands/config.js +83 -15
  11. package/dist/commands/config.js.map +1 -1
  12. package/dist/commands/doctor.d.ts +4 -0
  13. package/dist/commands/doctor.d.ts.map +1 -1
  14. package/dist/commands/doctor.js +385 -82
  15. package/dist/commands/doctor.js.map +1 -1
  16. package/dist/commands/generate-workflow.d.ts +6 -2
  17. package/dist/commands/generate-workflow.d.ts.map +1 -1
  18. package/dist/commands/generate-workflow.js +188 -33
  19. package/dist/commands/generate-workflow.js.map +1 -1
  20. package/dist/commands/history.d.ts +13 -0
  21. package/dist/commands/history.d.ts.map +1 -0
  22. package/dist/commands/history.js +415 -0
  23. package/dist/commands/history.js.map +1 -0
  24. package/dist/commands/init.d.ts +4 -0
  25. package/dist/commands/init.d.ts.map +1 -1
  26. package/dist/commands/init.js +252 -109
  27. package/dist/commands/init.js.map +1 -1
  28. package/dist/commands/pre-commit.d.ts +4 -0
  29. package/dist/commands/pre-commit.d.ts.map +1 -1
  30. package/dist/commands/pre-commit.js +158 -7
  31. package/dist/commands/pre-commit.js.map +1 -1
  32. package/dist/commands/state.d.ts +5 -1
  33. package/dist/commands/state.d.ts.map +1 -1
  34. package/dist/commands/state.js +192 -23
  35. package/dist/commands/state.js.map +1 -1
  36. package/dist/commands/sync-check.d.ts +4 -0
  37. package/dist/commands/sync-check.d.ts.map +1 -1
  38. package/dist/commands/sync-check.js +101 -14
  39. package/dist/commands/sync-check.js.map +1 -1
  40. package/dist/commands/validate.d.ts +5 -1
  41. package/dist/commands/validate.d.ts.map +1 -1
  42. package/dist/commands/validate.js +184 -28
  43. package/dist/commands/validate.js.map +1 -1
  44. package/dist/commands/watch-pr.d.ts +10 -0
  45. package/dist/commands/watch-pr.d.ts.map +1 -0
  46. package/dist/commands/watch-pr.js +443 -0
  47. package/dist/commands/watch-pr.js.map +1 -0
  48. package/dist/schemas/watch-pr-schema.d.ts +261 -0
  49. package/dist/schemas/watch-pr-schema.d.ts.map +1 -0
  50. package/dist/schemas/watch-pr-schema.js +58 -0
  51. package/dist/schemas/watch-pr-schema.js.map +1 -0
  52. package/dist/scripts/generate-watch-pr-schema.d.ts +12 -0
  53. package/dist/scripts/generate-watch-pr-schema.d.ts.map +1 -0
  54. package/dist/scripts/generate-watch-pr-schema.js +35 -0
  55. package/dist/scripts/generate-watch-pr-schema.js.map +1 -0
  56. package/dist/services/ci-provider-registry.d.ts +38 -0
  57. package/dist/services/ci-provider-registry.d.ts.map +1 -0
  58. package/dist/services/ci-provider-registry.js +53 -0
  59. package/dist/services/ci-provider-registry.js.map +1 -0
  60. package/dist/services/ci-provider.d.ts +165 -0
  61. package/dist/services/ci-provider.d.ts.map +1 -0
  62. package/dist/services/ci-provider.js +11 -0
  63. package/dist/services/ci-provider.js.map +1 -0
  64. package/dist/services/ci-providers/github-actions.d.ts +41 -0
  65. package/dist/services/ci-providers/github-actions.d.ts.map +1 -0
  66. package/dist/services/ci-providers/github-actions.js +314 -0
  67. package/dist/services/ci-providers/github-actions.js.map +1 -0
  68. package/dist/utils/check-validation.d.ts +7 -4
  69. package/dist/utils/check-validation.d.ts.map +1 -1
  70. package/dist/utils/check-validation.js +129 -48
  71. package/dist/utils/check-validation.js.map +1 -1
  72. package/dist/utils/config-loader.d.ts +15 -3
  73. package/dist/utils/config-loader.d.ts.map +1 -1
  74. package/dist/utils/config-loader.js +61 -17
  75. package/dist/utils/config-loader.js.map +1 -1
  76. package/dist/utils/context-detector.d.ts +1 -1
  77. package/dist/utils/context-detector.js +1 -1
  78. package/dist/utils/normalize-line-endings.d.ts +53 -0
  79. package/dist/utils/normalize-line-endings.d.ts.map +1 -0
  80. package/dist/utils/normalize-line-endings.js +57 -0
  81. package/dist/utils/normalize-line-endings.js.map +1 -0
  82. package/dist/utils/run-validation-with-cache.d.ts +48 -0
  83. package/dist/utils/run-validation-with-cache.d.ts.map +1 -0
  84. package/dist/utils/run-validation-with-cache.js +123 -0
  85. package/dist/utils/run-validation-with-cache.js.map +1 -0
  86. package/dist/utils/runner-adapter.d.ts +1 -0
  87. package/dist/utils/runner-adapter.d.ts.map +1 -1
  88. package/dist/utils/runner-adapter.js +25 -17
  89. package/dist/utils/runner-adapter.js.map +1 -1
  90. package/dist/utils/setup-checks/gitignore-check.d.ts +10 -15
  91. package/dist/utils/setup-checks/gitignore-check.d.ts.map +1 -1
  92. package/dist/utils/setup-checks/gitignore-check.js +20 -138
  93. package/dist/utils/setup-checks/gitignore-check.js.map +1 -1
  94. package/dist/utils/template-discovery.d.ts +40 -0
  95. package/dist/utils/template-discovery.d.ts.map +1 -0
  96. package/dist/utils/template-discovery.js +136 -0
  97. package/dist/utils/template-discovery.js.map +1 -0
  98. package/dist/utils/validate-workflow.d.ts +28 -0
  99. package/dist/utils/validate-workflow.d.ts.map +1 -0
  100. package/dist/utils/validate-workflow.js +247 -0
  101. package/dist/utils/validate-workflow.js.map +1 -0
  102. package/dist/utils/validation-cache.d.ts +30 -0
  103. package/dist/utils/validation-cache.d.ts.map +1 -0
  104. package/dist/utils/validation-cache.js +57 -0
  105. package/dist/utils/validation-cache.js.map +1 -0
  106. package/package.json +19 -16
  107. package/watch-pr-result.schema.json +204 -0
  108. package/LICENSE +0 -21
@@ -12,9 +12,14 @@
12
12
  */
13
13
  import { existsSync, readFileSync } from 'fs';
14
14
  import { execSync } from 'child_process';
15
- import { loadConfig } from '../utils/config-loader.js';
15
+ import { stringify as stringifyYaml } from 'yaml';
16
+ import { loadConfig, findConfigPath, loadConfigWithErrors } from '../utils/config-loader.js';
16
17
  import { checkSync, ciConfigToWorkflowOptions } from './generate-workflow.js';
17
18
  import { getMainBranch, getRemoteOrigin } from '@vibe-validate/config';
19
+ import { formatTemplateList } from '../utils/template-discovery.js';
20
+ import { checkHistoryHealth as checkValidationHistoryHealth } from '@vibe-validate/history';
21
+ /** @deprecated State file deprecated in v0.12.0 - validation now uses git notes */
22
+ const DEPRECATED_STATE_FILE = '.vibe-validate-state.yaml';
18
23
  /**
19
24
  * Check Node.js version meets requirements
20
25
  */
@@ -93,16 +98,12 @@ function checkGitRepository() {
93
98
  * Check if configuration file exists
94
99
  */
95
100
  function checkConfigFile() {
96
- const configPatterns = [
97
- 'vibe-validate.config.yaml',
98
- 'vibe-validate.config.mjs', // Legacy (deprecated)
99
- ];
100
- const found = configPatterns.find(pattern => existsSync(pattern));
101
- if (found) {
101
+ const yamlConfig = 'vibe-validate.config.yaml';
102
+ if (existsSync(yamlConfig)) {
102
103
  return {
103
104
  name: 'Configuration file',
104
105
  passed: true,
105
- message: `Found: ${found}`,
106
+ message: `Found: ${yamlConfig}`,
106
107
  };
107
108
  }
108
109
  else {
@@ -114,48 +115,71 @@ function checkConfigFile() {
114
115
  };
115
116
  }
116
117
  }
117
- /**
118
- * Check if config format needs migration from .mjs to .yaml
119
- */
120
- function checkConfigFormatMigration() {
121
- const mjsConfig = 'vibe-validate.config.mjs';
122
- const yamlConfig = 'vibe-validate.config.yaml';
123
- // If using YAML, all good!
124
- if (existsSync(yamlConfig)) {
125
- return {
126
- name: 'Config format',
127
- passed: true,
128
- message: 'Using modern YAML format',
129
- };
130
- }
131
- // If using legacy .mjs format, suggest migration
132
- if (existsSync(mjsConfig)) {
133
- return {
134
- name: 'Config format',
135
- passed: false,
136
- message: 'Using deprecated .mjs format (will be removed in v1.0)',
137
- suggestion: '⚠️ Migrate to YAML:\n 1. Run: vibe-validate init --migrate\n 2. Test the new YAML config\n 3. Delete vibe-validate.config.mjs after migration',
138
- };
139
- }
140
- // No config found (will be caught by checkConfigFile)
141
- return {
142
- name: 'Config format',
143
- passed: true,
144
- message: 'Skipped (no config file)',
145
- };
146
- }
147
118
  /**
148
119
  * Check if configuration is valid
120
+ *
121
+ * @param config - Config from loadConfig() (may be null)
122
+ * @param configWithErrors - Result from loadConfigWithErrors() with detailed error info
149
123
  */
150
- async function checkConfigValid(config) {
124
+ async function checkConfigValid(config, configWithErrors) {
151
125
  try {
152
126
  if (!config) {
153
- return {
154
- name: 'Configuration valid',
155
- passed: false,
156
- message: 'Failed to load configuration',
157
- suggestion: 'Check syntax in vibe-validate.config.*',
158
- };
127
+ // Check if we have detailed error information
128
+ if (configWithErrors?.errors && configWithErrors.filePath) {
129
+ const fileName = configWithErrors.filePath.split('/').pop() || 'vibe-validate.config.yaml';
130
+ const errorList = configWithErrors.errors.slice(0, 5); // Show first 5 errors
131
+ const errorMessages = errorList.map(err => ` • ${err}`).join('\n');
132
+ return {
133
+ name: 'Configuration valid',
134
+ passed: false,
135
+ message: `Found ${fileName} but it contains validation errors:\n${errorMessages}`,
136
+ suggestion: [
137
+ 'Fix validation errors shown above',
138
+ 'See configuration docs: https://github.com/jdutton/vibe-validate/blob/main/docs/configuration-reference.md',
139
+ 'JSON Schema for IDE validation: https://raw.githubusercontent.com/jdutton/vibe-validate/main/packages/config/vibe-validate.schema.json',
140
+ 'Example YAML configs: https://github.com/jdutton/vibe-validate/tree/main/config-templates'
141
+ ].join('\n '),
142
+ };
143
+ }
144
+ // Fallback: try to find config file path
145
+ const configPath = findConfigPath();
146
+ if (configPath) {
147
+ const fileName = configPath.split('/').pop() || 'vibe-validate.config.yaml';
148
+ return {
149
+ name: 'Configuration valid',
150
+ passed: false,
151
+ message: `Found ${fileName} but it contains validation errors`,
152
+ suggestion: [
153
+ `Fix syntax/validation errors in ${fileName}`,
154
+ 'See configuration docs: https://github.com/jdutton/vibe-validate/blob/main/docs/configuration-reference.md',
155
+ 'JSON Schema for IDE validation: https://raw.githubusercontent.com/jdutton/vibe-validate/main/packages/config/vibe-validate.schema.json',
156
+ 'Example YAML configs: https://github.com/jdutton/vibe-validate/tree/main/config-templates'
157
+ ].join('\n '),
158
+ };
159
+ }
160
+ else {
161
+ // No config file found
162
+ const templateList = formatTemplateList();
163
+ return {
164
+ name: 'Configuration valid',
165
+ passed: false,
166
+ message: 'No configuration file found',
167
+ suggestion: [
168
+ 'Copy a config template from GitHub:',
169
+ 'https://github.com/jdutton/vibe-validate/tree/main/config-templates',
170
+ '',
171
+ 'Available templates:',
172
+ ...templateList.map(line => line),
173
+ '',
174
+ 'Quick start:',
175
+ 'curl -o vibe-validate.config.yaml \\',
176
+ ' https://raw.githubusercontent.com/jdutton/vibe-validate/main/config-templates/typescript-nodejs.yaml',
177
+ '',
178
+ 'JSON Schema for IDE validation:',
179
+ 'https://raw.githubusercontent.com/jdutton/vibe-validate/main/packages/config/vibe-validate.schema.json',
180
+ ].join('\n '),
181
+ };
182
+ }
159
183
  }
160
184
  return {
161
185
  name: 'Configuration valid',
@@ -168,7 +192,7 @@ async function checkConfigValid(config) {
168
192
  name: 'Configuration valid',
169
193
  passed: false,
170
194
  message: `Invalid configuration: ${_error instanceof Error ? _error.message : String(_error)}`,
171
- suggestion: 'Fix syntax errors in vibe-validate.config.*',
195
+ suggestion: 'Check syntax in vibe-validate.config.yaml',
172
196
  };
173
197
  }
174
198
  }
@@ -235,7 +259,7 @@ async function checkWorkflowSync(config) {
235
259
  message: 'Skipped (no config)',
236
260
  };
237
261
  }
238
- // Use CI config from vibe-validate.config.mjs if available
262
+ // Use CI config from vibe-validate config
239
263
  const generateOptions = ciConfigToWorkflowOptions(config);
240
264
  const { inSync, diff } = checkSync(config, generateOptions);
241
265
  if (inSync) {
@@ -336,6 +360,19 @@ async function checkVersion() {
336
360
  encoding: 'utf8',
337
361
  stdio: 'pipe',
338
362
  }).trim();
363
+ // Compare versions (simple semver comparison)
364
+ const current = currentVersion.split('.').map(Number);
365
+ const latest = latestVersion.split('.').map(Number);
366
+ let isOutdated = false;
367
+ for (let i = 0; i < 3; i++) {
368
+ if (current[i] < latest[i]) {
369
+ isOutdated = true;
370
+ break;
371
+ }
372
+ else if (current[i] > latest[i]) {
373
+ break; // Current is newer
374
+ }
375
+ }
339
376
  if (currentVersion === latestVersion) {
340
377
  return {
341
378
  name: 'vibe-validate version',
@@ -343,7 +380,8 @@ async function checkVersion() {
343
380
  message: `Current version ${currentVersion} is up to date`,
344
381
  };
345
382
  }
346
- else {
383
+ else if (isOutdated) {
384
+ // Only show suggestion if current version is behind npm
347
385
  return {
348
386
  name: 'vibe-validate version',
349
387
  passed: true, // Warning only, not a failure
@@ -351,6 +389,14 @@ async function checkVersion() {
351
389
  suggestion: `Upgrade: npm install -D vibe-validate@latest (or pnpm add -D vibe-validate@latest)\n 💡 After upgrade: Run 'vibe-validate doctor' to verify setup`,
352
390
  };
353
391
  }
392
+ else {
393
+ // Current version is ahead of npm (pre-release or unpublished)
394
+ return {
395
+ name: 'vibe-validate version',
396
+ passed: true,
397
+ message: `Current: ${currentVersion} (ahead of npm: ${latestVersion})`,
398
+ };
399
+ }
354
400
  }
355
401
  catch (_npmError) {
356
402
  // npm registry unavailable - not a critical error
@@ -370,36 +416,35 @@ async function checkVersion() {
370
416
  }
371
417
  }
372
418
  /**
373
- * Check if .vibe-validate-state.yaml is in .gitignore
419
+ * Check if deprecated state file is in .gitignore
374
420
  */
375
421
  function checkGitignoreStateFile() {
376
422
  const gitignorePath = '.gitignore';
377
- const stateFileName = '.vibe-validate-state.yaml';
423
+ const stateFileName = DEPRECATED_STATE_FILE;
378
424
  // Check if .gitignore exists
379
425
  if (!existsSync(gitignorePath)) {
380
426
  return {
381
427
  name: 'Gitignore state file',
382
- passed: false,
383
- message: '.gitignore file not found',
384
- suggestion: 'Manual: echo ".vibe-validate-state.yaml" >> .gitignore\n 💡 Or run: vibe-validate init --fix-gitignore',
428
+ passed: true,
429
+ message: '.gitignore file not found (state file deprecated - using git notes)',
385
430
  };
386
431
  }
387
432
  try {
388
433
  const gitignoreContent = readFileSync(gitignorePath, 'utf8');
389
- // Check if state file is already in .gitignore
434
+ // Check if deprecated state file is in .gitignore
390
435
  if (gitignoreContent.includes(stateFileName)) {
391
436
  return {
392
- name: 'Gitignore state file',
393
- passed: true,
394
- message: '.vibe-validate-state.yaml is in .gitignore',
437
+ name: 'Gitignore state file (deprecated)',
438
+ passed: false,
439
+ message: `${DEPRECATED_STATE_FILE} in .gitignore (deprecated - can be removed)`,
440
+ suggestion: `Remove from .gitignore: sed -i.bak '/${DEPRECATED_STATE_FILE}/d' .gitignore && rm .gitignore.bak\n ℹ️ Validation now uses git notes instead of state file`,
395
441
  };
396
442
  }
397
443
  else {
398
444
  return {
399
445
  name: 'Gitignore state file',
400
- passed: false,
401
- message: '.vibe-validate-state.yaml not in .gitignore (state file should not be committed)',
402
- suggestion: 'Manual: echo ".vibe-validate-state.yaml" >> .gitignore\n 💡 Or run: vibe-validate init --fix-gitignore',
446
+ passed: true,
447
+ message: 'No deprecated state file entries in .gitignore',
403
448
  };
404
449
  }
405
450
  }
@@ -413,24 +458,54 @@ function checkGitignoreStateFile() {
413
458
  }
414
459
  }
415
460
  /**
416
- * Check if validation state file exists
461
+ * Check if deprecated validation state file exists
417
462
  */
418
463
  function checkValidationState() {
419
- const statePath = '.vibe-validate-state.yaml';
420
- // This check is informational only - missing state file is not an error
421
- // (fresh checkouts, CI environments, etc. won't have it until first validation run)
464
+ const statePath = DEPRECATED_STATE_FILE;
465
+ // Check if deprecated state file exists
422
466
  if (existsSync(statePath)) {
423
467
  return {
424
- name: 'Validation state',
425
- passed: true,
426
- message: 'Validation state file exists',
468
+ name: 'Validation state (deprecated)',
469
+ passed: false,
470
+ message: `${DEPRECATED_STATE_FILE} found (deprecated file - safe to remove)`,
471
+ suggestion: `Remove deprecated state file: rm ${DEPRECATED_STATE_FILE}\n ℹ️ Validation now uses git notes for improved caching`,
427
472
  };
428
473
  }
429
474
  else {
430
475
  return {
431
476
  name: 'Validation state',
432
- passed: true, // Always pass - this is informational only
433
- message: 'Validation state file not found (will be created on first validation run)',
477
+ passed: true,
478
+ message: 'No deprecated state file found (using git notes)',
479
+ };
480
+ }
481
+ }
482
+ /**
483
+ * Check validation history health
484
+ */
485
+ async function checkHistoryHealth() {
486
+ try {
487
+ const health = await checkValidationHistoryHealth();
488
+ if (!health.shouldWarn) {
489
+ return {
490
+ name: 'Validation history',
491
+ passed: true,
492
+ message: `${health.totalNotes} tree hashes tracked, history is healthy`,
493
+ };
494
+ }
495
+ // Has warnings
496
+ return {
497
+ name: 'Validation history',
498
+ passed: false,
499
+ message: `${health.totalNotes} tree hashes tracked (${health.oldNotesCount} older than 90 days)`,
500
+ suggestion: 'Prune old history: vibe-validate history prune --older-than "90 days"',
501
+ };
502
+ }
503
+ catch (_error) {
504
+ // Git notes not available or other error - not critical
505
+ return {
506
+ name: 'Validation history',
507
+ passed: true,
508
+ message: 'History unavailable (not in git repo or no validation runs yet)',
434
509
  };
435
510
  }
436
511
  }
@@ -589,6 +664,91 @@ async function checkRemoteMainBranch(config) {
589
664
  };
590
665
  }
591
666
  }
667
+ /**
668
+ * Check if secret scanning is configured and tool is available
669
+ */
670
+ async function checkSecretScanning(config) {
671
+ try {
672
+ if (!config) {
673
+ return {
674
+ name: 'Pre-commit secret scanning',
675
+ passed: true,
676
+ message: 'Skipped (no config)',
677
+ };
678
+ }
679
+ const secretScanning = config.hooks?.preCommit?.secretScanning;
680
+ // If not configured at all, recommend enabling it
681
+ if (!secretScanning) {
682
+ return {
683
+ name: 'Pre-commit secret scanning',
684
+ passed: true,
685
+ message: 'Secret scanning not configured',
686
+ suggestion: 'Recommended: Enable secret scanning to prevent credential leaks\n • Add to config: hooks.preCommit.secretScanning.enabled=true\n • scanCommand: "gitleaks protect --staged --verbose"\n • Install gitleaks: brew install gitleaks',
687
+ };
688
+ }
689
+ // If explicitly disabled, acknowledge user choice
690
+ if (secretScanning.enabled === false) {
691
+ return {
692
+ name: 'Pre-commit secret scanning',
693
+ passed: true,
694
+ message: 'Secret scanning disabled in config (user preference)',
695
+ };
696
+ }
697
+ // If enabled, check if tool is available
698
+ if (secretScanning.scanCommand) {
699
+ // Extract tool name (first word of command)
700
+ const toolName = secretScanning.scanCommand.split(' ')[0];
701
+ try {
702
+ // Try to get tool version
703
+ let version;
704
+ try {
705
+ version = execSync(`${toolName} version`, { encoding: 'utf8', stdio: 'pipe' }).trim();
706
+ }
707
+ catch (_versionError) {
708
+ // Try --version flag
709
+ version = execSync(`${toolName} --version`, { encoding: 'utf8', stdio: 'pipe' }).trim();
710
+ }
711
+ return {
712
+ name: 'Pre-commit secret scanning',
713
+ passed: true,
714
+ message: `Secret scanning enabled with ${toolName} ${version}`,
715
+ };
716
+ }
717
+ catch (_error) {
718
+ // Tool not found
719
+ // In CI, this is expected (secret scanning is pre-commit only, not needed in CI)
720
+ const isCI = process.env.CI === 'true' || process.env.CI === '1';
721
+ if (isCI) {
722
+ return {
723
+ name: 'Pre-commit secret scanning',
724
+ passed: true,
725
+ message: `Secret scanning enabled (pre-commit only, not needed in CI)`,
726
+ };
727
+ }
728
+ return {
729
+ name: 'Pre-commit secret scanning',
730
+ passed: true, // Advisory only, never fails
731
+ message: `Secret scanning enabled but '${toolName}' not found`,
732
+ suggestion: `Install ${toolName}:\n • gitleaks: brew install gitleaks\n • Or disable: set hooks.preCommit.secretScanning.enabled=false in config`,
733
+ };
734
+ }
735
+ }
736
+ // Enabled but no scanCommand (should be caught by schema validation)
737
+ return {
738
+ name: 'Pre-commit secret scanning',
739
+ passed: true,
740
+ message: 'Secret scanning enabled but no scanCommand configured',
741
+ suggestion: 'Add hooks.preCommit.secretScanning.scanCommand to config',
742
+ };
743
+ }
744
+ catch (_error) {
745
+ return {
746
+ name: 'Pre-commit secret scanning',
747
+ passed: true,
748
+ message: 'Skipped (config or execution error)',
749
+ };
750
+ }
751
+ }
592
752
  /**
593
753
  * Run all doctor checks
594
754
  */
@@ -597,12 +757,15 @@ export async function runDoctor(options = {}) {
597
757
  const allChecks = [];
598
758
  // Load config once to avoid duplicate warnings
599
759
  let config;
760
+ let configWithErrors;
600
761
  try {
601
762
  config = await loadConfig();
763
+ configWithErrors = await loadConfigWithErrors();
602
764
  }
603
765
  catch (_error) {
604
766
  // Config load error will be caught by checkConfigValid
605
767
  config = null;
768
+ configWithErrors = { config: null, errors: null, filePath: null };
606
769
  }
607
770
  // Run all checks
608
771
  allChecks.push(await checkVersion());
@@ -610,16 +773,17 @@ export async function runDoctor(options = {}) {
610
773
  allChecks.push(checkGitInstalled());
611
774
  allChecks.push(checkGitRepository());
612
775
  allChecks.push(checkConfigFile());
613
- allChecks.push(checkConfigFormatMigration());
614
- allChecks.push(await checkConfigValid(config));
776
+ allChecks.push(await checkConfigValid(config, configWithErrors));
615
777
  allChecks.push(await checkPackageManager(config));
616
778
  allChecks.push(await checkMainBranch(config));
617
779
  allChecks.push(await checkRemoteOrigin(config));
618
780
  allChecks.push(await checkRemoteMainBranch(config));
619
781
  allChecks.push(await checkWorkflowSync(config));
620
782
  allChecks.push(await checkPreCommitHook(config));
783
+ allChecks.push(await checkSecretScanning(config));
621
784
  allChecks.push(checkGitignoreStateFile());
622
785
  allChecks.push(checkValidationState());
786
+ allChecks.push(await checkHistoryHealth());
623
787
  // Collect suggestions from failed checks
624
788
  const suggestions = allChecks
625
789
  .filter(c => !c.passed && c.suggestion)
@@ -627,8 +791,9 @@ export async function runDoctor(options = {}) {
627
791
  const allPassed = allChecks.every(c => c.passed);
628
792
  const totalChecks = allChecks.length;
629
793
  const passedChecks = allChecks.filter(c => c.passed).length;
630
- // In non-verbose mode, only show failing checks (or all if all pass for summary)
631
- const checks = verbose ? allChecks : (allPassed ? allChecks : allChecks.filter(c => !c.passed));
794
+ // In non-verbose mode, show failing checks OR checks with recommendations
795
+ // In verbose mode, always show all checks
796
+ const checks = verbose ? allChecks : allChecks.filter(c => !c.passed || c.suggestion);
632
797
  return {
633
798
  allPassed,
634
799
  checks,
@@ -644,15 +809,30 @@ export async function runDoctor(options = {}) {
644
809
  export function doctorCommand(program) {
645
810
  program
646
811
  .command('doctor')
647
- .description('Diagnose vibe-validate setup and environment')
648
- .option('--json', 'Output results as JSON')
649
- .option('--verbose', 'Show all checks including passing ones')
650
- .action(async (options) => {
812
+ .description('Diagnose vibe-validate setup and environment (run after upgrading)')
813
+ .option('--yaml', 'Output YAML only (no human-friendly display)')
814
+ .action(async (options, command) => {
815
+ // Get verbose from global options (inherited from program)
816
+ const verbose = command.optsWithGlobals().verbose;
651
817
  try {
652
- const result = await runDoctor({ verbose: options.verbose });
653
- if (options.json) {
654
- // JSON output for programmatic use
655
- console.log(JSON.stringify(result, null, 2));
818
+ const result = await runDoctor({ verbose });
819
+ if (options.yaml) {
820
+ // YAML mode: Output structured result to stdout
821
+ // Small delay to ensure stderr is flushed
822
+ await new Promise(resolve => setTimeout(resolve, 10));
823
+ // RFC 4627 separator
824
+ process.stdout.write('---\n');
825
+ // Write pure YAML
826
+ process.stdout.write(stringifyYaml(result));
827
+ // CRITICAL: Wait for stdout to flush before exiting
828
+ await new Promise(resolve => {
829
+ if (process.stdout.write('')) {
830
+ resolve();
831
+ }
832
+ else {
833
+ process.stdout.once('drain', resolve);
834
+ }
835
+ });
656
836
  }
657
837
  else {
658
838
  // Human-friendly output
@@ -668,7 +848,7 @@ export function doctorCommand(program) {
668
848
  const icon = check.passed ? '✅' : '❌';
669
849
  console.log(`${icon} ${check.name}`);
670
850
  console.log(` ${check.message}`);
671
- if (check.suggestion && !check.passed) {
851
+ if (check.suggestion) {
672
852
  console.log(` 💡 ${check.suggestion}`);
673
853
  }
674
854
  console.log('');
@@ -697,4 +877,127 @@ export function doctorCommand(program) {
697
877
  }
698
878
  });
699
879
  }
880
+ /**
881
+ * Show verbose help with detailed documentation
882
+ */
883
+ export function showDoctorVerboseHelp() {
884
+ console.log(`# doctor Command Reference
885
+
886
+ > Diagnose vibe-validate setup and environment (run after upgrading)
887
+
888
+ ## Overview
889
+
890
+ The \`doctor\` command performs comprehensive diagnostics of your vibe-validate setup and environment. It checks Node.js version, git repository health, configuration validity, package manager availability, and CI/CD integration status.
891
+
892
+ ## How It Works
893
+
894
+ 1. Checks Node.js version (20+)
895
+ 2. Verifies git repository exists
896
+ 3. Checks package manager availability
897
+ 4. Validates configuration file
898
+ 5. Checks pre-commit hook setup
899
+ 6. Verifies GitHub Actions workflow sync
900
+
901
+ ## Options
902
+
903
+ - \`--yaml\` - Output YAML only (no human-friendly display)
904
+
905
+ ## Exit Codes
906
+
907
+ - \`0\` - All critical checks passed
908
+ - \`1\` - One or more critical checks failed
909
+
910
+ ## Examples
911
+
912
+ \`\`\`bash
913
+ # Run diagnostics
914
+ vibe-validate doctor
915
+
916
+ # YAML output only
917
+ vibe-validate doctor --yaml
918
+ \`\`\`
919
+
920
+ ## Common Workflows
921
+
922
+ ### After installing vibe-validate
923
+
924
+ \`\`\`bash
925
+ # Initialize configuration
926
+ vibe-validate init
927
+
928
+ # Verify setup
929
+ vibe-validate doctor
930
+ \`\`\`
931
+
932
+ ### After upgrading vibe-validate
933
+
934
+ \`\`\`bash
935
+ # Upgrade package
936
+ npm install -D vibe-validate@latest
937
+
938
+ # CRITICAL: Always run doctor after upgrade
939
+ vibe-validate doctor
940
+
941
+ # Fix any issues reported
942
+ \`\`\`
943
+
944
+ ### Troubleshooting validation issues
945
+
946
+ \`\`\`bash
947
+ # Diagnose environment
948
+ vibe-validate doctor
949
+
950
+ # Fix reported issues
951
+
952
+ # Retry validation
953
+ vibe-validate validate
954
+ \`\`\`
955
+
956
+ ## Checks Performed
957
+
958
+ ### Critical Checks (must pass)
959
+
960
+ - **Node.js version**: >=20.0.0 required
961
+ - **Git installed**: git command available
962
+ - **Git repository**: Current directory is a git repo
963
+ - **Configuration file**: vibe-validate.config.yaml exists
964
+ - **Configuration valid**: No syntax or schema errors
965
+
966
+ ### Advisory Checks (warnings only)
967
+
968
+ - **Package manager**: npm or pnpm available
969
+ - **Pre-commit hook**: Husky pre-commit hook configured
970
+ - **GitHub Actions workflow**: Workflow in sync with config
971
+ - **Validation history**: Git notes health status
972
+
973
+ ## Error Recovery
974
+
975
+ **If Node.js version check fails:**
976
+ \`\`\`bash
977
+ # Upgrade Node.js to 20+
978
+ # Via nvm:
979
+ nvm install 20
980
+ nvm use 20
981
+
982
+ # Or download from: https://nodejs.org/
983
+ \`\`\`
984
+
985
+ **If configuration check fails:**
986
+ \`\`\`bash
987
+ # Reinitialize with template
988
+ vibe-validate init --force
989
+
990
+ # Or manually fix YAML syntax errors
991
+ \`\`\`
992
+
993
+ **If workflow is out of sync:**
994
+ \`\`\`bash
995
+ # Regenerate workflow
996
+ vibe-validate generate-workflow
997
+
998
+ # Or reinitialize
999
+ vibe-validate init --setup-workflow
1000
+ \`\`\`
1001
+ `);
1002
+ }
700
1003
  //# sourceMappingURL=doctor.js.map