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);
  });
});

Best Practices

Be specific - Clear error messages with actionable suggestions

Use shouldRun() - Don't waste time running rules on irrelevant files

Keep it simple - One rule per concern

Test thoroughly - Write tests for edge cases

Document patterns - Add comments explaining complex regex

Use helpers - Extract common validation logic

Format errors - Use consistent emoji and formatting

Provide examples - Include example fixes in error messages