@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.
- package/README.md +26 -6
- package/bin/vibe-validate +131 -0
- package/config-templates/minimal.yaml +1 -1
- package/config-templates/typescript-library.yaml +1 -1
- package/config-templates/typescript-nodejs.yaml +1 -1
- package/config-templates/typescript-react.yaml +1 -1
- package/dist/bin.js +41 -21
- package/dist/bin.js.map +1 -1
- package/dist/commands/cleanup.d.ts +1 -1
- package/dist/commands/cleanup.d.ts.map +1 -1
- package/dist/commands/cleanup.js +135 -16
- package/dist/commands/cleanup.js.map +1 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +13 -10
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +253 -189
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/generate-workflow.d.ts.map +1 -1
- package/dist/commands/generate-workflow.js +15 -13
- package/dist/commands/generate-workflow.js.map +1 -1
- package/dist/commands/history.d.ts.map +1 -1
- package/dist/commands/history.js +305 -98
- package/dist/commands/history.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +14 -6
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/pre-commit.d.ts.map +1 -1
- package/dist/commands/pre-commit.js +8 -3
- package/dist/commands/pre-commit.js.map +1 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +620 -217
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/state.d.ts.map +1 -1
- package/dist/commands/state.js +4 -7
- package/dist/commands/state.js.map +1 -1
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +7 -7
- package/dist/commands/validate.js.map +1 -1
- package/dist/commands/watch-pr.d.ts.map +1 -1
- package/dist/commands/watch-pr.js +73 -49
- package/dist/commands/watch-pr.js.map +1 -1
- package/dist/schemas/run-result-schema-export.d.ts +32 -0
- package/dist/schemas/run-result-schema-export.d.ts.map +1 -0
- package/dist/schemas/run-result-schema-export.js +40 -0
- package/dist/schemas/run-result-schema-export.js.map +1 -0
- package/dist/schemas/run-result-schema.d.ts +850 -0
- package/dist/schemas/run-result-schema.d.ts.map +1 -0
- package/dist/schemas/run-result-schema.js +67 -0
- package/dist/schemas/run-result-schema.js.map +1 -0
- package/dist/schemas/watch-pr-schema.d.ts +431 -33
- package/dist/schemas/watch-pr-schema.d.ts.map +1 -1
- package/dist/schemas/watch-pr-schema.js +2 -2
- package/dist/schemas/watch-pr-schema.js.map +1 -1
- package/dist/scripts/generate-run-result-schema.d.ts +10 -0
- package/dist/scripts/generate-run-result-schema.d.ts.map +1 -0
- package/dist/scripts/generate-run-result-schema.js +20 -0
- package/dist/scripts/generate-run-result-schema.js.map +1 -0
- package/dist/scripts/generate-watch-pr-schema.js +3 -3
- package/dist/scripts/generate-watch-pr-schema.js.map +1 -1
- package/dist/services/ci-provider.d.ts +21 -6
- package/dist/services/ci-provider.d.ts.map +1 -1
- package/dist/services/ci-providers/github-actions.d.ts +21 -0
- package/dist/services/ci-providers/github-actions.d.ts.map +1 -1
- package/dist/services/ci-providers/github-actions.js +65 -49
- package/dist/services/ci-providers/github-actions.js.map +1 -1
- package/dist/utils/check-validation.d.ts.map +1 -1
- package/dist/utils/check-validation.js +9 -5
- package/dist/utils/check-validation.js.map +1 -1
- package/dist/utils/config-error-reporter.js +9 -7
- package/dist/utils/config-error-reporter.js.map +1 -1
- package/dist/utils/config-loader.js +5 -5
- package/dist/utils/config-loader.js.map +1 -1
- package/dist/utils/git-detection.d.ts +0 -22
- package/dist/utils/git-detection.d.ts.map +1 -1
- package/dist/utils/git-detection.js +64 -56
- package/dist/utils/git-detection.js.map +1 -1
- package/dist/utils/pid-lock.js +7 -7
- package/dist/utils/pid-lock.js.map +1 -1
- package/dist/utils/project-id.d.ts.map +1 -1
- package/dist/utils/project-id.js +8 -6
- package/dist/utils/project-id.js.map +1 -1
- package/dist/utils/runner-adapter.js +4 -3
- package/dist/utils/runner-adapter.js.map +1 -1
- package/dist/utils/setup-checks/hooks-check.js +3 -3
- package/dist/utils/setup-checks/hooks-check.js.map +1 -1
- package/dist/utils/setup-checks/workflow-check.js +3 -3
- package/dist/utils/setup-checks/workflow-check.js.map +1 -1
- package/dist/utils/temp-files.d.ts +67 -0
- package/dist/utils/temp-files.d.ts.map +1 -0
- package/dist/utils/temp-files.js +202 -0
- package/dist/utils/temp-files.js.map +1 -0
- package/dist/utils/template-discovery.d.ts.map +1 -1
- package/dist/utils/template-discovery.js +5 -4
- package/dist/utils/template-discovery.js.map +1 -1
- package/dist/utils/validate-workflow.d.ts.map +1 -1
- package/dist/utils/validate-workflow.js +169 -150
- package/dist/utils/validate-workflow.js.map +1 -1
- package/dist/vibe-validate +131 -0
- package/package.json +11 -9
- package/run-result.schema.json +186 -0
- 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,
|
|
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 =
|
|
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":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,
|
|
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;
|
|
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 (
|
|
49
|
-
|
|
203
|
+
catch (error) {
|
|
204
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
50
205
|
if (verbose) {
|
|
51
|
-
console.warn(chalk.yellow(
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
|
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
|
|
225
|
+
// Record validation history (if in git repo)
|
|
119
226
|
if (treeHashBefore) {
|
|
120
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|