@vibe-validate/cli 0.14.2 → 0.15.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/README.md +26 -6
  2. package/bin/vibe-validate +131 -0
  3. package/config-templates/minimal.yaml +1 -1
  4. package/config-templates/typescript-library.yaml +1 -1
  5. package/config-templates/typescript-nodejs.yaml +1 -1
  6. package/config-templates/typescript-react.yaml +1 -1
  7. package/dist/bin.js +41 -21
  8. package/dist/bin.js.map +1 -1
  9. package/dist/commands/cleanup.d.ts +1 -1
  10. package/dist/commands/cleanup.d.ts.map +1 -1
  11. package/dist/commands/cleanup.js +135 -16
  12. package/dist/commands/cleanup.js.map +1 -1
  13. package/dist/commands/config.d.ts.map +1 -1
  14. package/dist/commands/config.js +13 -10
  15. package/dist/commands/config.js.map +1 -1
  16. package/dist/commands/doctor.d.ts.map +1 -1
  17. package/dist/commands/doctor.js +253 -189
  18. package/dist/commands/doctor.js.map +1 -1
  19. package/dist/commands/generate-workflow.d.ts.map +1 -1
  20. package/dist/commands/generate-workflow.js +15 -13
  21. package/dist/commands/generate-workflow.js.map +1 -1
  22. package/dist/commands/history.d.ts.map +1 -1
  23. package/dist/commands/history.js +305 -98
  24. package/dist/commands/history.js.map +1 -1
  25. package/dist/commands/init.d.ts.map +1 -1
  26. package/dist/commands/init.js +14 -6
  27. package/dist/commands/init.js.map +1 -1
  28. package/dist/commands/pre-commit.d.ts.map +1 -1
  29. package/dist/commands/pre-commit.js +8 -3
  30. package/dist/commands/pre-commit.js.map +1 -1
  31. package/dist/commands/run.d.ts.map +1 -1
  32. package/dist/commands/run.js +620 -217
  33. package/dist/commands/run.js.map +1 -1
  34. package/dist/commands/state.d.ts.map +1 -1
  35. package/dist/commands/state.js +4 -7
  36. package/dist/commands/state.js.map +1 -1
  37. package/dist/commands/validate.d.ts.map +1 -1
  38. package/dist/commands/validate.js +7 -7
  39. package/dist/commands/validate.js.map +1 -1
  40. package/dist/commands/watch-pr.d.ts.map +1 -1
  41. package/dist/commands/watch-pr.js +73 -49
  42. package/dist/commands/watch-pr.js.map +1 -1
  43. package/dist/schemas/run-result-schema-export.d.ts +32 -0
  44. package/dist/schemas/run-result-schema-export.d.ts.map +1 -0
  45. package/dist/schemas/run-result-schema-export.js +40 -0
  46. package/dist/schemas/run-result-schema-export.js.map +1 -0
  47. package/dist/schemas/run-result-schema.d.ts +850 -0
  48. package/dist/schemas/run-result-schema.d.ts.map +1 -0
  49. package/dist/schemas/run-result-schema.js +67 -0
  50. package/dist/schemas/run-result-schema.js.map +1 -0
  51. package/dist/schemas/watch-pr-schema.d.ts +431 -33
  52. package/dist/schemas/watch-pr-schema.d.ts.map +1 -1
  53. package/dist/schemas/watch-pr-schema.js +2 -2
  54. package/dist/schemas/watch-pr-schema.js.map +1 -1
  55. package/dist/scripts/generate-run-result-schema.d.ts +10 -0
  56. package/dist/scripts/generate-run-result-schema.d.ts.map +1 -0
  57. package/dist/scripts/generate-run-result-schema.js +20 -0
  58. package/dist/scripts/generate-run-result-schema.js.map +1 -0
  59. package/dist/scripts/generate-watch-pr-schema.js +3 -3
  60. package/dist/scripts/generate-watch-pr-schema.js.map +1 -1
  61. package/dist/services/ci-provider.d.ts +21 -6
  62. package/dist/services/ci-provider.d.ts.map +1 -1
  63. package/dist/services/ci-providers/github-actions.d.ts +21 -0
  64. package/dist/services/ci-providers/github-actions.d.ts.map +1 -1
  65. package/dist/services/ci-providers/github-actions.js +65 -49
  66. package/dist/services/ci-providers/github-actions.js.map +1 -1
  67. package/dist/utils/check-validation.d.ts.map +1 -1
  68. package/dist/utils/check-validation.js +9 -5
  69. package/dist/utils/check-validation.js.map +1 -1
  70. package/dist/utils/config-error-reporter.js +9 -7
  71. package/dist/utils/config-error-reporter.js.map +1 -1
  72. package/dist/utils/config-loader.js +5 -5
  73. package/dist/utils/config-loader.js.map +1 -1
  74. package/dist/utils/git-detection.d.ts +0 -22
  75. package/dist/utils/git-detection.d.ts.map +1 -1
  76. package/dist/utils/git-detection.js +64 -56
  77. package/dist/utils/git-detection.js.map +1 -1
  78. package/dist/utils/pid-lock.js +7 -7
  79. package/dist/utils/pid-lock.js.map +1 -1
  80. package/dist/utils/project-id.d.ts.map +1 -1
  81. package/dist/utils/project-id.js +8 -6
  82. package/dist/utils/project-id.js.map +1 -1
  83. package/dist/utils/runner-adapter.js +4 -3
  84. package/dist/utils/runner-adapter.js.map +1 -1
  85. package/dist/utils/setup-checks/hooks-check.js +3 -3
  86. package/dist/utils/setup-checks/hooks-check.js.map +1 -1
  87. package/dist/utils/setup-checks/workflow-check.js +3 -3
  88. package/dist/utils/setup-checks/workflow-check.js.map +1 -1
  89. package/dist/utils/temp-files.d.ts +67 -0
  90. package/dist/utils/temp-files.d.ts.map +1 -0
  91. package/dist/utils/temp-files.js +202 -0
  92. package/dist/utils/temp-files.js.map +1 -0
  93. package/dist/utils/template-discovery.d.ts.map +1 -1
  94. package/dist/utils/template-discovery.js +5 -4
  95. package/dist/utils/template-discovery.js.map +1 -1
  96. package/dist/utils/validate-workflow.d.ts.map +1 -1
  97. package/dist/utils/validate-workflow.js +169 -150
  98. package/dist/utils/validate-workflow.js.map +1 -1
  99. package/dist/vibe-validate +131 -0
  100. package/package.json +11 -9
  101. package/run-result.schema.json +186 -0
  102. package/watch-pr-result.schema.json +128 -6
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Temporary file management utilities
3
+ *
4
+ * Provides organized temp directory structure for vibe-validate output files:
5
+ * - /tmp/vibe-validate/runs/{YYYY-MM-DD}/{shortHash-HH-mm-ss}/
6
+ * - Cleanup utilities for old temp files
7
+ * - Storage size calculation
8
+ */
9
+ import { mkdir, readdir, rm, stat } from 'node:fs/promises';
10
+ import { tmpdir } from 'node:os';
11
+ import { join } from 'node:path';
12
+ /**
13
+ * Get the root temporary directory for vibe-validate
14
+ * @returns Path to /tmp/vibe-validate (or OS equivalent)
15
+ */
16
+ export function getVibeValidateTempDir() {
17
+ return join(tmpdir(), 'vibe-validate');
18
+ }
19
+ /**
20
+ * Get the organized output directory for a specific run
21
+ * @param treeHash - Git tree hash or identifier
22
+ * @returns Path like /tmp/vibe-validate/runs/2025-11-05/abc123-17-30-45/
23
+ */
24
+ export function getRunOutputDir(treeHash) {
25
+ const now = new Date();
26
+ // Date folder: YYYY-MM-DD
27
+ const dateFolder = now.toISOString().split('T')[0];
28
+ // Short hash: first 6 chars
29
+ const shortHash = treeHash.substring(0, 6);
30
+ // Time suffix: HH-mm-ss
31
+ const timeSuffix = now.toISOString().split('T')[1].substring(0, 8).replace(/:/g, '-');
32
+ // Combined: abc123-17-30-45
33
+ const runFolder = `${shortHash}-${timeSuffix}`;
34
+ return join(getVibeValidateTempDir(), 'runs', dateFolder, runFolder);
35
+ }
36
+ /**
37
+ * Ensure a directory exists (create if needed)
38
+ */
39
+ export async function ensureDir(dirPath) {
40
+ try {
41
+ await mkdir(dirPath, { recursive: true });
42
+ }
43
+ catch (err) {
44
+ // Ignore if directory already exists
45
+ if (err && typeof err === 'object' && 'code' in err && err.code !== 'EEXIST') {
46
+ throw err;
47
+ }
48
+ }
49
+ }
50
+ /**
51
+ * Get the size of a directory recursively (in bytes)
52
+ */
53
+ async function getDirectorySize(dirPath) {
54
+ let totalSize = 0;
55
+ try {
56
+ const entries = await readdir(dirPath, { withFileTypes: true });
57
+ for (const entry of entries) {
58
+ const fullPath = join(dirPath, entry.name);
59
+ if (entry.isDirectory()) {
60
+ totalSize += await getDirectorySize(fullPath);
61
+ }
62
+ else if (entry.isFile()) {
63
+ const stats = await stat(fullPath);
64
+ totalSize += stats.size;
65
+ }
66
+ }
67
+ }
68
+ catch (err) {
69
+ // Ignore errors (directory might not exist or be inaccessible)
70
+ if (err && typeof err === 'object' && 'code' in err && err.code !== 'ENOENT') {
71
+ // Log unexpected errors but don't throw
72
+ console.warn(`Warning: Could not read directory ${dirPath}:`, err);
73
+ }
74
+ }
75
+ return totalSize;
76
+ }
77
+ /**
78
+ * Count the number of run directories
79
+ */
80
+ async function countRunDirectories(runsDir) {
81
+ let count = 0;
82
+ try {
83
+ const dateDirs = await readdir(runsDir, { withFileTypes: true });
84
+ for (const dateDir of dateDirs) {
85
+ if (dateDir.isDirectory()) {
86
+ const dateDirPath = join(runsDir, dateDir.name);
87
+ const runDirs = await readdir(dateDirPath, { withFileTypes: true });
88
+ count += runDirs.filter(d => d.isDirectory()).length;
89
+ }
90
+ }
91
+ }
92
+ catch (err) {
93
+ // Ignore errors (directory might not exist)
94
+ if (err && typeof err === 'object' && 'code' in err && err.code !== 'ENOENT') {
95
+ console.warn(`Warning: Could not count run directories:`, err);
96
+ }
97
+ }
98
+ return count;
99
+ }
100
+ /**
101
+ * Get temp file storage information
102
+ */
103
+ export async function getTempStorageInfo() {
104
+ const tempDir = getVibeValidateTempDir();
105
+ const runsDir = join(tempDir, 'runs');
106
+ const [sizeBytes, runCount] = await Promise.all([
107
+ getDirectorySize(runsDir),
108
+ countRunDirectories(runsDir),
109
+ ]);
110
+ return {
111
+ sizeBytes,
112
+ runCount,
113
+ path: runsDir,
114
+ };
115
+ }
116
+ /**
117
+ * Format bytes as human-readable size
118
+ */
119
+ export function formatBytes(bytes) {
120
+ if (bytes === 0)
121
+ return '0 B';
122
+ const k = 1024;
123
+ const sizes = ['B', 'KB', 'MB', 'GB'];
124
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
125
+ return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}`;
126
+ }
127
+ /**
128
+ * Clean up old temporary files
129
+ */
130
+ // eslint-disable-next-line sonarjs/cognitive-complexity -- Cleanup logic requires nested loops and error handling for directory traversal
131
+ export async function cleanupOldTempFiles(options = {}) {
132
+ const { olderThanDays = 7, dryRun = false, deleteAll = false, } = options;
133
+ const result = {
134
+ deletedCount: 0,
135
+ freedBytes: 0,
136
+ deletedPaths: [],
137
+ errors: [],
138
+ };
139
+ const tempDir = getVibeValidateTempDir();
140
+ const runsDir = join(tempDir, 'runs');
141
+ try {
142
+ const dateDirs = await readdir(runsDir, { withFileTypes: true });
143
+ const cutoffDate = new Date();
144
+ cutoffDate.setDate(cutoffDate.getDate() - olderThanDays);
145
+ for (const dateDir of dateDirs) {
146
+ if (!dateDir.isDirectory())
147
+ continue;
148
+ const dateDirPath = join(runsDir, dateDir.name);
149
+ try {
150
+ const runDirs = await readdir(dateDirPath, { withFileTypes: true });
151
+ for (const runDir of runDirs) {
152
+ if (!runDir.isDirectory())
153
+ continue;
154
+ const runDirPath = join(dateDirPath, runDir.name);
155
+ try {
156
+ const stats = await stat(runDirPath);
157
+ const shouldDelete = deleteAll || stats.mtime < cutoffDate;
158
+ if (shouldDelete) {
159
+ const dirSize = await getDirectorySize(runDirPath);
160
+ if (!dryRun) {
161
+ await rm(runDirPath, { recursive: true, force: true });
162
+ }
163
+ result.deletedCount++;
164
+ result.freedBytes += dirSize;
165
+ result.deletedPaths.push(runDirPath);
166
+ }
167
+ }
168
+ catch (err) {
169
+ const errorMsg = err instanceof Error ? err.message : String(err);
170
+ result.errors.push({ path: runDirPath, error: errorMsg });
171
+ }
172
+ }
173
+ // Try to remove empty date directories
174
+ if (!dryRun) {
175
+ try {
176
+ const remaining = await readdir(dateDirPath);
177
+ if (remaining.length === 0) {
178
+ await rm(dateDirPath, { recursive: true });
179
+ }
180
+ }
181
+ catch {
182
+ // Ignore errors when cleaning up empty directories
183
+ }
184
+ }
185
+ }
186
+ catch (err) {
187
+ const errorMsg = err instanceof Error ? err.message : String(err);
188
+ result.errors.push({ path: dateDirPath, error: errorMsg });
189
+ }
190
+ }
191
+ }
192
+ catch (err) {
193
+ if (err && typeof err === 'object' && 'code' in err && err.code === 'ENOENT') {
194
+ // Runs directory doesn't exist - nothing to clean up
195
+ return result;
196
+ }
197
+ const errorMsg = err instanceof Error ? err.message : String(err);
198
+ result.errors.push({ path: runsDir, error: errorMsg });
199
+ }
200
+ return result;
201
+ }
202
+ //# sourceMappingURL=temp-files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"temp-files.js","sourceRoot":"","sources":["../../src/utils/temp-files.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"template-discovery.d.ts","sourceRoot":"","sources":["../../src/utils/template-discovery.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,yDAAyD;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,kFAAkF;IAClF,WAAW,EAAE,MAAM,CAAC;IACpB,mGAAmG;IACnG,WAAW,EAAE,MAAM,CAAC;CACrB;AA6ED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,IAAI,gBAAgB,EAAE,CAsBtD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,EAAE,CAa7C"}
1
+ {"version":3,"file":"template-discovery.d.ts","sourceRoot":"","sources":["../../src/utils/template-discovery.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,yDAAyD;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,kFAAkF;IAClF,WAAW,EAAE,MAAM,CAAC;IACpB,mGAAmG;IACnG,WAAW,EAAE,MAAM,CAAC;CACrB;AA6ED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,IAAI,gBAAgB,EAAE,CAuBtD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,EAAE,CAa7C"}
@@ -3,9 +3,9 @@
3
3
  *
4
4
  * Discovers and reads metadata from config templates in the config-templates/ directory.
5
5
  */
6
- import { readdirSync, readFileSync, existsSync } from 'fs';
7
- import { join, dirname } from 'path';
8
- import { fileURLToPath } from 'url';
6
+ import { readdirSync, readFileSync, existsSync } from 'node:fs';
7
+ import { join, dirname } from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
9
  import { splitLines } from './normalize-line-endings.js';
10
10
  /**
11
11
  * Get the absolute path to the config-templates directory
@@ -51,7 +51,7 @@ function parseTemplateMetadata(filename, content) {
51
51
  let displayName = filename.replace('.yaml', '');
52
52
  const titleLine = lines.find(line => line.includes('CONFIGURATION TEMPLATE -'));
53
53
  if (titleLine) {
54
- const match = titleLine.match(/CONFIGURATION TEMPLATE\s*-\s*(.+)/);
54
+ const match = /CONFIGURATION TEMPLATE\s*-\s*(.+)/.exec(titleLine);
55
55
  if (match) {
56
56
  displayName = match[1].trim();
57
57
  // Remove "vibe-validate for " prefix if present
@@ -91,6 +91,7 @@ export function discoverTemplates() {
91
91
  // Read all .yaml files
92
92
  const files = readdirSync(templatesDir)
93
93
  .filter(file => file.endsWith('.yaml'))
94
+ // eslint-disable-next-line sonarjs/no-alphabetical-sort -- Alphabetical sorting is intentional for template list display
94
95
  .sort();
95
96
  // Parse metadata from each template
96
97
  const templates = [];
@@ -1 +1 @@
1
- {"version":3,"file":"template-discovery.js","sourceRoot":"","sources":["../../src/utils/template-discovery.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"template-discovery.js","sourceRoot":"","sources":["../../src/utils/template-discovery.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"validate-workflow.d.ts","sourceRoot":"","sources":["../../src/utils/validate-workflow.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAS5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAK1D,MAAM,WAAW,uBAAuB;IACtC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,YAAY,CAAC;CACvB;AAED;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,kBAAkB,EAC1B,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC,gBAAgB,CAAC,CAmP3B"}
1
+ {"version":3,"file":"validate-workflow.d.ts","sourceRoot":"","sources":["../../src/utils/validate-workflow.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAS5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAK1D,MAAM,WAAW,uBAAuB;IACtC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,YAAY,CAAC;CACvB;AAmLD;;;;;;;;;GASG;AAEH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,kBAAkB,EAC1B,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC,gBAAgB,CAAC,CAgG3B"}
@@ -10,6 +10,162 @@ import { recordValidationHistory, checkWorktreeStability, checkHistoryHealth, re
10
10
  import { createRunnerConfig } from './runner-adapter.js';
11
11
  import chalk from 'chalk';
12
12
  import { stringify as yamlStringify } from 'yaml';
13
+ /**
14
+ * Wait for stdout to flush before continuing
15
+ *
16
+ * Critical for YAML output when stdout is redirected to a file (CI).
17
+ * Without this, process.exit() can kill the process before the write buffer flushes.
18
+ *
19
+ * @internal
20
+ */
21
+ async function flushStdout() {
22
+ await new Promise(resolve => {
23
+ if (process.stdout.write('')) {
24
+ resolve();
25
+ }
26
+ else {
27
+ process.stdout.once('drain', resolve);
28
+ }
29
+ });
30
+ }
31
+ /**
32
+ * Output validation result as YAML
33
+ *
34
+ * @param result - Validation result to output
35
+ * @internal
36
+ */
37
+ async function outputYaml(result) {
38
+ // Small delay to ensure stderr is flushed before writing to stdout
39
+ await new Promise(resolve => setTimeout(resolve, 10));
40
+ // Output YAML document separator (RFC 4627)
41
+ process.stdout.write('---\n');
42
+ process.stdout.write(yamlStringify(result));
43
+ await flushStdout();
44
+ }
45
+ /**
46
+ * Check cache for passing validation run
47
+ *
48
+ * @param treeHash - Git tree hash to check
49
+ * @param yaml - Whether YAML output mode is enabled
50
+ * @returns Cached result if found, null otherwise
51
+ * @internal
52
+ */
53
+ async function checkCache(treeHash, yaml) {
54
+ try {
55
+ const historyNote = await readHistoryNote(treeHash);
56
+ if (historyNote && historyNote.runs.length > 0) {
57
+ // Find most recent passing run
58
+ const passingRun = [...historyNote.runs]
59
+ .reverse()
60
+ .find(run => run.passed);
61
+ if (passingRun) {
62
+ // Mark result as from cache (v0.15.0+ schema field)
63
+ const result = passingRun.result;
64
+ result.isCachedResult = true;
65
+ if (yaml) {
66
+ await outputYaml(result);
67
+ }
68
+ else {
69
+ const durationSecs = (passingRun.duration / 1000).toFixed(1);
70
+ console.log(chalk.green('āœ… Validation already passed for current working tree'));
71
+ console.log(chalk.gray(` Tree hash: ${treeHash.substring(0, 12)}...`));
72
+ console.log(chalk.gray(` Last validated: ${passingRun.timestamp}`));
73
+ console.log(chalk.gray(` Duration: ${durationSecs}s`));
74
+ console.log(chalk.gray(` Branch: ${passingRun.branch}`));
75
+ if (passingRun.result.phases) {
76
+ const totalSteps = passingRun.result.phases.reduce((sum, phase) => sum + phase.steps.length, 0);
77
+ console.log(chalk.gray(` Phases: ${passingRun.result.phases.length}, Steps: ${totalSteps}`));
78
+ }
79
+ }
80
+ return result;
81
+ }
82
+ }
83
+ }
84
+ catch {
85
+ // Cache check failed - proceed with validation
86
+ }
87
+ return null;
88
+ }
89
+ /**
90
+ * Record validation history with stability check
91
+ *
92
+ * @param treeHashBefore - Tree hash before validation
93
+ * @param result - Validation result to record
94
+ * @param verbose - Whether verbose output is enabled
95
+ * @internal
96
+ */
97
+ async function recordHistory(treeHashBefore, result, verbose) {
98
+ try {
99
+ // Check if worktree changed during validation
100
+ const stability = await checkWorktreeStability(treeHashBefore);
101
+ if (!stability.stable) {
102
+ console.warn(chalk.yellow('\nāš ļø Worktree changed during validation'));
103
+ console.warn(chalk.yellow(` Before: ${stability.treeHashBefore.slice(0, 12)}...`));
104
+ console.warn(chalk.yellow(` After: ${stability.treeHashAfter.slice(0, 12)}...`));
105
+ console.warn(chalk.yellow(' Results valid but history not recorded (unstable state)'));
106
+ }
107
+ else {
108
+ // Record to git notes
109
+ const recordResult = await recordValidationHistory(treeHashBefore, result);
110
+ if (recordResult.recorded) {
111
+ if (verbose) {
112
+ console.log(chalk.gray(`\nšŸ“ History recorded (tree: ${treeHashBefore.slice(0, 12)})`));
113
+ }
114
+ }
115
+ else if (verbose) {
116
+ console.warn(chalk.yellow(`āš ļø History recording failed: ${recordResult.reason}`));
117
+ }
118
+ }
119
+ }
120
+ catch (error) {
121
+ // Silent failure - don't block validation
122
+ if (verbose) {
123
+ const errorMessage = error instanceof Error ? error.message : String(error);
124
+ console.warn(chalk.yellow(`āš ļø History recording error: ${errorMessage}`));
125
+ }
126
+ }
127
+ }
128
+ /**
129
+ * Display failure information and extraction quality feedback
130
+ *
131
+ * @param result - Validation result (failed)
132
+ * @param config - Vibe validate configuration
133
+ * @internal
134
+ */
135
+ function displayFailureInfo(result, config) {
136
+ console.error(chalk.blue('\nšŸ“‹ View error details:'), chalk.white('vibe-validate state'));
137
+ // Find the failed step's command (v0.15.0+: rerunCommand removed, use step.command)
138
+ const failedStep = result.phases
139
+ ?.flatMap(phase => phase.steps)
140
+ .find(step => step.name === result.failedStep);
141
+ if (failedStep?.command) {
142
+ console.error(chalk.blue('šŸ”„ To retry:'), chalk.white(failedStep.command));
143
+ }
144
+ if (result.fullLogFile) {
145
+ console.error(chalk.blue('šŸ“„ Full log:'), chalk.gray(result.fullLogFile));
146
+ }
147
+ // Context-aware extraction quality feedback (only when developerFeedback is enabled)
148
+ if (config.developerFeedback) {
149
+ const poorExtractionSteps = result.phases
150
+ ?.flatMap(phase => phase.steps)
151
+ .filter(step => !step.passed && step.extraction?.metadata && step.extraction.metadata.confidence < 50);
152
+ if (poorExtractionSteps && poorExtractionSteps.length > 0) {
153
+ // VV_CONTEXT is set by the smart wrapper (vibe-validate/vv)
154
+ // 'dev' = developer mode (working on vibe-validate itself)
155
+ const isDevMode = process.env.VV_CONTEXT === 'dev';
156
+ console.error('');
157
+ console.error(chalk.yellow('āš ļø Poor extraction quality detected'));
158
+ if (isDevMode) {
159
+ console.error(chalk.yellow(' šŸ’” Developer mode: Improve extractors in packages/extractors/'));
160
+ console.error(chalk.gray(' See packages/extractors/test/samples/ for how to add test cases'));
161
+ }
162
+ else {
163
+ console.error(chalk.yellow(' šŸ’” Help improve vibe-validate by reporting this extraction issue'));
164
+ console.error(chalk.gray(' https://github.com/anthropics/vibe-validate/issues/new?template=extractor-improvement.yml'));
165
+ }
166
+ }
167
+ }
168
+ }
13
169
  /**
14
170
  * Execute validation workflow with caching, history recording, and output formatting.
15
171
  *
@@ -20,6 +176,7 @@ import { stringify as yamlStringify } from 'yaml';
20
176
  * @returns Validation result
21
177
  * @throws Error if validation encounters fatal error
22
178
  */
179
+ // eslint-disable-next-line sonarjs/cognitive-complexity -- Complexity 27 acceptable for workflow orchestration (down from 81) - coordinates caching, validation, history recording, and output formatting
23
180
  export async function runValidateWorkflow(config, options) {
24
181
  try {
25
182
  // If --check flag is used, only check validation state without running
@@ -27,8 +184,6 @@ export async function runValidateWorkflow(config, options) {
27
184
  const yaml = options.yaml ?? false;
28
185
  const { checkValidationStatus } = await import('./check-validation.js');
29
186
  await checkValidationStatus(config, yaml);
30
- // checkValidationStatus calls process.exit, so this line never executes
31
- // But TypeScript doesn't know that, so we need to satisfy the return type
32
187
  throw new Error('checkValidationStatus should have exited');
33
188
  }
34
189
  const verbose = options.verbose ?? false;
@@ -45,68 +200,20 @@ export async function runValidateWorkflow(config, options) {
45
200
  try {
46
201
  treeHashBefore = await getGitTreeHash();
47
202
  }
48
- catch (_error) {
49
- // Not in git repo or git command failed - continue without history
203
+ catch (error) {
204
+ const errorMsg = error instanceof Error ? error.message : String(error);
50
205
  if (verbose) {
51
- console.warn(chalk.yellow('āš ļø Could not get git tree hash - history recording disabled'));
206
+ console.warn(chalk.yellow(`āš ļø Could not get git tree hash - history recording disabled: ${errorMsg}`));
52
207
  }
53
208
  }
54
209
  // Check cache: if validation already passed for this tree hash, skip re-running
55
210
  if (treeHashBefore && !options.force) {
56
- try {
57
- const historyNote = await readHistoryNote(treeHashBefore);
58
- if (historyNote && historyNote.runs.length > 0) {
59
- // Find most recent passing run
60
- const passingRun = [...historyNote.runs]
61
- .reverse()
62
- .find(run => run.passed);
63
- if (passingRun) {
64
- if (yaml) {
65
- // YAML mode: Output cached result as YAML to stdout
66
- await new Promise(resolve => setTimeout(resolve, 10));
67
- // Output YAML document separator (RFC 4627)
68
- process.stdout.write('---\n');
69
- process.stdout.write(yamlStringify(passingRun.result));
70
- // Wait for stdout to flush before exiting
71
- await new Promise(resolve => {
72
- if (process.stdout.write('')) {
73
- resolve();
74
- }
75
- else {
76
- process.stdout.once('drain', resolve);
77
- }
78
- });
79
- }
80
- else {
81
- // Human-readable mode: Display cache hit message
82
- const durationSecs = (passingRun.duration / 1000).toFixed(1);
83
- console.log(chalk.green('āœ… Validation already passed for current working tree'));
84
- console.log(chalk.gray(` Tree hash: ${treeHashBefore.substring(0, 12)}...`));
85
- console.log(chalk.gray(` Last validated: ${passingRun.timestamp}`));
86
- console.log(chalk.gray(` Duration: ${durationSecs}s`));
87
- console.log(chalk.gray(` Branch: ${passingRun.branch}`));
88
- if (passingRun.result?.phases) {
89
- const totalSteps = passingRun.result.phases.reduce((sum, phase) => sum + (phase.steps?.length || 0), 0);
90
- console.log(chalk.gray(` Phases: ${passingRun.result.phases.length}, Steps: ${totalSteps}`));
91
- }
92
- }
93
- // Cache hit - return early without calling process.exit
94
- // This allows tests to complete without throwing process.exit errors
95
- // In production, Commander will exit with code 0
96
- // Mark result as from cache so caller knows not to call process.exit
97
- const result = passingRun.result;
98
- result._fromCache = true;
99
- return result;
100
- }
101
- }
102
- }
103
- catch (_error) {
104
- // Cache check failed - proceed with validation
105
- // This is expected for first-time validation
211
+ const cachedResult = await checkCache(treeHashBefore, yaml);
212
+ if (cachedResult) {
213
+ return cachedResult;
106
214
  }
107
215
  }
108
- // Display tree hash before running validation (debugging/transparency aid)
109
- // This goes to stderr, so it's visible even in YAML mode
216
+ // Display tree hash before running validation
110
217
  if (treeHashBefore) {
111
218
  console.error(chalk.gray(`🌳 Working tree: ${treeHashBefore.slice(0, 12)}...`));
112
219
  if (!yaml) {
@@ -115,37 +222,9 @@ export async function runValidateWorkflow(config, options) {
115
222
  }
116
223
  // Run validation
117
224
  const result = await runValidation(runnerConfig);
118
- // Record validation history (if in git repo and stability check passes)
225
+ // Record validation history (if in git repo)
119
226
  if (treeHashBefore) {
120
- try {
121
- // Check if worktree changed during validation
122
- const stability = await checkWorktreeStability(treeHashBefore);
123
- if (!stability.stable) {
124
- console.warn(chalk.yellow('\nāš ļø Worktree changed during validation'));
125
- console.warn(chalk.yellow(` Before: ${stability.treeHashBefore.slice(0, 12)}...`));
126
- console.warn(chalk.yellow(` After: ${stability.treeHashAfter.slice(0, 12)}...`));
127
- console.warn(chalk.yellow(' Results valid but history not recorded (unstable state)'));
128
- }
129
- else {
130
- // Record to git notes
131
- const recordResult = await recordValidationHistory(treeHashBefore, result);
132
- if (recordResult.recorded) {
133
- if (verbose) {
134
- console.log(chalk.gray(`\nšŸ“ History recorded (tree: ${treeHashBefore.slice(0, 12)})`));
135
- }
136
- }
137
- else if (verbose) {
138
- console.warn(chalk.yellow(`āš ļø History recording failed: ${recordResult.reason}`));
139
- }
140
- }
141
- }
142
- catch (error) {
143
- // Silent failure - don't block validation
144
- if (verbose) {
145
- const errorMessage = error instanceof Error ? error.message : String(error);
146
- console.warn(chalk.yellow(`āš ļø History recording error: ${errorMessage}`));
147
- }
148
- }
227
+ await recordHistory(treeHashBefore, result, verbose);
149
228
  }
150
229
  // Proactive health check (non-blocking)
151
230
  try {
@@ -160,58 +239,11 @@ export async function runValidateWorkflow(config, options) {
160
239
  }
161
240
  // If validation failed, show agent-friendly error details
162
241
  if (!result.passed) {
163
- console.error(chalk.blue('\nšŸ“‹ View error details:'), chalk.white('vibe-validate state'));
164
- if (result.rerunCommand) {
165
- console.error(chalk.blue('šŸ”„ To retry:'), chalk.white(result.rerunCommand));
166
- }
167
- if (result.fullLogFile) {
168
- console.error(chalk.blue('šŸ“„ Full log:'), chalk.gray(result.fullLogFile));
169
- }
170
- // Context-aware extraction quality feedback (only when developerFeedback is enabled)
171
- if (config.developerFeedback) {
172
- // Check if any steps had poor extraction quality
173
- const poorExtractionSteps = result.phases
174
- ?.flatMap(phase => phase.steps || [])
175
- .filter(step => !step.passed && step.extractionQuality && step.extractionQuality.score < 50);
176
- if (poorExtractionSteps && poorExtractionSteps.length > 0) {
177
- // Detect if we're dogfooding (in the vibe-validate project itself)
178
- const isDogfooding = process.cwd().includes('vibe-validate');
179
- console.error('');
180
- console.error(chalk.yellow('āš ļø Poor extraction quality detected'));
181
- if (isDogfooding) {
182
- // Developing vibe-validate itself: direct contributor call-to-action
183
- console.error(chalk.yellow(' šŸ’” vibe-validate improvement opportunity: Improve extractors in packages/extractors/'));
184
- console.error(chalk.gray(' See packages/extractors/test/samples/ for how to add test cases'));
185
- }
186
- else {
187
- // External project: user feedback to improve vibe-validate
188
- console.error(chalk.yellow(' šŸ’” Help improve vibe-validate by reporting this extraction issue'));
189
- console.error(chalk.gray(' https://github.com/anthropics/vibe-validate/issues/new?template=extractor-improvement.yml'));
190
- }
191
- }
192
- }
242
+ displayFailureInfo(result, config);
193
243
  }
194
244
  // Output YAML validation result if --yaml flag is set
195
245
  if (yaml) {
196
- // Small delay to ensure stderr is flushed before writing to stdout
197
- await new Promise(resolve => setTimeout(resolve, 10));
198
- // Output YAML document separator (RFC 4627) to mark transition from stderr to stdout
199
- process.stdout.write('---\n');
200
- // Output pure YAML without headers (workflow provides display framing)
201
- process.stdout.write(yamlStringify(result));
202
- // CRITICAL: Wait for stdout to flush before exiting
203
- // When stdout is redirected to a file (CI), process.exit() can kill the process
204
- // before the write buffer is flushed, causing truncated output
205
- await new Promise(resolve => {
206
- if (process.stdout.write('')) {
207
- // Write buffer is empty, can exit immediately
208
- resolve();
209
- }
210
- else {
211
- // Wait for drain event
212
- process.stdout.once('drain', resolve);
213
- }
214
- });
246
+ await outputYaml(result);
215
247
  }
216
248
  return result;
217
249
  }
@@ -225,20 +257,7 @@ export async function runValidateWorkflow(config, options) {
225
257
  error: error instanceof Error ? error.message : String(error),
226
258
  errorStack: error instanceof Error ? error.stack : undefined,
227
259
  };
228
- // Small delay to ensure stderr is flushed before writing to stdout
229
- await new Promise(resolve => setTimeout(resolve, 10));
230
- // Output YAML document separator
231
- process.stdout.write('---\n');
232
- process.stdout.write(yamlStringify(errorResult));
233
- // Wait for stdout to flush before exiting
234
- await new Promise(resolve => {
235
- if (process.stdout.write('')) {
236
- resolve();
237
- }
238
- else {
239
- process.stdout.once('drain', resolve);
240
- }
241
- });
260
+ await outputYaml(errorResult);
242
261
  }
243
262
  // Re-throw to allow caller to handle exit
244
263
  throw error;