vibe-validate 0.17.0 → 0.17.1-rc.2

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,742 @@
1
+ # Error Extractors Guide
2
+
3
+ Learn how vibe-validate formats error output for optimal human and AI assistant readability.
4
+
5
+ ## Table of Contents
6
+
7
+ - [What are Error Extractors?](#what-are-error-extractors)
8
+ - [Supported Tools](#supported-tools)
9
+ - [How Formatting Works](#how-formatting-works)
10
+ - [Extractor Details](#extractor-details)
11
+ - [Agent-Friendly Output](#agent-friendly-output)
12
+ - [Custom Extractors](#custom-extractors)
13
+ - [Troubleshooting](#troubleshooting)
14
+
15
+ ## What are Error Extractors?
16
+
17
+ Error extractors parse validation output and extract actionable error information for humans and AI assistants.
18
+
19
+ **Purpose:**
20
+ - **Noise Reduction**: Extract only relevant error information
21
+ - **Actionability**: Provide clear next steps for fixing issues
22
+ - **Agent Optimization**: Format errors for AI assistant consumption
23
+ - **Consistency**: Uniform error format across all tools
24
+
25
+ **Benefits:**
26
+ - 📊 **Concise Output** - Only show what matters
27
+ - 🎯 **File:Line Context** - Jump directly to error locations
28
+ - 🤖 **AI-Friendly** - Optimized for Claude Code, Cursor, etc.
29
+ - 🔧 **Tool-Specific** - Understand each tool's error format
30
+
31
+ ## Supported Tools
32
+
33
+ vibe-validate includes extractors for common development tools:
34
+
35
+ | Tool | Extractor | Detection |
36
+ |------|-----------|-----------|
37
+ | TypeScript (`tsc`) | `typescript-extractor` | Step name contains "TypeScript" or "tsc" |
38
+ | ESLint | `eslint-extractor` | Step name contains "ESLint" or "eslint" |
39
+ | Vitest | `vitest-extractor` | Step name contains "Vitest", "test", "Test" |
40
+ | Jest | `vitest-extractor` | Step name contains "Jest" or "jest" |
41
+ | OpenAPI | `openapi-extractor` | Step name contains "OpenAPI" or command contains "swagger" |
42
+ | Generic | `generic-extractor` | Fallback for unknown tools |
43
+
44
+ **Auto-Detection**: vibe-validate automatically selects the appropriate extractor based on step name or command.
45
+
46
+ ## How Formatting Works
47
+
48
+ ### 1. Command Execution
49
+
50
+ vibe-validate runs your validation command and captures stdout/stderr:
51
+
52
+ ```bash
53
+ tsc --noEmit
54
+ ```
55
+
56
+ **Raw Output (verbose):**
57
+ ```
58
+ src/index.ts:42:5 - error TS2322: Type 'string' is not assignable to type 'number'.
59
+
60
+ 42 count: "five",
61
+ ~~~~~
62
+
63
+ src/types.ts:12:3
64
+ 12 count: number;
65
+ ~~~~~
66
+ The expected type comes from property 'count' which is declared here on type 'Config'
67
+
68
+
69
+ Found 1 error in src/index.ts:42
70
+ ```
71
+
72
+ ### 2. Error Parsing
73
+
74
+ The TypeScript extractor extracts:
75
+ - File path: `src/index.ts`
76
+ - Line number: `42`
77
+ - Column: `5`
78
+ - Error code: `TS2322`
79
+ - Message: `Type 'string' is not assignable to type 'number'`
80
+
81
+ ### 3. Formatted Output
82
+
83
+ **Human-readable:**
84
+ ```
85
+ ❌ TypeScript
86
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
87
+ src/index.ts:42:5 - error TS2322
88
+ Type 'string' is not assignable to type 'number'
89
+ ```
90
+
91
+ **Agent-friendly (YAML):**
92
+ <!-- validation-result:partial -->
93
+ ```yaml
94
+ passed: false
95
+ phases:
96
+ - name: Pre-Qualification
97
+ passed: false
98
+ steps:
99
+ - name: TypeScript
100
+ passed: false
101
+ command: tsc --noEmit
102
+ extraction:
103
+ errors:
104
+ - file: src/index.ts
105
+ line: 42
106
+ column: 5
107
+ message: "error TS2322: Type 'string' is not assignable to type 'number'"
108
+ summary: 1 error found
109
+ totalErrors: 1
110
+ ```
111
+
112
+ ## Extractor Details
113
+
114
+ ### TypeScript Extractor
115
+
116
+ **Handles:** `tsc --noEmit` output
117
+
118
+ **Extracts:**
119
+ - File path and location (file:line:column)
120
+ - Error code (e.g., TS2322, TS2345)
121
+ - Error message
122
+ - Context from related locations
123
+
124
+ **Example:**
125
+ ```
126
+ Input:
127
+ src/index.ts:42:5 - error TS2322: Type 'string' is not assignable to type 'number'.
128
+
129
+ Output:
130
+ src/index.ts:42:5 - error TS2322
131
+ Type 'string' is not assignable to type 'number'
132
+ ```
133
+
134
+ **Line Pattern:**
135
+ ```regex
136
+ /^(.+?)\((\d+),(\d+)\): error TS(\d+):/
137
+ /^(.+?):(\d+):(\d+) - error TS(\d+):/
138
+ ```
139
+
140
+ ---
141
+
142
+ ### ESLint Extractor
143
+
144
+ **Handles:** `eslint` output (all formats)
145
+
146
+ **Extracts:**
147
+ - File path and location (file:line:column)
148
+ - Rule name (e.g., no-unused-vars, @typescript-eslint/no-explicit-any)
149
+ - Error message
150
+ - Severity (error/warning)
151
+
152
+ **Example:**
153
+ ```
154
+ Input:
155
+ /path/to/src/index.ts
156
+ 42:5 error 'count' is defined but never used no-unused-vars
157
+
158
+ Output:
159
+ src/index.ts:42:5 - error no-unused-vars
160
+ 'count' is defined but never used
161
+ ```
162
+
163
+ **Line Pattern:**
164
+ ```regex
165
+ /^\s*(\d+):(\d+)\s+(error|warning)\s+(.+?)\s+([\w\/-@]+)$/
166
+ ```
167
+
168
+ ---
169
+
170
+ ### Vitest/Jest Extractor
171
+
172
+ **Handles:** Vitest and Jest test output
173
+
174
+ **Extracts:**
175
+ - Test file path
176
+ - Test name (suite + test description)
177
+ - Failure message
178
+ - Expected vs. received values
179
+ - Error location (file:line)
180
+
181
+ **Example:**
182
+ ```
183
+ Input:
184
+ ❯ src/index.test.ts > User validation > should reject invalid email
185
+ AssertionError: expected 'invalid' to equal 'test@example.com'
186
+ ❯ src/index.test.ts:42:5
187
+
188
+ Output:
189
+ src/index.test.ts:42:5
190
+ Test: User validation > should reject invalid email
191
+ AssertionError: expected 'invalid' to equal 'test@example.com'
192
+ ```
193
+
194
+ **Patterns:**
195
+ - Test failure: `❯ test/file.test.ts > Suite > Test Name`
196
+ - Assertion error: `AssertionError: ...`
197
+ - Location: `❯ file.ts:line:column`
198
+
199
+ ---
200
+
201
+ ### OpenAPI Extractor
202
+
203
+ **Handles:** OpenAPI/Swagger validation output
204
+
205
+ **Extracts:**
206
+ - Schema path
207
+ - Validation error
208
+ - Expected vs. actual values
209
+
210
+ **Example:**
211
+ ```
212
+ Input:
213
+ ✖ validation error in paths./api/users.get.responses.200
214
+ expected object but got array
215
+
216
+ Output:
217
+ paths./api/users.get.responses.200
218
+ Expected object but got array
219
+ ```
220
+
221
+ **Line Pattern:**
222
+ ```regex
223
+ /validation error in (.+)/
224
+ ```
225
+
226
+ ---
227
+
228
+ ### Generic Extractor
229
+
230
+ **Handles:** Unknown tools (fallback)
231
+
232
+ **Extracts:**
233
+ - Error and warning lines
234
+ - File paths with line numbers
235
+ - ANSI color code removal
236
+ - Noise filtering (progress bars, timestamps, etc.)
237
+
238
+ **Example:**
239
+ ```
240
+ Input:
241
+ [32m✔[0m Building...
242
+ [31m✖[0m Error: Failed to compile
243
+ src/index.ts:42:5: Syntax error
244
+
245
+ Output:
246
+ Error: Failed to compile
247
+ src/index.ts:42:5: Syntax error
248
+ ```
249
+
250
+ **Features:**
251
+ - ANSI color removal
252
+ - Progress indicator filtering
253
+ - Timestamp removal
254
+ - Empty line collapsing
255
+
256
+ ---
257
+
258
+ ## Agent-Friendly Output
259
+
260
+ vibe-validate optimizes error output for AI assistants like Claude Code, Cursor, Aider, and Continue.
261
+
262
+ ### Detection
263
+
264
+ Automatically detects agent context:
265
+
266
+ ```typescript
267
+ if (process.env.CLAUDE_CODE === '1') {
268
+ // Use YAML format
269
+ }
270
+ if (process.env.CURSOR === '1') {
271
+ // Use YAML format
272
+ }
273
+ if (process.env.CI === 'true') {
274
+ // Use YAML format
275
+ }
276
+ ```
277
+
278
+ ### YAML Output Format
279
+
280
+ **Structure:**
281
+ <!-- validation-result:example -->
282
+ ```yaml
283
+ passed: false
284
+ timestamp: 2025-10-16T15:30:00.000Z
285
+ treeHash: a1b2c3d4e5f6789abc123def456
286
+ summary: "TypeScript type check failed"
287
+ failedStep: TypeScript
288
+ phases:
289
+ - name: "Pre-Qualification"
290
+ passed: false
291
+ durationSecs: 3.8
292
+ steps:
293
+ - name: "TypeScript"
294
+ command: "pnpm typecheck"
295
+ exitCode: 1
296
+ durationSecs: 3.8
297
+ passed: false
298
+ extraction:
299
+ errors:
300
+ - file: src/index.ts
301
+ line: 42
302
+ column: 5
303
+ message: "error TS2322: Type 'string' is not assignable to type 'number'"
304
+ - file: src/auth.ts
305
+ line: 128
306
+ column: 10
307
+ message: "error TS2345: Argument of type 'null' is not assignable to parameter of type 'User'"
308
+ summary: "2 type errors"
309
+ totalErrors: 2
310
+ ```
311
+
312
+ ### Why YAML?
313
+
314
+ **Benefits for AI assistants:**
315
+ - **Structured data** - Easy to parse programmatically
316
+ - **Embedded output** - Error details included in validation state (no separate log files)
317
+ - **No ambiguity** - Clear field boundaries (no color codes)
318
+ - **Cacheable** - Stored in git notes (access via `vibe-validate state`)
319
+
320
+ ### Using State File in Agent Workflows
321
+
322
+ **Example usage in Claude Code:**
323
+ ```bash
324
+ # Check validation status
325
+ vibe-validate validate --check
326
+
327
+ # View state file with formatted errors
328
+ vibe-validate state
329
+
330
+ # Claude Code reads extraction.errors and suggests fixes
331
+ ```
332
+
333
+ ---
334
+
335
+ ## Custom Extractors
336
+
337
+ You can create custom extractors for tools not supported by default.
338
+
339
+ ### Quick Start: Generate Extractor Plugin
340
+
341
+ **RECOMMENDED**: Use the built-in scaffolding tool to create a plugin:
342
+
343
+ ```bash
344
+ # Create extractor plugin with scaffolding
345
+ vv create-extractor my-tool \
346
+ --description "Extracts errors from my-tool output" \
347
+ --author "Your Name <you@example.com>" \
348
+ --detection-pattern "ERROR:"
349
+ ```
350
+
351
+ This creates a plugin directory `vibe-validate-plugin-my-tool/` with:
352
+ - TypeScript plugin template with detection and extraction logic
353
+ - Example detection pattern configured
354
+ - package.json with proper metadata
355
+ - tsconfig.json for TypeScript support
356
+ - README.md with usage instructions
357
+
358
+ **Generated plugin structure:**
359
+ ```
360
+ vibe-validate-plugin-my-tool/
361
+ ├── package.json
362
+ ├── tsconfig.json
363
+ ├── index.ts # Your extractor implementation
364
+ └── README.md
365
+ ```
366
+
367
+ **Next steps:**
368
+ 1. **Build the plugin**: `cd vibe-validate-plugin-my-tool && npm run build`
369
+ 2. **Edit `index.ts`**: Customize detection and extraction logic
370
+ 3. **Test it**: `vv run <your-command>` (plugin auto-discovered)
371
+ 4. **Refine**: Iterate on detection patterns and extraction logic
372
+
373
+ ### How Plugin Discovery Works
374
+
375
+ Plugins in `vibe-validate-local-plugins/` or matching the naming pattern `vibe-validate-plugin-*` in your project are automatically discovered and loaded.
376
+
377
+ **Manual plugin location:**
378
+ ```bash
379
+ # Move built plugin to auto-discovery directory
380
+ mkdir -p vibe-validate-local-plugins
381
+ mv vibe-validate-plugin-my-tool vibe-validate-local-plugins/
382
+ ```
383
+
384
+ **Plugin registration in config (optional):**
385
+ ```yaml
386
+ # vibe-validate.config.yaml
387
+ git:
388
+ mainBranch: main
389
+
390
+ extractors:
391
+ # Explicitly register plugins (alternative to auto-discovery)
392
+ - path: ./vibe-validate-local-plugins/vibe-validate-plugin-my-tool
393
+ trust: sandbox # Run in sandbox (default)
394
+
395
+ validation:
396
+ phases:
397
+ - name: Build
398
+ steps:
399
+ - name: My Tool
400
+ command: my-tool build
401
+ # Extractor auto-detects based on detection pattern
402
+ ```
403
+
404
+ ### Manual Implementation (Advanced)
405
+
406
+ If you need fine-grained control, you can manually implement the extractor interface:
407
+
408
+ #### Step 1: Implement Extractor Plugin
409
+
410
+ ```typescript
411
+ // vibe-validate-plugin-my-tool/index.ts
412
+ import type { ExtractorPlugin } from '@vibe-validate/extractors';
413
+
414
+ const plugin: ExtractorPlugin = {
415
+ // Detection function
416
+ detect(output: string) {
417
+ const hasErrorMarker = output.includes('ERROR:');
418
+ if (hasErrorMarker) {
419
+ return {
420
+ confidence: 90,
421
+ hints: {
422
+ required: ['ERROR:'],
423
+ optional: [],
424
+ },
425
+ };
426
+ }
427
+ return { confidence: 0, hints: { required: [], optional: [] } };
428
+ },
429
+
430
+ // Extraction function
431
+ extract(output: string) {
432
+ const lines = output.split('\n');
433
+ const errors: string[] = [];
434
+
435
+ for (const line of lines) {
436
+ const match = line.match(/ERROR: (.+)/);
437
+ if (match) {
438
+ errors.push(match[1]);
439
+ }
440
+ }
441
+
442
+ return {
443
+ summary: `Found ${errors.length} errors`,
444
+ details: errors.join('\n'),
445
+ errorCount: errors.length,
446
+ errors: errors.map(msg => ({ message: msg })),
447
+ };
448
+ },
449
+ };
450
+
451
+ export default plugin;
452
+ ```
453
+
454
+ #### Step 2: Build and Test
455
+
456
+ ```bash
457
+ # Build TypeScript plugin
458
+ cd vibe-validate-plugin-my-tool
459
+ npm run build
460
+
461
+ # Test with real output
462
+ cd /path/to/your/project
463
+ vv run my-tool build
464
+ ```
465
+
466
+ #### Step 3: Verify Detection
467
+
468
+ Check that your plugin was loaded and used:
469
+
470
+ ```bash
471
+ # Check extraction metadata in state
472
+ vv state
473
+
474
+ # Look for:
475
+ # metadata:
476
+ # detection:
477
+ # extractor: my-tool # Your plugin name
478
+ # confidence: 90
479
+ ```
480
+
481
+ ### Extractor Interface
482
+
483
+ ```typescript
484
+ export interface FormattedError {
485
+ summary: string; // One-line summary
486
+ details: string; // Detailed error output
487
+ errorCount: number; // Number of errors found
488
+ hints?: string[]; // Optional fix suggestions
489
+ }
490
+ ```
491
+
492
+ ### Example: Custom Extractor for Docker Build
493
+
494
+ ```typescript
495
+ import { type FormattedError } from '@vibe-validate/extractors';
496
+
497
+ export function formatDockerBuild(output: string): FormattedError {
498
+ const lines = output.split('\n');
499
+ const errors: string[] = [];
500
+
501
+ let inErrorSection = false;
502
+
503
+ for (const line of lines) {
504
+ if (line.includes('ERROR [')) {
505
+ inErrorSection = true;
506
+ errors.push(line);
507
+ } else if (inErrorSection && line.trim()) {
508
+ errors.push(line);
509
+ } else if (line.startsWith('----')) {
510
+ inErrorSection = false;
511
+ }
512
+ }
513
+
514
+ return {
515
+ summary: `Docker build failed with ${errors.length} errors`,
516
+ details: errors.join('\n'),
517
+ errorCount: errors.length,
518
+ hints: [
519
+ 'Check Dockerfile syntax',
520
+ 'Verify base image exists',
521
+ 'Ensure dependencies are available',
522
+ ],
523
+ };
524
+ }
525
+ ```
526
+
527
+ ---
528
+
529
+ ## Troubleshooting
530
+
531
+ ### "Extractor not working for my tool"
532
+
533
+ **Cause**: Tool not detected or tool outputs non-standard format.
534
+
535
+ **Solution 1**: Rename step to include tool name:
536
+ ```typescript
537
+ steps: [
538
+ { name: 'TypeScript Check', command: 'tsc --noEmit' }, // ✅ Detected
539
+ { name: 'Check Types', command: 'tsc --noEmit' }, // ❌ Not detected
540
+ ]
541
+ ```
542
+
543
+ **Solution 2**: Create custom extractor (see above).
544
+
545
+ ### "Too much noise in error output"
546
+
547
+ **Cause**: Generic extractor doesn't filter tool-specific noise.
548
+
549
+ **Solution**: Use tool-specific extractor by naming step appropriately:
550
+ ```typescript
551
+ steps: [
552
+ { name: 'Tests', command: 'npm test' }, // ❌ Generic extractor
553
+ { name: 'Vitest Tests', command: 'npm test' }, // ✅ Vitest extractor
554
+ ]
555
+ ```
556
+
557
+ ### "Error output truncated"
558
+
559
+ **Cause**: Output exceeds maximum length (default: 5000 characters).
560
+
561
+ **Solution**: Errors are intentionally limited for readability. Fix the first few errors, then re-run validation.
562
+
563
+ **Best practice**: Fix errors incrementally:
564
+ ```bash
565
+ # Run validation
566
+ vibe-validate validate
567
+
568
+ # Fix first 1-2 errors
569
+ vim src/index.ts
570
+
571
+ # Re-run validation (much faster with caching)
572
+ vibe-validate validate
573
+ ```
574
+
575
+ ### "ANSI color codes in output"
576
+
577
+ **Cause**: Tool outputs colors even in non-TTY mode.
578
+
579
+ **Solution**: Disable colors in tool configuration:
580
+ ```typescript
581
+ steps: [
582
+ { name: 'ESLint', command: 'eslint --no-color src/' },
583
+ { name: 'Vitest', command: 'vitest run --reporter=basic' },
584
+ ]
585
+ ```
586
+
587
+ Or set `NO_COLOR` environment variable:
588
+ ```bash
589
+ NO_COLOR=1 vibe-validate validate
590
+ ```
591
+
592
+ ### "Agent prompt not helpful"
593
+
594
+ **Cause**: Generic extractor or insufficient error context.
595
+
596
+ **Solution**: Use tool-specific extractor for better error extraction.
597
+
598
+ ---
599
+
600
+ ## Extractor Best Practices
601
+
602
+ ### 1. Name Steps Clearly
603
+
604
+ Use descriptive names that match extractor detection:
605
+
606
+ **Good:**
607
+ ```typescript
608
+ steps: [
609
+ { name: 'TypeScript', command: 'tsc --noEmit' },
610
+ { name: 'ESLint', command: 'eslint src/' },
611
+ { name: 'Vitest Unit Tests', command: 'vitest run' },
612
+ ]
613
+ ```
614
+
615
+ **Less Good:**
616
+ ```typescript
617
+ steps: [
618
+ { name: 'Types', command: 'tsc --noEmit' }, // May not detect
619
+ { name: 'Lint', command: 'eslint src/' }, // May not detect
620
+ { name: 'Tests', command: 'vitest run' }, // May detect generically
621
+ ]
622
+ ```
623
+
624
+ ### 2. Use Stable Output Formats
625
+
626
+ Configure tools for consistent, parseable output:
627
+
628
+ ```typescript
629
+ steps: [
630
+ { name: 'TypeScript', command: 'tsc --noEmit --pretty false' },
631
+ { name: 'ESLint', command: 'eslint --format=stylish src/' },
632
+ { name: 'Vitest', command: 'vitest run --reporter=verbose' },
633
+ ]
634
+ ```
635
+
636
+ ### 3. Disable Progress Indicators
637
+
638
+ Progress bars and spinners add noise:
639
+
640
+ ```typescript
641
+ steps: [
642
+ { name: 'Build', command: 'npm run build --silent' },
643
+ { name: 'Tests', command: 'vitest run --reporter=basic' },
644
+ ]
645
+ ```
646
+
647
+ ### 4. Test Extractor Output
648
+
649
+ Verify extractor works as expected:
650
+
651
+ ```bash
652
+ # Force validation to fail
653
+ vibe-validate validate --force
654
+
655
+ # Check formatted output
656
+ vibe-validate state
657
+ ```
658
+
659
+ ### 5. Contribute Extractors
660
+
661
+ If you create a useful extractor, consider contributing it to vibe-validate:
662
+
663
+ 1. Fork the repository
664
+ 2. Add extractor to `packages/extractors/src/`
665
+ 3. Add tests to `packages/extractors/test/`
666
+ 4. Update detection logic in `smart-extractor.ts`
667
+ 5. Submit pull request
668
+
669
+ ---
670
+
671
+ ## Advanced Features
672
+
673
+ ### Error Line Extraction
674
+
675
+ Extractors extract only error lines, removing:
676
+ - Progress bars and spinners
677
+ - Timestamps and metadata
678
+ - Success messages
679
+ - ANSI color codes
680
+ - Empty lines
681
+
682
+ **Example:**
683
+ ```
684
+ Input:
685
+ [32m✔[0m Building... (1234ms)
686
+ [31m✖[0m TypeScript
687
+ src/index.ts:42:5 - error TS2322
688
+ [2K
689
+
690
+ Output:
691
+ src/index.ts:42:5 - error TS2322
692
+ ```
693
+
694
+ ### Context Preservation
695
+
696
+ Some extractors preserve important context:
697
+
698
+ **TypeScript:**
699
+ ```
700
+ src/index.ts:42:5 - error TS2322
701
+ Type 'string' is not assignable to type 'number'
702
+
703
+ src/types.ts:12:3
704
+ The expected type comes from property 'count'
705
+ ```
706
+
707
+ **Vitest:**
708
+ ```
709
+ src/index.test.ts:42:5
710
+ Test: User validation > should reject invalid email
711
+ Expected: test@example.com
712
+ Received: invalid
713
+ ```
714
+
715
+ ### Multi-Error Aggregation
716
+
717
+ Extractors aggregate multiple errors:
718
+
719
+ ```
720
+ TypeScript (3 errors):
721
+ 1. src/index.ts:42:5 - TS2322
722
+ 2. src/auth.ts:128:10 - TS2345
723
+ 3. src/utils.ts:56:12 - TS2339
724
+ ```
725
+
726
+ ---
727
+
728
+ ## Related Documentation
729
+
730
+ - [Getting Started Guide](./getting-started.md)
731
+ - [Configuration Reference](./configuration-reference.md)
732
+ - [CLI Reference](./cli-reference.md)
733
+ - [Config Templates Guide](./../config-templates/README.md)
734
+ - [Agent Integration Guide](./agent-integration-guide.md)
735
+
736
+ ---
737
+
738
+ ## See Also
739
+
740
+ - [Extractor Source Code](https://github.com/yourusername/vibe-validate/tree/main/packages/extractors)
741
+ - [Extractor Tests](https://github.com/yourusername/vibe-validate/tree/main/packages/extractors/test)
742
+ - [Contributing Guide](https://github.com/yourusername/vibe-validate/blob/main/CONTRIBUTING.md)