Custom Rules
vibecop supports custom rules defined in YAML files. Custom rules use ast-grep pattern syntax for AST-based matching, giving you the full power of tree-sitter pattern matching without writing TypeScript.
Directory Setup
Create a .vibecop/rules/ directory in your project root:
.vibecop/ rules/ no-console-error.yaml require-error-boundary.yamlThen reference it in .vibecop.yml:
custom-rules-dir: .vibecop/rulesIf custom-rules-dir is not set, vibecop looks for .vibecop/rules/ by default.
YAML Rule Format
Each .yaml or .yml file in the rules directory defines one rule:
id: no-console-errorname: No Console Errordescription: Disallow console.error in production codeseverity: warningcategory: qualitylanguages: - typescript - javascriptmessage: "console.error() found. Use a structured logger instead."suggestion: "Replace with logger.error() from your logging library."rule: pattern: console.error($$$ARGS)examples: valid: - "logger.error('something failed', error);" invalid: - "console.error('something failed', error);"Schema Reference
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique rule ID (lowercase, hyphens, starts with letter) |
name | string | Yes | Human-readable name |
description | string | Yes | What the rule detects |
severity | error / warning / info | Yes | Finding severity |
category | correctness / quality / security / testing | Yes | Rule category |
languages | array | Yes | Languages to check: javascript, typescript, tsx, python |
message | string | Yes | Message shown in findings |
suggestion | string | No | How to fix the issue |
rule | object | Yes | ast-grep rule object (pattern matching config) |
examples | object | No | Test cases for validation |
The id Field
Must match the regex ^[a-z][a-z0-9-]*$. Examples: no-console-error, require-auth-check, ban-eval.
The rule Field
The rule field is an ast-grep rule object. It supports the full ast-grep rule configuration syntax:
# Simple pattern matchrule: pattern: console.error($$$ARGS)
# Match with constraintsrule: pattern: fetch($URL) not: inside: pattern: try { $$$ } catch ($ERR) { $$$ }
# Match specific node kindsrule: kind: call_expression has: pattern: eval($ARG)See the ast-grep rule reference for the complete rule syntax.
The examples Field
Provide test cases to validate your rule:
examples: valid: - "logger.error('something failed');" - "console.log('debug info');" invalid: - "console.error('something failed');" - "console.error(error);"validexamples should not trigger the ruleinvalidexamples should trigger the rule
Testing Custom Rules
Run vibecop test-rules to validate your custom rules against their inline examples:
vibecop test-rulesOr specify a custom rules directory:
vibecop test-rules --rules-dir .vibecop/rulesOutput:
✓ no-console-error: 2 invalid examples matched, 2 valid examples clean✓ require-error-boundary: 1 invalid example matched, 1 valid example clean✗ ban-eval: invalid example 1 did not match (rule may be wrong)
2 rules passed, 1 rule failedThe command exits with code 1 if any rules fail, making it suitable for CI.
Example Rules
Ban setTimeout in Non-Test Files
id: no-settimeoutname: No setTimeoutdescription: Disallow setTimeout in production codeseverity: warningcategory: qualitylanguages: - typescript - javascriptmessage: "setTimeout() found. Use a proper scheduling mechanism."suggestion: "Use a task queue or cron job for delayed execution."rule: pattern: setTimeout($CALLBACK, $DELAY)examples: valid: - "scheduleTask(callback, delay);" invalid: - "setTimeout(() => { process(); }, 1000);"Require try/catch Around Fetch
id: unhandled-fetchname: Unhandled Fetchdescription: fetch() calls without error handlingseverity: warningcategory: correctnesslanguages: - typescript - javascriptmessage: "fetch() without try/catch. Network requests can fail."suggestion: "Wrap in try/catch or add .catch() handler."rule: pattern: await fetch($$$ARGS) not: inside: kind: try_statementexamples: invalid: - | async function getData() { const res = await fetch('/api/data'); return res.json(); }Ban any Type Annotation
id: no-any-typename: No Any Typedescription: Disallow explicit any type annotationsseverity: warningcategory: qualitylanguages: - typescript - tsxmessage: "Explicit 'any' type used. Provide a specific type."suggestion: "Replace with a proper type or use 'unknown' if the type is truly unknown."rule: pattern: "$NAME: any"examples: valid: - "const value: string = 'hello';" invalid: - "const value: any = getData();"How Custom Rules Work
- At startup, vibecop loads all
.yaml/.ymlfiles from the custom rules directory - Each file is parsed and validated against the
CustomRuleSchema(Zod) - Valid rules are converted to
Detectorobjects - During scanning, custom rule detectors run alongside built-in detectors
- Custom rules respect the same ignore patterns and severity overrides as built-in rules
Invalid rule files produce warnings but do not stop the scan — other rules and built-in detectors continue to run.