@vibe-validate/cli 0.16.0 → 0.17.0-rc.5

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.
@@ -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