@vibe-validate/cli 0.16.0 → 0.17.0-rc.10
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/config-templates/minimal.yaml +19 -8
- package/config-templates/typescript-library.yaml +19 -8
- package/config-templates/typescript-nodejs.yaml +19 -8
- package/config-templates/typescript-react.yaml +19 -8
- package/dist/bin.js +6 -0
- package/dist/bin.js.map +1 -1
- package/dist/commands/create-extractor.d.ts +13 -0
- package/dist/commands/create-extractor.d.ts.map +1 -0
- package/dist/commands/create-extractor.js +790 -0
- package/dist/commands/create-extractor.js.map +1 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +140 -109
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/generate-workflow.d.ts.map +1 -1
- package/dist/commands/generate-workflow.js +8 -0
- package/dist/commands/generate-workflow.js.map +1 -1
- package/dist/commands/pre-commit.d.ts.map +1 -1
- package/dist/commands/pre-commit.js +128 -56
- package/dist/commands/pre-commit.js.map +1 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +107 -44
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +25 -9
- package/dist/commands/validate.js.map +1 -1
- package/dist/services/ci-providers/github-actions.d.ts.map +1 -1
- package/dist/services/ci-providers/github-actions.js +3 -2
- package/dist/services/ci-providers/github-actions.js.map +1 -1
- package/dist/utils/config-loader.d.ts +26 -3
- package/dist/utils/config-loader.d.ts.map +1 -1
- package/dist/utils/config-loader.js +80 -11
- package/dist/utils/config-loader.js.map +1 -1
- package/dist/utils/git-detection.d.ts.map +1 -1
- package/dist/utils/git-detection.js +18 -18
- package/dist/utils/git-detection.js.map +1 -1
- package/dist/utils/project-id.d.ts +1 -2
- package/dist/utils/project-id.d.ts.map +1 -1
- package/dist/utils/project-id.js +6 -11
- package/dist/utils/project-id.js.map +1 -1
- package/dist/utils/runner-adapter.d.ts.map +1 -1
- package/dist/utils/runner-adapter.js +1 -0
- package/dist/utils/runner-adapter.js.map +1 -1
- package/dist/utils/secret-scanning.d.ts +72 -0
- package/dist/utils/secret-scanning.d.ts.map +1 -0
- package/dist/utils/secret-scanning.js +205 -0
- package/dist/utils/secret-scanning.js.map +1 -0
- package/dist/utils/validate-workflow.d.ts.map +1 -1
- package/dist/utils/validate-workflow.js +9 -1
- package/dist/utils/validate-workflow.js.map +1 -1
- package/package.json +8 -6
|
@@ -0,0 +1,790 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create Extractor Command
|
|
3
|
+
*
|
|
4
|
+
* Interactive scaffold generator for vibe-validate extractor plugins.
|
|
5
|
+
* Creates a fully-functional plugin directory with tests, samples, and documentation.
|
|
6
|
+
*/
|
|
7
|
+
import { mkdirSync, writeFileSync, existsSync, readFileSync } from 'node:fs';
|
|
8
|
+
import { join, dirname } from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
import prompts from 'prompts';
|
|
12
|
+
export function createExtractorCommand(program) {
|
|
13
|
+
program
|
|
14
|
+
.command('create-extractor [name]')
|
|
15
|
+
.description('Create a new extractor plugin from template')
|
|
16
|
+
.option('--description <desc>', 'Plugin description')
|
|
17
|
+
.option('--author <author>', 'Author name and email')
|
|
18
|
+
.option('--detection-pattern <pattern>', 'Detection keyword or pattern')
|
|
19
|
+
.option('--priority <number>', 'Detection priority (higher = check first)', '70')
|
|
20
|
+
.option('-f, --force', 'Overwrite existing plugin directory')
|
|
21
|
+
.action(async (name, options) => {
|
|
22
|
+
try {
|
|
23
|
+
const cwd = process.cwd();
|
|
24
|
+
// Interactive prompts for missing information
|
|
25
|
+
const context = await gatherContext(name, options);
|
|
26
|
+
// Determine output directory
|
|
27
|
+
const pluginDir = join(cwd, `vibe-validate-plugin-${context.pluginName}`);
|
|
28
|
+
// Check if directory exists
|
|
29
|
+
if (existsSync(pluginDir) && !options.force) {
|
|
30
|
+
console.error(chalk.red('❌ Plugin directory already exists:'));
|
|
31
|
+
console.error(chalk.gray(` ${pluginDir}`));
|
|
32
|
+
console.error(chalk.gray(' Use --force to overwrite'));
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
// Create plugin directory
|
|
36
|
+
console.log(chalk.blue('🔨 Creating extractor plugin...'));
|
|
37
|
+
createPluginDirectory(pluginDir, context);
|
|
38
|
+
console.log(chalk.green('✅ Extractor plugin created successfully!'));
|
|
39
|
+
console.log(chalk.blue(`📁 Created: ${pluginDir}`));
|
|
40
|
+
console.log();
|
|
41
|
+
console.log(chalk.yellow('Next steps:'));
|
|
42
|
+
console.log(chalk.gray(' 1. cd ' + `vibe-validate-plugin-${context.pluginName}`));
|
|
43
|
+
console.log(chalk.gray(' 2. npm install'));
|
|
44
|
+
console.log(chalk.gray(' 3. Add your sample error output to samples/sample-error.txt'));
|
|
45
|
+
console.log(chalk.gray(' 4. Implement detect() and extract() functions in index.ts'));
|
|
46
|
+
console.log(chalk.gray(' 5. Run tests: npm test'));
|
|
47
|
+
console.log(chalk.gray(' 6. Test the plugin: vibe-validate test-extractor .'));
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
console.error(chalk.red('❌ Failed to create extractor plugin:'), error);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Gather context from command-line arguments and interactive prompts
|
|
58
|
+
*/
|
|
59
|
+
async function gatherContext(name, options) {
|
|
60
|
+
// Prompt for missing information
|
|
61
|
+
const responses = await prompts([
|
|
62
|
+
{
|
|
63
|
+
type: name ? null : 'text',
|
|
64
|
+
name: 'pluginName',
|
|
65
|
+
message: 'Plugin name (kebab-case, e.g., "my-tool"):',
|
|
66
|
+
validate: (value) => /^[a-z][a-z0-9-]*$/.test(value) || 'Must be lowercase alphanumeric with hyphens',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
type: options.description ? null : 'text',
|
|
70
|
+
name: 'description',
|
|
71
|
+
message: 'Plugin description:',
|
|
72
|
+
validate: (value) => value.length > 0 || 'Description is required',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
type: options.author ? null : 'text',
|
|
76
|
+
name: 'author',
|
|
77
|
+
message: 'Author (name <email>):',
|
|
78
|
+
initial: process.env.GIT_AUTHOR_NAME
|
|
79
|
+
? `${process.env.GIT_AUTHOR_NAME} <${process.env.GIT_AUTHOR_EMAIL ?? ''}>`
|
|
80
|
+
: '',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
type: options.detectionPattern ? null : 'text',
|
|
84
|
+
name: 'detectionPattern',
|
|
85
|
+
message: 'Detection keyword (e.g., "ERROR:", "[FAIL]"):',
|
|
86
|
+
validate: (value) => value.length > 0 || 'Detection keyword is required',
|
|
87
|
+
},
|
|
88
|
+
]);
|
|
89
|
+
// User cancelled prompts
|
|
90
|
+
if (!responses.pluginName && !name) {
|
|
91
|
+
console.log(chalk.yellow('\n✋ Cancelled'));
|
|
92
|
+
process.exit(0);
|
|
93
|
+
}
|
|
94
|
+
const pluginName = name ?? responses.pluginName;
|
|
95
|
+
const description = options.description ?? responses.description;
|
|
96
|
+
const author = options.author ?? responses.author ?? 'Unknown';
|
|
97
|
+
const detectionPattern = options.detectionPattern ?? responses.detectionPattern;
|
|
98
|
+
const priority = typeof options.priority === 'number'
|
|
99
|
+
? options.priority
|
|
100
|
+
: Number.parseInt(options.priority ?? '70', 10);
|
|
101
|
+
// Generate derived values
|
|
102
|
+
const className = kebabToPascalCase(pluginName);
|
|
103
|
+
const displayName = kebabToTitleCase(pluginName);
|
|
104
|
+
const year = new Date().getFullYear().toString();
|
|
105
|
+
return {
|
|
106
|
+
pluginName,
|
|
107
|
+
className,
|
|
108
|
+
displayName,
|
|
109
|
+
description,
|
|
110
|
+
author,
|
|
111
|
+
priority,
|
|
112
|
+
detectionPattern,
|
|
113
|
+
year,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Create plugin directory with all files
|
|
118
|
+
*/
|
|
119
|
+
function createPluginDirectory(pluginDir, context) {
|
|
120
|
+
// Create directories
|
|
121
|
+
mkdirSync(pluginDir, { recursive: true });
|
|
122
|
+
mkdirSync(join(pluginDir, 'samples'), { recursive: true });
|
|
123
|
+
// Write files
|
|
124
|
+
writeFileSync(join(pluginDir, 'index.ts'), generateIndexTs(context), 'utf-8');
|
|
125
|
+
writeFileSync(join(pluginDir, 'index.test.ts'), generateIndexTestTs(context), 'utf-8');
|
|
126
|
+
writeFileSync(join(pluginDir, 'README.md'), generateReadme(context), 'utf-8');
|
|
127
|
+
writeFileSync(join(pluginDir, 'CLAUDE.md'), generateClaudeMd(context), 'utf-8');
|
|
128
|
+
writeFileSync(join(pluginDir, 'package.json'), generatePackageJson(context), 'utf-8');
|
|
129
|
+
writeFileSync(join(pluginDir, 'tsconfig.json'), generateTsConfig(context), 'utf-8');
|
|
130
|
+
writeFileSync(join(pluginDir, 'samples', 'sample-error.txt'), generateSampleError(context), 'utf-8');
|
|
131
|
+
console.log(chalk.gray(' ✓ Created index.ts'));
|
|
132
|
+
console.log(chalk.gray(' ✓ Created index.test.ts'));
|
|
133
|
+
console.log(chalk.gray(' ✓ Created README.md'));
|
|
134
|
+
console.log(chalk.gray(' ✓ Created CLAUDE.md'));
|
|
135
|
+
console.log(chalk.gray(' ✓ Created package.json'));
|
|
136
|
+
console.log(chalk.gray(' ✓ Created tsconfig.json'));
|
|
137
|
+
console.log(chalk.gray(' ✓ Created samples/sample-error.txt'));
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Generate index.ts (main plugin file)
|
|
141
|
+
*/
|
|
142
|
+
function generateIndexTs(context) {
|
|
143
|
+
return `/**
|
|
144
|
+
* ${context.displayName} Extractor Plugin
|
|
145
|
+
*
|
|
146
|
+
* ${context.description}
|
|
147
|
+
*
|
|
148
|
+
* @package vibe-validate-plugin-${context.pluginName}
|
|
149
|
+
*/
|
|
150
|
+
|
|
151
|
+
import type {
|
|
152
|
+
ExtractorPlugin,
|
|
153
|
+
DetectionResult,
|
|
154
|
+
ErrorExtractorResult,
|
|
155
|
+
FormattedError,
|
|
156
|
+
} from '@vibe-validate/extractors';
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Detects if output is from ${context.displayName}
|
|
160
|
+
*/
|
|
161
|
+
export function detect${context.className}(output: string): DetectionResult {
|
|
162
|
+
const lines = output.split('\\n');
|
|
163
|
+
let score = 0;
|
|
164
|
+
const foundPatterns: string[] = [];
|
|
165
|
+
|
|
166
|
+
// TODO: Implement your detection logic here
|
|
167
|
+
// Example: Look for specific keywords or patterns
|
|
168
|
+
for (const line of lines) {
|
|
169
|
+
if (line.includes('${context.detectionPattern}')) {
|
|
170
|
+
score += 50;
|
|
171
|
+
foundPatterns.push('${context.detectionPattern} marker');
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Determine reason based on score
|
|
177
|
+
let reason: string;
|
|
178
|
+
if (score >= 70) {
|
|
179
|
+
reason = '${context.displayName} output detected';
|
|
180
|
+
} else if (score >= 40) {
|
|
181
|
+
reason = 'Possible ${context.displayName} output';
|
|
182
|
+
} else {
|
|
183
|
+
reason = 'Not ${context.displayName} output';
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
confidence: Math.min(score, 100),
|
|
188
|
+
patterns: foundPatterns,
|
|
189
|
+
reason,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Extracts errors from ${context.displayName} output
|
|
195
|
+
*/
|
|
196
|
+
export function extract${context.className}(
|
|
197
|
+
output: string,
|
|
198
|
+
command?: string
|
|
199
|
+
): ErrorExtractorResult {
|
|
200
|
+
const detection = detect${context.className}(output);
|
|
201
|
+
|
|
202
|
+
if (detection.confidence < 40) {
|
|
203
|
+
return {
|
|
204
|
+
summary: 'Not ${context.displayName} output',
|
|
205
|
+
totalErrors: 0,
|
|
206
|
+
errors: [],
|
|
207
|
+
metadata: {
|
|
208
|
+
detection: {
|
|
209
|
+
extractor: '${context.pluginName}',
|
|
210
|
+
confidence: detection.confidence,
|
|
211
|
+
patterns: detection.patterns,
|
|
212
|
+
reason: detection.reason,
|
|
213
|
+
},
|
|
214
|
+
confidence: detection.confidence,
|
|
215
|
+
completeness: 100,
|
|
216
|
+
issues: [],
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const errors: FormattedError[] = [];
|
|
222
|
+
const lines = output.split('\\n');
|
|
223
|
+
|
|
224
|
+
// TODO: Implement your error extraction logic here
|
|
225
|
+
// Example: Parse error lines and extract file, line, column, message
|
|
226
|
+
for (const line of lines) {
|
|
227
|
+
if (line.includes('${context.detectionPattern}')) {
|
|
228
|
+
// Extract error details (customize this for your format)
|
|
229
|
+
errors.push({
|
|
230
|
+
file: 'unknown',
|
|
231
|
+
line: 1,
|
|
232
|
+
message: line.trim(),
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const summary = \`\${errors.length} error(s) found\`;
|
|
238
|
+
const guidance = errors.length > 0
|
|
239
|
+
? \`Fix errors shown above. Run \${command ?? 'your-command'} to see all details.\`
|
|
240
|
+
: undefined;
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
summary,
|
|
244
|
+
totalErrors: errors.length,
|
|
245
|
+
errors,
|
|
246
|
+
guidance,
|
|
247
|
+
metadata: {
|
|
248
|
+
detection: {
|
|
249
|
+
extractor: '${context.pluginName}',
|
|
250
|
+
confidence: detection.confidence,
|
|
251
|
+
patterns: detection.patterns,
|
|
252
|
+
reason: detection.reason,
|
|
253
|
+
},
|
|
254
|
+
confidence: 100,
|
|
255
|
+
completeness: 100,
|
|
256
|
+
issues: [],
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* ${context.displayName} Extractor Plugin
|
|
263
|
+
*/
|
|
264
|
+
const ${context.pluginName.replace(/-/g, '')}Extractor: ExtractorPlugin = {
|
|
265
|
+
metadata: {
|
|
266
|
+
name: '${context.pluginName}',
|
|
267
|
+
version: '1.0.0',
|
|
268
|
+
author: '${context.author}',
|
|
269
|
+
description: '${context.description}',
|
|
270
|
+
repository: 'https://github.com/your-username/vibe-validate-plugin-${context.pluginName}',
|
|
271
|
+
tags: ['custom', '${context.pluginName}'],
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
hints: {
|
|
275
|
+
required: ['${context.detectionPattern}'],
|
|
276
|
+
},
|
|
277
|
+
|
|
278
|
+
priority: ${context.priority},
|
|
279
|
+
|
|
280
|
+
detect: detect${context.className},
|
|
281
|
+
extract: extract${context.className},
|
|
282
|
+
|
|
283
|
+
samples: [
|
|
284
|
+
{
|
|
285
|
+
name: 'basic-error',
|
|
286
|
+
description: 'Basic error output',
|
|
287
|
+
inputFile: './samples/sample-error.txt',
|
|
288
|
+
expected: {
|
|
289
|
+
totalErrors: 1,
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
],
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
export default ${context.pluginName.replace(/-/g, '')}Extractor;
|
|
296
|
+
`;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Generate index.test.ts (tests)
|
|
300
|
+
*/
|
|
301
|
+
function generateIndexTestTs(context) {
|
|
302
|
+
return `/**
|
|
303
|
+
* ${context.displayName} Extractor Tests
|
|
304
|
+
*/
|
|
305
|
+
|
|
306
|
+
import { describe, it, expect } from 'vitest';
|
|
307
|
+
import { readFileSync } from 'node:fs';
|
|
308
|
+
import { join } from 'node:path';
|
|
309
|
+
import extractor from './index.js';
|
|
310
|
+
|
|
311
|
+
describe('${context.pluginName} extractor', () => {
|
|
312
|
+
it('should have correct metadata', () => {
|
|
313
|
+
expect(extractor.metadata.name).toBe('${context.pluginName}');
|
|
314
|
+
expect(extractor.metadata.version).toBe('1.0.0');
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('should detect ${context.displayName} output', () => {
|
|
318
|
+
const output = '${context.detectionPattern} Something went wrong';
|
|
319
|
+
const result = extractor.detect(output);
|
|
320
|
+
|
|
321
|
+
expect(result.confidence).toBeGreaterThan(40);
|
|
322
|
+
expect(result.patterns.length).toBeGreaterThan(0);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('should not detect non-${context.displayName} output', () => {
|
|
326
|
+
const output = 'This is some random text';
|
|
327
|
+
const result = extractor.detect(output);
|
|
328
|
+
|
|
329
|
+
expect(result.confidence).toBeLessThan(40);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it('should extract errors from ${context.displayName} output', () => {
|
|
333
|
+
const output = '${context.detectionPattern} Something went wrong';
|
|
334
|
+
const result = extractor.extract(output);
|
|
335
|
+
|
|
336
|
+
expect(result.totalErrors).toBeGreaterThan(0);
|
|
337
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('should process sample file', () => {
|
|
341
|
+
const samplePath = join(__dirname, 'samples', 'sample-error.txt');
|
|
342
|
+
const sampleContent = readFileSync(samplePath, 'utf-8');
|
|
343
|
+
|
|
344
|
+
const result = extractor.extract(sampleContent);
|
|
345
|
+
|
|
346
|
+
// Update these expectations based on your sample file
|
|
347
|
+
expect(result.totalErrors).toBeGreaterThanOrEqual(1);
|
|
348
|
+
expect(result.summary).toBeTruthy();
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
`;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Generate README.md
|
|
355
|
+
*/
|
|
356
|
+
function generateReadme(context) {
|
|
357
|
+
return `# ${context.displayName} Extractor Plugin
|
|
358
|
+
|
|
359
|
+
${context.description}
|
|
360
|
+
|
|
361
|
+
## Installation
|
|
362
|
+
|
|
363
|
+
\`\`\`bash
|
|
364
|
+
npm install vibe-validate-plugin-${context.pluginName}
|
|
365
|
+
\`\`\`
|
|
366
|
+
|
|
367
|
+
## Usage
|
|
368
|
+
|
|
369
|
+
Add to your \`vibe-validate.config.yaml\`:
|
|
370
|
+
|
|
371
|
+
\`\`\`yaml
|
|
372
|
+
extractors:
|
|
373
|
+
external:
|
|
374
|
+
- package: vibe-validate-plugin-${context.pluginName}
|
|
375
|
+
trust: sandbox # Run in sandbox for security
|
|
376
|
+
\`\`\`
|
|
377
|
+
|
|
378
|
+
Or use a local plugin:
|
|
379
|
+
|
|
380
|
+
\`\`\`yaml
|
|
381
|
+
extractors:
|
|
382
|
+
localPlugins:
|
|
383
|
+
- path: ./vibe-validate-local-plugins/${context.pluginName}
|
|
384
|
+
trust: sandbox
|
|
385
|
+
\`\`\`
|
|
386
|
+
|
|
387
|
+
## Development
|
|
388
|
+
|
|
389
|
+
\`\`\`bash
|
|
390
|
+
# Install dependencies
|
|
391
|
+
npm install
|
|
392
|
+
|
|
393
|
+
# Run tests
|
|
394
|
+
npm test
|
|
395
|
+
|
|
396
|
+
# Build
|
|
397
|
+
npm run build
|
|
398
|
+
\`\`\`
|
|
399
|
+
|
|
400
|
+
## How It Works
|
|
401
|
+
|
|
402
|
+
This extractor:
|
|
403
|
+
1. Detects ${context.displayName} output by looking for "${context.detectionPattern}"
|
|
404
|
+
2. Extracts error information (file, line, message)
|
|
405
|
+
3. Formats errors for LLM consumption
|
|
406
|
+
|
|
407
|
+
## Configuration
|
|
408
|
+
|
|
409
|
+
Priority: ${context.priority}
|
|
410
|
+
|
|
411
|
+
Detection hints:
|
|
412
|
+
- Required keywords: "${context.detectionPattern}"
|
|
413
|
+
|
|
414
|
+
## Sample Output
|
|
415
|
+
|
|
416
|
+
See \`samples/sample-error.txt\` for example error output this extractor handles.
|
|
417
|
+
|
|
418
|
+
## License
|
|
419
|
+
|
|
420
|
+
MIT
|
|
421
|
+
|
|
422
|
+
## Author
|
|
423
|
+
|
|
424
|
+
${context.author}
|
|
425
|
+
`;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Generate CLAUDE.md
|
|
429
|
+
*/
|
|
430
|
+
function generateClaudeMd(context) {
|
|
431
|
+
return `# ${context.displayName} Extractor - Claude Code Guidance
|
|
432
|
+
|
|
433
|
+
This file provides guidance to Claude Code when working on this extractor.
|
|
434
|
+
|
|
435
|
+
## What This Extractor Does
|
|
436
|
+
|
|
437
|
+
${context.description}
|
|
438
|
+
|
|
439
|
+
## Plugin Architecture
|
|
440
|
+
|
|
441
|
+
This extractor follows the **ExtractorPlugin** interface:
|
|
442
|
+
|
|
443
|
+
\`\`\`typescript
|
|
444
|
+
{
|
|
445
|
+
metadata: { name, version, author, description, repository, tags },
|
|
446
|
+
hints: { required, anyOf, forbidden },
|
|
447
|
+
priority: number,
|
|
448
|
+
detect(output: string): DetectionResult,
|
|
449
|
+
extract(output: string, command?: string): ErrorExtractorResult,
|
|
450
|
+
samples: ExtractorSample[],
|
|
451
|
+
}
|
|
452
|
+
\`\`\`
|
|
453
|
+
|
|
454
|
+
### Key Principles
|
|
455
|
+
|
|
456
|
+
1. **No File I/O** - Extractor receives \`output: string\` parameter only (safe for sandboxing)
|
|
457
|
+
2. **Hints for Performance** - Simple string.includes() checks filter candidates before expensive detect()
|
|
458
|
+
3. **Samples Required** - Real-world test data co-located for testing
|
|
459
|
+
4. **Metadata is Source of Truth** - Registration name comes from \`metadata.name\`
|
|
460
|
+
|
|
461
|
+
## Code Structure
|
|
462
|
+
|
|
463
|
+
### Files
|
|
464
|
+
- \`index.ts\` - Main plugin export with detect() and extract() functions
|
|
465
|
+
- \`samples/\` - Real-world error output samples
|
|
466
|
+
- \`index.test.ts\` - Tests using samples
|
|
467
|
+
- \`README.md\` - Human-readable documentation
|
|
468
|
+
- \`CLAUDE.md\` - This file (LLM-specific guidance)
|
|
469
|
+
|
|
470
|
+
## Detection Logic
|
|
471
|
+
|
|
472
|
+
### Two-Phase Detection
|
|
473
|
+
|
|
474
|
+
**Phase 1: Fast Hints (string.includes() only)**
|
|
475
|
+
\`\`\`typescript
|
|
476
|
+
hints: {
|
|
477
|
+
required: ['${context.detectionPattern}'],
|
|
478
|
+
}
|
|
479
|
+
\`\`\`
|
|
480
|
+
|
|
481
|
+
**Phase 2: Precise Detection (if hints match)**
|
|
482
|
+
\`\`\`typescript
|
|
483
|
+
detect(output: string): DetectionResult {
|
|
484
|
+
// Additive scoring based on patterns found
|
|
485
|
+
// Returns confidence 0-100
|
|
486
|
+
}
|
|
487
|
+
\`\`\`
|
|
488
|
+
|
|
489
|
+
### Confidence Scoring
|
|
490
|
+
|
|
491
|
+
Adjust these thresholds based on your tool's output format:
|
|
492
|
+
- 70+ points = high confidence
|
|
493
|
+
- 40-69 = possible match
|
|
494
|
+
- <40 = not a match
|
|
495
|
+
|
|
496
|
+
## Testing Requirements
|
|
497
|
+
|
|
498
|
+
**CRITICAL:** All changes MUST include tests with real output from your tool.
|
|
499
|
+
|
|
500
|
+
### Test Data Requirements
|
|
501
|
+
|
|
502
|
+
1. **Real-world samples** - Use actual tool output (not hand-crafted)
|
|
503
|
+
2. **Co-located** - Store in \`samples/\` directory
|
|
504
|
+
3. **Redacted** - Remove sensitive paths, usernames, company names
|
|
505
|
+
|
|
506
|
+
### Running Tests
|
|
507
|
+
|
|
508
|
+
\`\`\`bash
|
|
509
|
+
# All tests
|
|
510
|
+
npm test
|
|
511
|
+
|
|
512
|
+
# Watch mode
|
|
513
|
+
npm test -- --watch
|
|
514
|
+
\`\`\`
|
|
515
|
+
|
|
516
|
+
## Security Considerations
|
|
517
|
+
|
|
518
|
+
This extractor is **SAFE for sandboxed execution**:
|
|
519
|
+
- ✅ **No file I/O** - Only reads \`output: string\` parameter
|
|
520
|
+
- ✅ **No process execution** - No \`child_process\`, \`exec\`, \`spawn\`
|
|
521
|
+
- ✅ **No network access** - No \`fetch\`, \`http\`, \`https\`
|
|
522
|
+
- ✅ **No dangerous APIs** - No \`eval\`, \`Function()\`, \`require()\`
|
|
523
|
+
- ✅ **Deterministic** - Same input always produces same output
|
|
524
|
+
|
|
525
|
+
## Common Modifications
|
|
526
|
+
|
|
527
|
+
### Adding New Error Pattern
|
|
528
|
+
|
|
529
|
+
1. Add pattern to detection logic
|
|
530
|
+
2. Update scoring if needed
|
|
531
|
+
3. Add sample demonstrating the pattern
|
|
532
|
+
4. Add test case
|
|
533
|
+
|
|
534
|
+
### Adjusting Detection Confidence
|
|
535
|
+
|
|
536
|
+
If false positives/negatives occur:
|
|
537
|
+
1. Review \`hints\` - are they too broad/narrow?
|
|
538
|
+
2. Review \`detect()\` scoring - do patterns need reweighting?
|
|
539
|
+
3. Add test case demonstrating the issue
|
|
540
|
+
4. Adjust hints or detection logic
|
|
541
|
+
`;
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Get CLI version from package.json
|
|
545
|
+
*/
|
|
546
|
+
function getCliVersion() {
|
|
547
|
+
try {
|
|
548
|
+
// Get the path to the CLI's package.json
|
|
549
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
550
|
+
const __dirname = dirname(__filename);
|
|
551
|
+
// Go up from dist/commands/ to package.json
|
|
552
|
+
const packageJsonPath = join(__dirname, '..', '..', 'package.json');
|
|
553
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
554
|
+
return packageJson.version;
|
|
555
|
+
}
|
|
556
|
+
catch {
|
|
557
|
+
// Fallback to a known version if reading fails
|
|
558
|
+
return '0.17.0';
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Get version range for package.json (e.g., "0.17.0-rc4" -> "^0.17.0-rc4")
|
|
563
|
+
*/
|
|
564
|
+
function getVersionRange(version) {
|
|
565
|
+
// For RC versions, use exact version to avoid issues
|
|
566
|
+
if (version.includes('-rc')) {
|
|
567
|
+
return version;
|
|
568
|
+
}
|
|
569
|
+
// For stable versions, use caret range
|
|
570
|
+
return `^${version}`;
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Generate package.json
|
|
574
|
+
*/
|
|
575
|
+
function generatePackageJson(context) {
|
|
576
|
+
const cliVersion = getCliVersion();
|
|
577
|
+
const versionRange = getVersionRange(cliVersion);
|
|
578
|
+
return `{
|
|
579
|
+
"name": "vibe-validate-plugin-${context.pluginName}",
|
|
580
|
+
"version": "1.0.0",
|
|
581
|
+
"description": "${context.description}",
|
|
582
|
+
"type": "module",
|
|
583
|
+
"main": "./dist/index.js",
|
|
584
|
+
"types": "./dist/index.d.ts",
|
|
585
|
+
"files": [
|
|
586
|
+
"dist",
|
|
587
|
+
"samples"
|
|
588
|
+
],
|
|
589
|
+
"scripts": {
|
|
590
|
+
"build": "tsc",
|
|
591
|
+
"test": "vitest run",
|
|
592
|
+
"test:watch": "vitest watch",
|
|
593
|
+
"prepublishOnly": "npm run build && npm test"
|
|
594
|
+
},
|
|
595
|
+
"keywords": [
|
|
596
|
+
"vibe-validate",
|
|
597
|
+
"extractor",
|
|
598
|
+
"plugin",
|
|
599
|
+
"${context.pluginName}"
|
|
600
|
+
],
|
|
601
|
+
"author": "${context.author}",
|
|
602
|
+
"license": "MIT",
|
|
603
|
+
"peerDependencies": {
|
|
604
|
+
"@vibe-validate/extractors": "${versionRange}"
|
|
605
|
+
},
|
|
606
|
+
"devDependencies": {
|
|
607
|
+
"@types/node": "^20.0.0",
|
|
608
|
+
"@vibe-validate/extractors": "${versionRange}",
|
|
609
|
+
"typescript": "^5.3.0",
|
|
610
|
+
"vitest": "^2.0.0"
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
`;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Generate tsconfig.json
|
|
617
|
+
*/
|
|
618
|
+
function generateTsConfig(_context) {
|
|
619
|
+
return `{
|
|
620
|
+
"compilerOptions": {
|
|
621
|
+
"target": "ES2022",
|
|
622
|
+
"module": "ES2022",
|
|
623
|
+
"lib": ["ES2022"],
|
|
624
|
+
"moduleResolution": "node",
|
|
625
|
+
"esModuleInterop": true,
|
|
626
|
+
"declaration": true,
|
|
627
|
+
"outDir": "./dist",
|
|
628
|
+
"rootDir": "./",
|
|
629
|
+
"strict": true,
|
|
630
|
+
"skipLibCheck": true,
|
|
631
|
+
"forceConsistentCasingInFileNames": true
|
|
632
|
+
},
|
|
633
|
+
"include": ["*.ts"],
|
|
634
|
+
"exclude": ["node_modules", "dist"]
|
|
635
|
+
}
|
|
636
|
+
`;
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Generate sample error file
|
|
640
|
+
*/
|
|
641
|
+
function generateSampleError(context) {
|
|
642
|
+
return `${context.detectionPattern} Example error message
|
|
643
|
+
${context.detectionPattern} Add real error output from your tool here
|
|
644
|
+
${context.detectionPattern} Replace this with actual error messages
|
|
645
|
+
|
|
646
|
+
Replace this file with real error output from your tool.
|
|
647
|
+
This helps with testing and validation.
|
|
648
|
+
`;
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Convert kebab-case to PascalCase
|
|
652
|
+
*/
|
|
653
|
+
function kebabToPascalCase(str) {
|
|
654
|
+
return str
|
|
655
|
+
.split('-')
|
|
656
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
657
|
+
.join('');
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Convert kebab-case to Title Case
|
|
661
|
+
*/
|
|
662
|
+
function kebabToTitleCase(str) {
|
|
663
|
+
return str
|
|
664
|
+
.split('-')
|
|
665
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
666
|
+
.join(' ');
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Show verbose help with detailed documentation
|
|
670
|
+
*/
|
|
671
|
+
export function showCreateExtractorVerboseHelp() {
|
|
672
|
+
console.log(`# create-extractor Command Reference
|
|
673
|
+
|
|
674
|
+
> Create a new extractor plugin from template
|
|
675
|
+
|
|
676
|
+
## Overview
|
|
677
|
+
|
|
678
|
+
The \`create-extractor\` command generates a fully-functional extractor plugin directory with:
|
|
679
|
+
- TypeScript source code with detect() and extract() functions
|
|
680
|
+
- Test suite using Vitest
|
|
681
|
+
- Sample error data
|
|
682
|
+
- Documentation (README.md, CLAUDE.md)
|
|
683
|
+
- Package.json with proper dependencies
|
|
684
|
+
- TypeScript configuration
|
|
685
|
+
|
|
686
|
+
## How It Works
|
|
687
|
+
|
|
688
|
+
1. **Prompts for plugin metadata** (name, description, author, etc.)
|
|
689
|
+
2. **Generates plugin directory** with all necessary files
|
|
690
|
+
3. **Creates sample test data** directory
|
|
691
|
+
4. **Outputs next steps** for development
|
|
692
|
+
|
|
693
|
+
## Options
|
|
694
|
+
|
|
695
|
+
- \`[name]\` - Plugin name (kebab-case, e.g., "my-tool")
|
|
696
|
+
- \`--description <desc>\` - Plugin description
|
|
697
|
+
- \`--author <author>\` - Author name and email
|
|
698
|
+
- \`--detection-pattern <pattern>\` - Detection keyword or pattern
|
|
699
|
+
- \`--priority <number>\` - Detection priority (default: 70)
|
|
700
|
+
- \`-f, --force\` - Overwrite existing plugin directory
|
|
701
|
+
|
|
702
|
+
## Exit Codes
|
|
703
|
+
|
|
704
|
+
- \`0\` - Plugin created successfully
|
|
705
|
+
- \`1\` - Failed (directory exists without --force, or invalid input)
|
|
706
|
+
|
|
707
|
+
## Files Created
|
|
708
|
+
|
|
709
|
+
- \`vibe-validate-plugin-<name>/\`
|
|
710
|
+
- \`index.ts\` - Main plugin code
|
|
711
|
+
- \`index.test.ts\` - Test suite
|
|
712
|
+
- \`README.md\` - User documentation
|
|
713
|
+
- \`CLAUDE.md\` - AI assistant guidance
|
|
714
|
+
- \`package.json\` - NPM package metadata
|
|
715
|
+
- \`tsconfig.json\` - TypeScript configuration
|
|
716
|
+
- \`samples/sample-error.txt\` - Example error output
|
|
717
|
+
|
|
718
|
+
## Examples
|
|
719
|
+
|
|
720
|
+
\`\`\`bash
|
|
721
|
+
# Interactive mode (prompts for all info)
|
|
722
|
+
vibe-validate create-extractor
|
|
723
|
+
|
|
724
|
+
# With plugin name specified
|
|
725
|
+
vibe-validate create-extractor my-tool
|
|
726
|
+
|
|
727
|
+
# Non-interactive (all options provided)
|
|
728
|
+
vibe-validate create-extractor my-tool \\
|
|
729
|
+
--description "Extracts errors from my tool" \\
|
|
730
|
+
--author "John Doe <john@example.com>" \\
|
|
731
|
+
--detection-pattern "ERROR:" \\
|
|
732
|
+
--priority 70
|
|
733
|
+
|
|
734
|
+
# Overwrite existing plugin
|
|
735
|
+
vibe-validate create-extractor my-tool --force
|
|
736
|
+
\`\`\`
|
|
737
|
+
|
|
738
|
+
## Development Workflow
|
|
739
|
+
|
|
740
|
+
\`\`\`bash
|
|
741
|
+
# 1. Create plugin
|
|
742
|
+
vibe-validate create-extractor my-tool
|
|
743
|
+
|
|
744
|
+
# 2. Navigate to plugin directory
|
|
745
|
+
cd vibe-validate-plugin-my-tool
|
|
746
|
+
|
|
747
|
+
# 3. Install dependencies
|
|
748
|
+
npm install
|
|
749
|
+
|
|
750
|
+
# 4. Add real error output to samples/
|
|
751
|
+
# Copy actual error output from your tool
|
|
752
|
+
|
|
753
|
+
# 5. Implement detect() and extract() functions
|
|
754
|
+
# Edit index.ts with your detection and extraction logic
|
|
755
|
+
|
|
756
|
+
# 6. Run tests
|
|
757
|
+
npm test
|
|
758
|
+
|
|
759
|
+
# 7. Test with vibe-validate
|
|
760
|
+
vibe-validate test-extractor .
|
|
761
|
+
|
|
762
|
+
# 8. Publish (optional)
|
|
763
|
+
npm publish
|
|
764
|
+
\`\`\`
|
|
765
|
+
|
|
766
|
+
## Plugin Structure
|
|
767
|
+
|
|
768
|
+
Generated plugins follow the vibe-validate plugin architecture:
|
|
769
|
+
|
|
770
|
+
- **ExtractorPlugin interface** with metadata, hints, priority, detect(), extract()
|
|
771
|
+
- **No file I/O** - safe for sandboxed execution
|
|
772
|
+
- **Fast hints** - string.includes() checks for efficient filtering
|
|
773
|
+
- **Samples** - co-located test data for validation
|
|
774
|
+
|
|
775
|
+
## Next Steps After Creation
|
|
776
|
+
|
|
777
|
+
1. **Replace sample-error.txt** with real error output from your tool
|
|
778
|
+
2. **Implement detect()** function to identify your tool's output
|
|
779
|
+
3. **Implement extract()** function to parse errors
|
|
780
|
+
4. **Run tests** to validate functionality
|
|
781
|
+
5. **Test with vibe-validate** using \`test-extractor\` command
|
|
782
|
+
6. **Publish to npm** (optional) or use locally
|
|
783
|
+
|
|
784
|
+
## Related Commands
|
|
785
|
+
|
|
786
|
+
- \`vibe-validate fork-extractor <name>\` - Copy built-in extractor for customization
|
|
787
|
+
- \`vibe-validate test-extractor <path>\` - Validate plugin functionality and security
|
|
788
|
+
`);
|
|
789
|
+
}
|
|
790
|
+
//# sourceMappingURL=create-extractor.js.map
|