Skip to content
alex-dbsPublic

About

Define your policies as code, instead of markdown files that your AI agent will ignore. Enforce your checks using predictable code, or leverage Jev for tricky ones.

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Repository files navigation

CodeRuler shield logo

Tired of asking your AI agent to follow your conventions, only to watch it ignore them? Define your conventions as simple rules and let CodeRuler check your code against them, without forgetting one.

Install

CodeRuler requires Node.js 24.15+, or Node.js 26+. Git is required for check --diff and check --pr.

Install the CLI globally:

npm install -g @alex-dbs/coderuler
coderuler --help
coderuler skill install

The npm package is scoped. The command is coderuler. A global installation can load rules without a project-local dependency.

For a project-specific CLI version, install it locally:

npm install --save-dev @alex-dbs/coderuler
npx coderuler --help
npx coderuler skill install

The global installation is optional: with only the project-local dependency, run commands through npx coderuler.

The npm install commands require the first release to be published. To run an unreleased checkout, use npm ci, npm run build, then npm start -- --help.

Rule dependencies

Use ordinary JavaScript or TypeScript imports in rules. Add public npm packages without running npm install or changing application dependencies:

coderuler deps add bignumber.js@11.1.5
coderuler deps add bignumber.js@latest
coderuler deps remove bignumber.js
coderuler deps sync

deps add accepts names, versions, ranges, and tags.

Write a rule

Save rules in .coderuler (or any subfolders) with a name that ends in .rule.ts, .rule.mts, .rule.js, or .rule.mjs. A rule is a class that extends Rule and is the module's default export. Save the example below as .coderuler/no-any.rule.ts.

This rule reports explicit any types in TypeScript files.

import { Rule, scopes, test, violation } from '@alex-dbs/coderuler';

export default class NoAny extends Rule {
  files = ['src/**/*.{ts,tsx}']; // Which files?

  scope = scopes.astGrep({ // Which part of the file to check; here ast Grep
    language: 'typescript',
    rule: {
      kind: 'predefined_type',
      pattern: { context: 'type ExplicitAny = any;', selector: 'predefined_type' },
    },
  });

  // The check is simple: if something matches the scope, then it is a violation.
  check = () => violation('Replace explicit any with a specific type or unknown and narrow it.');
}

// You can also write tests here for better developer experience!
export const tests = [
  test('explicit any violates').withFiles({
    'src/example.ts': 'let value: any;',
  }).violates(),
  test('a specific type passes').withFiles({
    'src/example.ts': 'let value: string;',
  }).passes(),
];

Regular-expression rule

Use a regular expression for a pattern with a clear pass or fail condition. This rule selects google.api.http URLs from protobuf files and requires versioned, such as /v1/invoices.

import { Rule, regex, scopes } from '@alex-dbs/coderuler';

export default class PublicApiPaths extends Rule {
  files = ['api/**/*.proto'];

  scope = scopes.protobuf({ path: "$.rpcs[*].options['google.api.http']..['get','put','post','delete','patch','path']" });

  check = regex({
    pattern: /^\/v\d+\/[a-z][a-z0-9_]*s(?:\/.*)?$/,
    violationMessage: 'Use a versioned lowercase plural path.',
  });
}

AI rule

Use an AI rule when a policy needs meaning or context that a fixed pattern cannot capture. This rule sends one selected function to the AI decision API, Jev from Typesafe.

import { Rule, ai, scopes } from '@alex-dbs/coderuler';
import type { ScopeSubject } from '@alex-dbs/coderuler';

export default class SafeResponseHeaders extends Rule {
  files = ['src/**/*.js'];

  scope = scopes.astGrep({
    language: 'javascript',
    rule: { kind: 'function_declaration' },
  });

  check = ai({
    instruction: `Reject CR and LF in untrusted values before inserting them into
HTTP response headers or status lines.
This policy covers response construction and writes visible in the selected function. Hidden helper behavior and unseen callers are outside its scope.
Treat serialization arguments and statusMessage as untrusted. Fixed values and values checked for both CR and LF meet the requirement. Preserve fixed HTTP delimiters.`,
    input: subject => (subject as ScopeSubject).source,
    violationMessage: 'Reject CR and LF in request data before writing a response header.',
  });
}

This example shows an AI check for CWE-113.

AI rules require a configured service and can incur charges.

Tip

Keep the selected scope as small as the rule allows. A smaller input can reduce token costs and help the model focus on relevant code. Include enough context for the policy to make a sound decision.

Run validation, tests, and the rule against your project (use npx coderuler for a project-local installation):

coderuler validate no-any
coderuler test no-any
coderuler check . --rules no-any

Rule pipeline

A scope is a file or selected part of a file. CodeRuler validates selected rules, then applies this pipeline to each rule:

