@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.
- package/README.md +84 -92
- package/dist/bin.js +137 -20
- package/dist/bin.js.map +1 -1
- package/dist/commands/cleanup.d.ts +4 -0
- package/dist/commands/cleanup.d.ts.map +1 -1
- package/dist/commands/cleanup.js +96 -15
- package/dist/commands/cleanup.js.map +1 -1
- package/dist/commands/config.d.ts +4 -0
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +83 -15
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/doctor.d.ts +4 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +385 -82
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/generate-workflow.d.ts +6 -2
- package/dist/commands/generate-workflow.d.ts.map +1 -1
- package/dist/commands/generate-workflow.js +188 -33
- package/dist/commands/generate-workflow.js.map +1 -1
- package/dist/commands/history.d.ts +13 -0
- package/dist/commands/history.d.ts.map +1 -0
- package/dist/commands/history.js +415 -0
- package/dist/commands/history.js.map +1 -0
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +252 -109
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/pre-commit.d.ts +4 -0
- package/dist/commands/pre-commit.d.ts.map +1 -1
- package/dist/commands/pre-commit.js +158 -7
- package/dist/commands/pre-commit.js.map +1 -1
- package/dist/commands/state.d.ts +5 -1
- package/dist/commands/state.d.ts.map +1 -1
- package/dist/commands/state.js +192 -23
- package/dist/commands/state.js.map +1 -1
- package/dist/commands/sync-check.d.ts +4 -0
- package/dist/commands/sync-check.d.ts.map +1 -1
- package/dist/commands/sync-check.js +101 -14
- package/dist/commands/sync-check.js.map +1 -1
- package/dist/commands/validate.d.ts +5 -1
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +184 -28
- package/dist/commands/validate.js.map +1 -1
- package/dist/commands/watch-pr.d.ts +10 -0
- package/dist/commands/watch-pr.d.ts.map +1 -0
- package/dist/commands/watch-pr.js +443 -0
- package/dist/commands/watch-pr.js.map +1 -0
- package/dist/schemas/watch-pr-schema.d.ts +261 -0
- package/dist/schemas/watch-pr-schema.d.ts.map +1 -0
- package/dist/schemas/watch-pr-schema.js +58 -0
- package/dist/schemas/watch-pr-schema.js.map +1 -0
- package/dist/scripts/generate-watch-pr-schema.d.ts +12 -0
- package/dist/scripts/generate-watch-pr-schema.d.ts.map +1 -0
- package/dist/scripts/generate-watch-pr-schema.js +35 -0
- package/dist/scripts/generate-watch-pr-schema.js.map +1 -0
- package/dist/services/ci-provider-registry.d.ts +38 -0
- package/dist/services/ci-provider-registry.d.ts.map +1 -0
- package/dist/services/ci-provider-registry.js +53 -0
- package/dist/services/ci-provider-registry.js.map +1 -0
- package/dist/services/ci-provider.d.ts +165 -0
- package/dist/services/ci-provider.d.ts.map +1 -0
- package/dist/services/ci-provider.js +11 -0
- package/dist/services/ci-provider.js.map +1 -0
- package/dist/services/ci-providers/github-actions.d.ts +41 -0
- package/dist/services/ci-providers/github-actions.d.ts.map +1 -0
- package/dist/services/ci-providers/github-actions.js +314 -0
- package/dist/services/ci-providers/github-actions.js.map +1 -0
- package/dist/utils/check-validation.d.ts +7 -4
- package/dist/utils/check-validation.d.ts.map +1 -1
- package/dist/utils/check-validation.js +129 -48
- package/dist/utils/check-validation.js.map +1 -1
- package/dist/utils/config-loader.d.ts +15 -3
- package/dist/utils/config-loader.d.ts.map +1 -1
- package/dist/utils/config-loader.js +61 -17
- package/dist/utils/config-loader.js.map +1 -1
- package/dist/utils/context-detector.d.ts +1 -1
- package/dist/utils/context-detector.js +1 -1
- package/dist/utils/normalize-line-endings.d.ts +53 -0
- package/dist/utils/normalize-line-endings.d.ts.map +1 -0
- package/dist/utils/normalize-line-endings.js +57 -0
- package/dist/utils/normalize-line-endings.js.map +1 -0
- package/dist/utils/run-validation-with-cache.d.ts +48 -0
- package/dist/utils/run-validation-with-cache.d.ts.map +1 -0
- package/dist/utils/run-validation-with-cache.js +123 -0
- package/dist/utils/run-validation-with-cache.js.map +1 -0
- package/dist/utils/runner-adapter.d.ts +1 -0
- package/dist/utils/runner-adapter.d.ts.map +1 -1
- package/dist/utils/runner-adapter.js +25 -17
- package/dist/utils/runner-adapter.js.map +1 -1
- package/dist/utils/setup-checks/gitignore-check.d.ts +10 -15
- package/dist/utils/setup-checks/gitignore-check.d.ts.map +1 -1
- package/dist/utils/setup-checks/gitignore-check.js +20 -138
- package/dist/utils/setup-checks/gitignore-check.js.map +1 -1
- package/dist/utils/template-discovery.d.ts +40 -0
- package/dist/utils/template-discovery.d.ts.map +1 -0
- package/dist/utils/template-discovery.js +136 -0
- package/dist/utils/template-discovery.js.map +1 -0
- package/dist/utils/validate-workflow.d.ts +28 -0
- package/dist/utils/validate-workflow.d.ts.map +1 -0
- package/dist/utils/validate-workflow.js +247 -0
- package/dist/utils/validate-workflow.js.map +1 -0
- package/dist/utils/validation-cache.d.ts +30 -0
- package/dist/utils/validation-cache.d.ts.map +1 -0
- package/dist/utils/validation-cache.js +57 -0
- package/dist/utils/validation-cache.js.map +1 -0
- package/package.json +19 -16
- package/watch-pr-result.schema.json +204 -0
- package/LICENSE +0 -21
|
@@ -1,156 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Gitignore Setup Check
|
|
2
|
+
* Gitignore Setup Check (DEPRECATED)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* @deprecated Since v0.12.0 - State file (.vibe-validate-state.yaml) is deprecated.
|
|
5
|
+
* Validation history is now stored in git notes instead of a state file.
|
|
6
|
+
* This check always passes and does not modify .gitignore.
|
|
7
|
+
*
|
|
8
|
+
* Use `vibe-validate doctor` to detect and remove deprecated state file entries.
|
|
6
9
|
*/
|
|
7
|
-
import { readFile, writeFile } from 'fs/promises';
|
|
8
|
-
import { existsSync } from 'fs';
|
|
9
|
-
import { join } from 'path';
|
|
10
|
-
const STATE_FILE_ENTRY = '.vibe-validate-state.yaml';
|
|
11
10
|
export class GitignoreSetupCheck {
|
|
12
11
|
id = 'gitignore';
|
|
13
|
-
name = 'Gitignore Setup';
|
|
14
|
-
async check(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
// Check if .gitignore exists
|
|
18
|
-
if (!existsSync(gitignorePath)) {
|
|
19
|
-
return {
|
|
20
|
-
passed: false,
|
|
21
|
-
message: '.gitignore not found',
|
|
22
|
-
suggestion: `Create .gitignore and add ${STATE_FILE_ENTRY}`,
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
// Read .gitignore content
|
|
26
|
-
const content = await readFile(gitignorePath, 'utf-8');
|
|
27
|
-
// Check if state file entry exists (with flexible whitespace)
|
|
28
|
-
const hasEntry = content
|
|
29
|
-
.split('\n')
|
|
30
|
-
.some(line => line.trim() === STATE_FILE_ENTRY);
|
|
31
|
-
if (!hasEntry) {
|
|
32
|
-
return {
|
|
33
|
-
passed: false,
|
|
34
|
-
message: `.gitignore missing ${STATE_FILE_ENTRY} entry`,
|
|
35
|
-
suggestion: `Add ${STATE_FILE_ENTRY} to .gitignore`,
|
|
36
|
-
};
|
|
37
|
-
}
|
|
12
|
+
name = 'Gitignore Setup (deprecated)';
|
|
13
|
+
async check(_options) {
|
|
14
|
+
// DEPRECATED: State file is no longer used (git notes replaced it in v0.12.0)
|
|
15
|
+
// Always return passed - no .gitignore modifications needed
|
|
38
16
|
return {
|
|
39
17
|
passed: true,
|
|
40
|
-
message: '.gitignore
|
|
18
|
+
message: '.gitignore check skipped (state file deprecated in v0.12.0)',
|
|
41
19
|
};
|
|
42
20
|
}
|
|
43
|
-
async preview(
|
|
44
|
-
|
|
45
|
-
const gitignorePath = join(cwd, '.gitignore');
|
|
46
|
-
// Check current state
|
|
47
|
-
const checkResult = await this.check(options);
|
|
48
|
-
if (checkResult.passed) {
|
|
49
|
-
return {
|
|
50
|
-
description: '.gitignore already configured correctly',
|
|
51
|
-
filesAffected: [],
|
|
52
|
-
changes: [],
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
// If .gitignore doesn't exist
|
|
56
|
-
if (!existsSync(gitignorePath)) {
|
|
57
|
-
const content = this.generateNewGitignoreContent();
|
|
58
|
-
return {
|
|
59
|
-
description: 'Create new .gitignore with vibe-validate state file entry',
|
|
60
|
-
filesAffected: ['.gitignore'],
|
|
61
|
-
changes: [
|
|
62
|
-
{
|
|
63
|
-
file: '.gitignore',
|
|
64
|
-
action: 'create',
|
|
65
|
-
content,
|
|
66
|
-
},
|
|
67
|
-
],
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
// If .gitignore exists but missing entry
|
|
21
|
+
async preview(_options) {
|
|
22
|
+
// DEPRECATED: No .gitignore modifications needed (state file deprecated)
|
|
71
23
|
return {
|
|
72
|
-
description:
|
|
73
|
-
filesAffected: [
|
|
74
|
-
changes: [
|
|
75
|
-
{
|
|
76
|
-
file: '.gitignore',
|
|
77
|
-
action: 'modify',
|
|
78
|
-
},
|
|
79
|
-
],
|
|
24
|
+
description: 'Gitignore check deprecated (state file no longer used)',
|
|
25
|
+
filesAffected: [],
|
|
26
|
+
changes: [],
|
|
80
27
|
};
|
|
81
28
|
}
|
|
82
|
-
async fix(
|
|
83
|
-
|
|
84
|
-
const gitignorePath = join(cwd, '.gitignore');
|
|
85
|
-
const dryRun = options?.dryRun ?? false;
|
|
86
|
-
// Check current state
|
|
87
|
-
const checkResult = await this.check(options);
|
|
88
|
-
if (checkResult.passed && !options?.force) {
|
|
89
|
-
return {
|
|
90
|
-
success: true,
|
|
91
|
-
message: '.gitignore already configured correctly',
|
|
92
|
-
filesChanged: [],
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
if (dryRun) {
|
|
96
|
-
return {
|
|
97
|
-
success: true,
|
|
98
|
-
message: '[dry-run] Would update .gitignore',
|
|
99
|
-
filesChanged: [],
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
// If .gitignore doesn't exist, create it
|
|
103
|
-
if (!existsSync(gitignorePath)) {
|
|
104
|
-
const content = this.generateNewGitignoreContent();
|
|
105
|
-
await writeFile(gitignorePath, content, 'utf-8');
|
|
106
|
-
return {
|
|
107
|
-
success: true,
|
|
108
|
-
message: 'Created .gitignore with state file entry',
|
|
109
|
-
filesChanged: ['.gitignore'],
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
// If .gitignore exists, add entry if missing
|
|
113
|
-
const content = await readFile(gitignorePath, 'utf-8');
|
|
114
|
-
const hasEntry = content
|
|
115
|
-
.split('\n')
|
|
116
|
-
.some(line => line.trim() === STATE_FILE_ENTRY);
|
|
117
|
-
if (hasEntry) {
|
|
118
|
-
// Entry already exists (idempotent)
|
|
119
|
-
return {
|
|
120
|
-
success: true,
|
|
121
|
-
message: '.gitignore already contains state file entry',
|
|
122
|
-
filesChanged: [],
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
// Add entry to existing .gitignore
|
|
126
|
-
const updatedContent = this.addEntryToGitignore(content);
|
|
127
|
-
await writeFile(gitignorePath, updatedContent, 'utf-8');
|
|
29
|
+
async fix(_options) {
|
|
30
|
+
// DEPRECATED: No .gitignore modifications needed (state file deprecated)
|
|
128
31
|
return {
|
|
129
32
|
success: true,
|
|
130
|
-
message:
|
|
131
|
-
filesChanged: [
|
|
33
|
+
message: 'Gitignore check deprecated (state file no longer used)',
|
|
34
|
+
filesChanged: [],
|
|
132
35
|
};
|
|
133
36
|
}
|
|
134
|
-
/**
|
|
135
|
-
* Generate content for a new .gitignore file
|
|
136
|
-
*/
|
|
137
|
-
generateNewGitignoreContent() {
|
|
138
|
-
return `# vibe-validate
|
|
139
|
-
${STATE_FILE_ENTRY}
|
|
140
|
-
`;
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Add state file entry to existing .gitignore content
|
|
144
|
-
*/
|
|
145
|
-
addEntryToGitignore(content) {
|
|
146
|
-
// Ensure content ends with newline
|
|
147
|
-
let updatedContent = content;
|
|
148
|
-
if (!content.endsWith('\n')) {
|
|
149
|
-
updatedContent += '\n';
|
|
150
|
-
}
|
|
151
|
-
// Add vibe-validate section
|
|
152
|
-
updatedContent += `\n# vibe-validate\n${STATE_FILE_ENTRY}\n`;
|
|
153
|
-
return updatedContent;
|
|
154
|
-
}
|
|
155
37
|
}
|
|
156
38
|
//# sourceMappingURL=gitignore-check.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gitignore-check.js","sourceRoot":"","sources":["../../../src/utils/setup-checks/gitignore-check.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"gitignore-check.js","sourceRoot":"","sources":["../../../src/utils/setup-checks/gitignore-check.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAUH,MAAM,OAAO,mBAAmB;IACrB,EAAE,GAAG,WAAW,CAAC;IACjB,IAAI,GAAG,8BAA8B,CAAC;IAE/C,KAAK,CAAC,KAAK,CAAC,QAAqB;QAC/B,8EAA8E;QAC9E,4DAA4D;QAC5D,OAAO;YACL,MAAM,EAAE,IAAI;YACZ,OAAO,EAAE,6DAA6D;SACvE,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,QAAqB;QACjC,yEAAyE;QACzE,OAAO;YACL,WAAW,EAAE,wDAAwD;YACrE,aAAa,EAAE,EAAE;YACjB,OAAO,EAAE,EAAE;SACZ,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,QAAqB;QAC7B,yEAAyE;QACzE,OAAO;YACL,OAAO,EAAE,IAAI;YACb,OAAO,EAAE,wDAAwD;YACjE,YAAY,EAAE,EAAE;SACjB,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template Discovery Utility
|
|
3
|
+
*
|
|
4
|
+
* Discovers and reads metadata from config templates in the config-templates/ directory.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Metadata for a config template
|
|
8
|
+
*/
|
|
9
|
+
export interface TemplateMetadata {
|
|
10
|
+
/** Template filename (e.g., "typescript-nodejs.yaml") */
|
|
11
|
+
filename: string;
|
|
12
|
+
/** Display name extracted from header comment (e.g., "TypeScript for Node.js") */
|
|
13
|
+
displayName: string;
|
|
14
|
+
/** Short description extracted from template (e.g., "Node.js apps, APIs, and backend services") */
|
|
15
|
+
description: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Discover all available config templates
|
|
19
|
+
*
|
|
20
|
+
* Scans the config-templates/ directory and extracts metadata from each template.
|
|
21
|
+
* Results are sorted alphabetically by filename.
|
|
22
|
+
*
|
|
23
|
+
* @returns Array of template metadata
|
|
24
|
+
*/
|
|
25
|
+
export declare function discoverTemplates(): TemplateMetadata[];
|
|
26
|
+
/**
|
|
27
|
+
* Format template list for CLI output
|
|
28
|
+
*
|
|
29
|
+
* Creates a human-readable list of templates with descriptions.
|
|
30
|
+
*
|
|
31
|
+
* Example output:
|
|
32
|
+
* ```
|
|
33
|
+
* ⢠typescript-library.yaml - TypeScript libraries and npm packages
|
|
34
|
+
* ⢠typescript-nodejs.yaml - Node.js apps, APIs, and backend services
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @returns Formatted template list (one template per line)
|
|
38
|
+
*/
|
|
39
|
+
export declare function formatTemplateList(): string[];
|
|
40
|
+
//# sourceMappingURL=template-discovery.d.ts.map
|
|
@@ -0,0 +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;AAoFD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,IAAI,gBAAgB,EAAE,CAsBtD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,EAAE,CAa7C"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template Discovery Utility
|
|
3
|
+
*
|
|
4
|
+
* Discovers and reads metadata from config templates in the config-templates/ directory.
|
|
5
|
+
*/
|
|
6
|
+
import { readdirSync, readFileSync, existsSync } from 'fs';
|
|
7
|
+
import { join, dirname } from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
import { splitLines } from './normalize-line-endings.js';
|
|
10
|
+
/**
|
|
11
|
+
* Get the absolute path to the config-templates directory
|
|
12
|
+
*
|
|
13
|
+
* This function works both in development (from source) and when installed as npm package.
|
|
14
|
+
*
|
|
15
|
+
* @returns Absolute path to config-templates directory
|
|
16
|
+
*/
|
|
17
|
+
function getTemplatesDir() {
|
|
18
|
+
// In production (npm package), templates are at <package-root>/config-templates
|
|
19
|
+
// In development, templates are at <repo-root>/config-templates
|
|
20
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
21
|
+
const __dirname = dirname(__filename);
|
|
22
|
+
// Try paths in order:
|
|
23
|
+
// 1. Development: packages/cli/src/utils/../../../config-templates
|
|
24
|
+
const devPath = join(__dirname, '../../../../config-templates');
|
|
25
|
+
if (existsSync(devPath)) {
|
|
26
|
+
return devPath;
|
|
27
|
+
}
|
|
28
|
+
// 2. Production: packages/cli/dist/utils/../../config-templates
|
|
29
|
+
const prodPath = join(__dirname, '../../../config-templates');
|
|
30
|
+
if (existsSync(prodPath)) {
|
|
31
|
+
return prodPath;
|
|
32
|
+
}
|
|
33
|
+
// 3. Fallback: assume monorepo root
|
|
34
|
+
const fallbackPath = join(process.cwd(), 'config-templates');
|
|
35
|
+
return fallbackPath;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Extract template metadata from YAML file
|
|
39
|
+
*
|
|
40
|
+
* Parses the header comment block to extract display name and description.
|
|
41
|
+
*
|
|
42
|
+
* Expected format:
|
|
43
|
+
* ```yaml
|
|
44
|
+
* # ============================================================================
|
|
45
|
+
* # CONFIGURATION TEMPLATE - vibe-validate for TypeScript Libraries
|
|
46
|
+
* # ============================================================================
|
|
47
|
+
* # ...
|
|
48
|
+
* # This template is optimized for TypeScript libraries and npm packages.
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* @param filename - Template filename
|
|
52
|
+
* @param content - Template file content
|
|
53
|
+
* @returns Template metadata
|
|
54
|
+
*/
|
|
55
|
+
function parseTemplateMetadata(filename, content) {
|
|
56
|
+
const lines = splitLines(content);
|
|
57
|
+
// Find the title line (line 2, format: "# CONFIGURATION TEMPLATE - <title>")
|
|
58
|
+
let displayName = filename.replace('.yaml', '');
|
|
59
|
+
const titleLine = lines.find(line => line.includes('CONFIGURATION TEMPLATE -'));
|
|
60
|
+
if (titleLine) {
|
|
61
|
+
const match = titleLine.match(/CONFIGURATION TEMPLATE\s*-\s*(.+)/);
|
|
62
|
+
if (match) {
|
|
63
|
+
displayName = match[1].trim();
|
|
64
|
+
// Remove "vibe-validate for " prefix if present
|
|
65
|
+
displayName = displayName.replace(/^vibe-validate for\s+/i, '');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Find the description (first line starting with "# This template is")
|
|
69
|
+
let description = '';
|
|
70
|
+
const descLine = lines.find(line => line.trim().startsWith('# This template is'));
|
|
71
|
+
if (descLine) {
|
|
72
|
+
description = descLine.replace(/^#\s*This template is\s+/, '').replace(/\.$/, '').trim();
|
|
73
|
+
// Capitalize first letter
|
|
74
|
+
if (description.length > 0) {
|
|
75
|
+
description = description.charAt(0).toUpperCase() + description.slice(1);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
filename,
|
|
80
|
+
displayName,
|
|
81
|
+
description,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Discover all available config templates
|
|
86
|
+
*
|
|
87
|
+
* Scans the config-templates/ directory and extracts metadata from each template.
|
|
88
|
+
* Results are sorted alphabetically by filename.
|
|
89
|
+
*
|
|
90
|
+
* @returns Array of template metadata
|
|
91
|
+
*/
|
|
92
|
+
export function discoverTemplates() {
|
|
93
|
+
const templatesDir = getTemplatesDir();
|
|
94
|
+
// Check if directory exists
|
|
95
|
+
if (!existsSync(templatesDir)) {
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
// Read all .yaml files
|
|
99
|
+
const files = readdirSync(templatesDir)
|
|
100
|
+
.filter(file => file.endsWith('.yaml'))
|
|
101
|
+
.sort();
|
|
102
|
+
// Parse metadata from each template
|
|
103
|
+
const templates = [];
|
|
104
|
+
for (const file of files) {
|
|
105
|
+
const filePath = join(templatesDir, file);
|
|
106
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
107
|
+
templates.push(parseTemplateMetadata(file, content));
|
|
108
|
+
}
|
|
109
|
+
return templates;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Format template list for CLI output
|
|
113
|
+
*
|
|
114
|
+
* Creates a human-readable list of templates with descriptions.
|
|
115
|
+
*
|
|
116
|
+
* Example output:
|
|
117
|
+
* ```
|
|
118
|
+
* ⢠typescript-library.yaml - TypeScript libraries and npm packages
|
|
119
|
+
* ⢠typescript-nodejs.yaml - Node.js apps, APIs, and backend services
|
|
120
|
+
* ```
|
|
121
|
+
*
|
|
122
|
+
* @returns Formatted template list (one template per line)
|
|
123
|
+
*/
|
|
124
|
+
export function formatTemplateList() {
|
|
125
|
+
const templates = discoverTemplates();
|
|
126
|
+
if (templates.length === 0) {
|
|
127
|
+
return ['No templates found'];
|
|
128
|
+
}
|
|
129
|
+
return templates.map(t => {
|
|
130
|
+
if (t.description) {
|
|
131
|
+
return `⢠${t.filename} - ${t.description}`;
|
|
132
|
+
}
|
|
133
|
+
return `⢠${t.filename}`;
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=template-discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template-discovery.js","sourceRoot":"","sources":["../../src/utils/template-discovery.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Validation Workflow
|
|
3
|
+
*
|
|
4
|
+
* Core validation logic used by both validate and pre-commit commands.
|
|
5
|
+
* Handles caching, history recording, and output formatting.
|
|
6
|
+
*/
|
|
7
|
+
import type { VibeValidateConfig } from '@vibe-validate/config';
|
|
8
|
+
import type { ValidationResult } from '@vibe-validate/core';
|
|
9
|
+
import type { AgentContext } from './context-detector.js';
|
|
10
|
+
export interface ValidateWorkflowOptions {
|
|
11
|
+
force?: boolean;
|
|
12
|
+
verbose?: boolean;
|
|
13
|
+
yaml?: boolean;
|
|
14
|
+
check?: boolean;
|
|
15
|
+
context: AgentContext;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Execute validation workflow with caching, history recording, and output formatting.
|
|
19
|
+
*
|
|
20
|
+
* This is the shared implementation used by both `validate` and `pre-commit` commands.
|
|
21
|
+
*
|
|
22
|
+
* @param config - Loaded vibe-validate configuration
|
|
23
|
+
* @param options - Validation options (force, verbose, yaml, check, context)
|
|
24
|
+
* @returns Validation result
|
|
25
|
+
* @throws Error if validation encounters fatal error
|
|
26
|
+
*/
|
|
27
|
+
export declare function runValidateWorkflow(config: VibeValidateConfig, options: ValidateWorkflowOptions): Promise<ValidationResult>;
|
|
28
|
+
//# sourceMappingURL=validate-workflow.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Validation Workflow
|
|
3
|
+
*
|
|
4
|
+
* Core validation logic used by both validate and pre-commit commands.
|
|
5
|
+
* Handles caching, history recording, and output formatting.
|
|
6
|
+
*/
|
|
7
|
+
import { runValidation } from '@vibe-validate/core';
|
|
8
|
+
import { getGitTreeHash } from '@vibe-validate/git';
|
|
9
|
+
import { recordValidationHistory, checkWorktreeStability, checkHistoryHealth, readHistoryNote, } from '@vibe-validate/history';
|
|
10
|
+
import { createRunnerConfig } from './runner-adapter.js';
|
|
11
|
+
import chalk from 'chalk';
|
|
12
|
+
import { stringify as yamlStringify } from 'yaml';
|
|
13
|
+
/**
|
|
14
|
+
* Execute validation workflow with caching, history recording, and output formatting.
|
|
15
|
+
*
|
|
16
|
+
* This is the shared implementation used by both `validate` and `pre-commit` commands.
|
|
17
|
+
*
|
|
18
|
+
* @param config - Loaded vibe-validate configuration
|
|
19
|
+
* @param options - Validation options (force, verbose, yaml, check, context)
|
|
20
|
+
* @returns Validation result
|
|
21
|
+
* @throws Error if validation encounters fatal error
|
|
22
|
+
*/
|
|
23
|
+
export async function runValidateWorkflow(config, options) {
|
|
24
|
+
try {
|
|
25
|
+
// If --check flag is used, only check validation state without running
|
|
26
|
+
if (options.check) {
|
|
27
|
+
const yaml = options.yaml ?? false;
|
|
28
|
+
const { checkValidationStatus } = await import('./check-validation.js');
|
|
29
|
+
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
|
+
throw new Error('checkValidationStatus should have exited');
|
|
33
|
+
}
|
|
34
|
+
const verbose = options.verbose ?? false;
|
|
35
|
+
const yaml = options.yaml ?? false;
|
|
36
|
+
// Create runner config
|
|
37
|
+
const runnerConfig = createRunnerConfig(config, {
|
|
38
|
+
force: options.force,
|
|
39
|
+
verbose,
|
|
40
|
+
yaml,
|
|
41
|
+
context: options.context,
|
|
42
|
+
});
|
|
43
|
+
// Get tree hash BEFORE validation (for caching and stability check)
|
|
44
|
+
let treeHashBefore = null;
|
|
45
|
+
try {
|
|
46
|
+
treeHashBefore = await getGitTreeHash();
|
|
47
|
+
}
|
|
48
|
+
catch (_error) {
|
|
49
|
+
// Not in git repo or git command failed - continue without history
|
|
50
|
+
if (verbose) {
|
|
51
|
+
console.warn(chalk.yellow('ā ļø Could not get git tree hash - history recording disabled'));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Check cache: if validation already passed for this tree hash, skip re-running
|
|
55
|
+
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
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Display tree hash before running validation (debugging/transparency aid)
|
|
109
|
+
// This goes to stderr, so it's visible even in YAML mode
|
|
110
|
+
if (treeHashBefore) {
|
|
111
|
+
console.error(chalk.gray(`š³ Working tree: ${treeHashBefore.slice(0, 12)}...`));
|
|
112
|
+
if (!yaml) {
|
|
113
|
+
console.log(''); // Blank line for readability (human mode only)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Run validation
|
|
117
|
+
const result = await runValidation(runnerConfig);
|
|
118
|
+
// Record validation history (if in git repo and stability check passes)
|
|
119
|
+
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
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// Proactive health check (non-blocking)
|
|
151
|
+
try {
|
|
152
|
+
const health = await checkHistoryHealth();
|
|
153
|
+
if (health.shouldWarn) {
|
|
154
|
+
console.log('');
|
|
155
|
+
console.log(chalk.blue(health.warningMessage));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// Silent failure - don't block validation
|
|
160
|
+
}
|
|
161
|
+
// If validation failed, show agent-friendly error details
|
|
162
|
+
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
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// Output YAML validation result if --yaml flag is set
|
|
195
|
+
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
|
+
});
|
|
215
|
+
}
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
console.error(chalk.red('ā Validation failed with error:'), error);
|
|
220
|
+
// If YAML mode, output error as YAML to stdout for CI extraction
|
|
221
|
+
if (options.yaml) {
|
|
222
|
+
const errorResult = {
|
|
223
|
+
passed: false,
|
|
224
|
+
timestamp: new Date().toISOString(),
|
|
225
|
+
error: error instanceof Error ? error.message : String(error),
|
|
226
|
+
errorStack: error instanceof Error ? error.stack : undefined,
|
|
227
|
+
};
|
|
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
|
+
});
|
|
242
|
+
}
|
|
243
|
+
// Re-throw to allow caller to handle exit
|
|
244
|
+
throw error;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
//# sourceMappingURL=validate-workflow.js.map
|