Creating Rules
Learn how to create custom validation rules for your project.
Basic Rule Structure
Use defineCodeRule() to create a validation rule:
import { defineCodeRule } from "@syncrolabs/claude-code-validator";
export const myRule = defineCodeRule({
name: "my-rule",
description: "Description of what this rule checks",
shouldRun: (context) => {
// Determine if rule should run
return context.filePath.endsWith(".ts");
},
validate(context) {
const errors: string[] = [];
// Check for patterns
if (context.content.includes("bad-pattern")) {
errors.push("â Error message here");
}
return errors;
},
});
Pattern-Based Rules
For simple regex-based validation, define patterns first:
import { defineCodeRule } from "@syncrolabs/claude-code-validator";
import type { PatternRule } from "@syncrolabs/claude-code-validator";
const patterns: PatternRule[] = [
{
regex: /OldAPI/,
message: "â OldAPI is deprecated",
replacement: "NewAPI",
},
{
regex: /dangerousFunction/,
message: "â dangerousFunction is not allowed",
replacement: "safeFunction",
},
];
export const patternRule = defineCodeRule({
name: "pattern-rule",
description: "Checks for deprecated patterns",
shouldRun: (context) => /\.(ts|js)$/.test(context.filePath),
validate(context) {
const errors: string[] = [];
for (const { regex, message, replacement } of patterns) {
if (regex.test(context.content)) {
let error = message;
if (replacement) {
error += `\n â Use: ${replacement}`;
}
error += `\n đ File: ${context.filePath}`;
errors.push(error);
}
}
return errors;
},
});
Component-Based Rules
Check for specific component usage:
import { defineCodeRule } from "@syncrolabs/claude-code-validator";
const DEPRECATED_COMPONENTS = ["OldButton", "OldInput", "OldForm"];
export const componentRule = defineCodeRule({
name: "component-rule",
description: "Prevents usage of deprecated components",
shouldRun: (context) => /\.(vue|jsx|tsx)$/.test(context.filePath),
validate(context) {
const errors: string[] = [];
for (const component of DEPRECATED_COMPONENTS) {
const regex = new RegExp(`<${component}[\\s>]`);
if (regex.test(context.content)) {
errors.push(
`â <${component}> is deprecated\n` +
` â Use the new component library instead\n` +
` đ File: ${context.filePath}`
);
}
}
return errors;
},
});
Prop Validation Rules
Validate component props:
import { defineCodeRule } from "@syncrolabs/claude-code-validator";
const VALID_COLORS = ["primary", "secondary", "error", "success", "warning"];
export const propRule = defineCodeRule({
name: "prop-rule",
description: "Validates color prop values",
shouldRun: (context) => context.filePath.endsWith(".vue"),
validate(context) {
const errors: string[] = [];
// Match color="value" or :color="value"
const colorRegex = /:?color=["']([^"']+)["']/g;
let match;
while ((match = colorRegex.exec(context.content)) !== null) {
const color = match[1];
if (!VALID_COLORS.includes(color)) {
errors.push(
`â Invalid color "${color}"\n` +
` â Valid colors: ${VALID_COLORS.join(", ")}\n` +
` đ File: ${context.filePath}`
);
}
}
return errors;
},
});
API Pattern Rules
Enforce API usage patterns:
import { defineCodeRule } from "@syncrolabs/claude-code-validator";
export const apiRule = defineCodeRule({
name: "api-rule",
description: "Enforce use of defineModel() macro",
shouldRun: (context) => context.filePath.endsWith(".vue"),
validate(context) {
const errors: string[] = [];
// Check for old pattern
const hasModelValueProp = /defineProps[^}]*modelValue[^}]*}/s.test(
context.content
);
const hasUpdateEmit = /defineEmits[^}]*update:modelValue[^}]*}/s.test(
context.content
);
if (hasModelValueProp && hasUpdateEmit) {
errors.push(
`â Using modelValue prop with update:modelValue emit is outdated\n` +
` â Use the defineModel() macro instead\n` +
` âšī¸ Example: const model = defineModel<string>()\n` +
` đ File: ${context.filePath}`
);
}
return errors;
},
});
Async Rules
For rules that need async operations:
import { defineCodeRule } from "@syncrolabs/claude-code-validator";
export const asyncRule = defineCodeRule({
name: "async-rule",
description: "Performs async validation",
shouldRun: () => true,
async validate(context) {
const errors: string[] = [];
// Fetch external data
const bannedPatterns = await fetchBannedPatterns();
for (const pattern of bannedPatterns) {
if (context.content.includes(pattern)) {
errors.push(
`â Pattern "${pattern}" is not allowed\n` +
` đ File: ${context.filePath}`
);
}
}
return errors;
},
});
Multi-Rule Files
You can export multiple rules from a single file:
import { defineCodeRule } from "@syncrolabs/claude-code-validator";
export const ruleOne = defineCodeRule({
name: "rule-one",
description: "First rule",
shouldRun: () => true,
validate: () => [],
});
export const ruleTwo = defineCodeRule({
name: "rule-two",
description: "Second rule",
shouldRun: () => true,
validate: () => [],
});
// Both rules will be discovered and registered
Helper Functions
Create reusable validation helpers:
import { defineCodeRule } from "@syncrolabs/claude-code-validator";
// Helper function
function checkForPattern(
content: string,
pattern: RegExp,
message: string
): string | null {
if (pattern.test(content)) {
return message;
}
return null;
}
export const helperRule = defineCodeRule({
name: "helper-rule",
description: "Uses helper functions",
shouldRun: (context) => context.filePath.endsWith(".ts"),
validate(context) {
const errors: string[] = [];
const error1 = checkForPattern(
context.content,
/console\.log/,
"â console.log is not allowed"
);
const error2 = checkForPattern(
context.content,
/debugger/,
"â debugger statement is not allowed"
);
if (error1) errors.push(error1);
if (error2) errors.push(error2);
return errors;
},
});
File Organization
Organize rules by domain:
.claude/rules/
âââ vue/
â âââ components.ts # Component-related rules
â âââ props.ts # Prop validation rules
â âââ composables.ts # Composables rules
âââ typescript/
â âââ imports.ts # Import rules
â âââ types.ts # Type rules
âââ security/
âââ xss.ts # XSS prevention
âââ secrets.ts # Secret detection
All files are automatically discovered regardless of directory structure!
Testing Rules
Create tests alongside your rules:
// .claude/rules/tests/my-rule.test.ts
import { describe, it, expect } from "vitest";
import { myRule } from "../my-rule";
describe("My Rule", () => {
it("should detect bad pattern", () => {
const context = {
toolName: "Write",
filePath: "test.ts",
content: "bad-pattern here",
operation: "write" as const,
};
const errors = myRule.validate(context);
expect(errors).toHaveLength(1);
expect(errors[0]).toContain("bad-pattern");
});
it("should pass with good code", () => {
const context = {
toolName: "Write",
filePath: "test.ts",
content: "good-pattern here",
operation: "write" as const,
};
const errors = myRule.validate(context);
expect(errors).toHaveLength(0);
});
});