enabled → when → files and excludes → scope or select → filter → group → check → report

  • enabled defaults to true. Set it to false to disable a rule.
  • when(context) runs once for each enabled rule before file selection. It defaults to true. Return false to skip the rule, or return { applies: false, reason: '...' } to explain why. An example: apply a rule based on some conditions, for instance, the Go version of the project.
  • files is a nonempty list of root-relative globs. It defaults to ['**/*']. exclude is a list of root-relative globs that removes matching files. It defaults to [].
  • scope selects the parts of each file to evaluate. Without it, CodeRuler uses one whole-file scope. Use scopes.file(), scopes.json(), scopes.yaml(), scopes.csv(), scopes.protobuf(), or scopes.astGrep() to choose the source or data.
  • Override select(context) when you need custom file or scope selection. Call context.select({ files, exclude, scope }) to reuse built-in selection.
  • filter(subject, context) runs once per selected scope, before grouping. It defaults to true. Return false or { applies: false, reason: '...' } to skip a scope. Put applicability and required-data checks here (null check for instance).
  • group controls which scopes the evaluator sees together. It defaults to 'scope', which evaluates each scope separately. Set it to 'file', 'folder', 'project', or { by: 'folder', level }. folder groups scopes within each containing folder. project groups all selected scopes together. level is a nonpositive integer.
  • check is the required evaluator. Assign regex(...), ai(...), or a function as a property. Return pass() or violation(message) from a custom function.
  • comment optionally replaces each GitHub finding body for this rule when you use --auto-comment with check --diff or check --pr. Set a nonblank Markdown string, for example comment = 'Follow [our policy](https://example.com/policy).'. Without it, CodeRuler uses the generated body. Inline locations do not change.

The settings field sets AI options for one rule, such as its model, decision threshold, and input or output token prices.

Tip

Browse the .coderuler.examples folder for rule examples.

Commands

  • update upgrades the active global npm installation to the latest release. Update local dependencies with their package manager instead.
  • validate [rule] loads rules and validates their structure without running them.
  • test [rule] runs the inline tests declared by selected rules.
  • check [path] runs selected rules against a file or folder. Use --rules <rule> to select a rule name, module path, or directory. It exits with status 1 when a rule reports a violation.
  • inspect <rule> [folder] shows how one rule selects and evaluates subjects.
  • skill install and skill update install or refresh CodeRuler's rule-writing skill for coding agents.
  • deps add, deps remove, and deps sync manage locked rule packages and editor declarations.

For validate, test, and check --rules, a rule name is the exact filename stem of a discovered rule. Discovery searches .coderuler in the project and ~/.coderuler in your home directory, including subfolders. A name filters discovered rules; an explicit module path or directory replaces discovery. Use --help after a command for its options.

AI decisions

AI rules use Jev through Typesafe. Set TYPESAFE_API_KEY before running a rule that uses ai(). You can set TYPESAFE_BASE_URL and TYPESAFE_DEFAULT_MODEL to choose the service URL and model. See .env.example for the available environment variables and pricing defaults.

AI calls can send rule input to the configured service and can incur charges. The normal test command runs offline. Use test --ai to run cases declared as aiTests.

FAQ

Why not use a linter?

Use a linter for common syntax and style rules. Use CodeRuler for project policies that need custom code, structured scopes, or AI judgment. The tools work well together.

Why not use AGENTS.md?

AGENTS.md guides coding agents, but it does not enforce the instructions by itself. CodeRuler runs executable rules against project files and reports violations. Use both when you need instructions and enforcement.

Why not use a subagent?

A subagent can review code, but each review uses time and tokens. CodeRuler lets you save recurring policies as rules and run them when needed. Use a subagent for broad reviews or questions that do not fit a rule.

Why TypeScript or JavaScript?

Neither language is chosen for raw execution speed. Many developers already know one of them, and a rule can live in a single module. CodeRuler keeps work focused with file and scope selection, and it bounds concurrency. We replace LONG and COSTLY AI Agent verification checks by predictable code, running at the speed of less than 1ms per verification; way faster than any LLM. Having a faster tool is better of course, but the developer experience is more important than 0.3ms gain per rule 😉. Perhaps one day, the core will be re-written in rust... who knows!

Does CodeRuler support every language?

Rules are TypeScript or JavaScript modules, but they can inspect text files in any language. AST rules use the built-in and bundled ast-grep grammars. If a language has no grammar, write a custom selector or evaluator that reads its source text.

Can I do anything with a rule?

Rule modules are trusted Node.js code. You can write custom selectors, filters, and evaluators. When a simple code is not enough, you can rely on AI decision api. So yes, you can do whatever you like with CodeRuler. Keep in mind that a good rule MUST be auditable, easy to read, maintain and review.

Contributing

Before you contribute, install dependencies and run the project checks from the repository root:

npm ci
npm test
node dist/cli.js deps sync
npm run typecheck

Add or update tests for changes to rule behavior. The rule-writing skill explains how to create rules, add inline tests, and inspect rule behavior.

License

This project is licensed under the Apache License, Version 2.0. See LICENSE.

About

Define your policies as code, instead of markdown files that your AI agent will ignore. Enforce your checks using predictable code, or leverage Jev for tricky ones.

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages