Skip to content

Rules

Rules are the executable side of an ADR. They live in companion .rules.ts files alongside the ADR document and export a plain object typed with satisfies RuleSet. When you run archgate check, the CLI loads each ADR that has rules: true, imports its companion rules file, and executes every check against your codebase.

A rules file is a TypeScript module that default-exports a plain object conforming to the RuleSet type. The type is provided by the local shim auto-generated by archgate init (no npm install needed):

/// <reference path="../rules.d.ts" />
export default {
rules: {
"rule-key": {
description: "What this rule checks",
severity: "error",
async check(ctx) {
// Inspect files and report violations
},
},
},
} satisfies RuleSet;

Each key in the rules object becomes the rule ID. The full rule identifier shown in check output combines the ADR ID and the rule key, for example ARCH-004/no-barrel-files.

Every rule has three parts:

| Property | Type | Required | Description | | ------------- | -------- | -------- | --------------------------------------------- | | description | string | Yes | A short summary of what the rule checks | | severity | string | No | "error" (default), "warning", or "info" | | check | function | Yes | Async function receiving a RuleContext |

Severity determines what happens when a rule finds a problem:

| Severity | Exit Code | Effect | | --------- | --------- | ----------------------------------------- | | error | 1 | Violation is reported and the check fails | | warning | 0 | Warning is logged but the check passes | | info | 0 | Informational message, check passes |

When archgate check runs, exit code 1 means at least one error-severity violation was found. Exit code 0 means no errors (warnings and info messages are logged but do not block).

The check function receives a RuleContext object that provides everything a rule needs to inspect the codebase and report findings.

| Property | Type | Description | | ------------------ | ---------- | ---------------------------------------------------------------------------------- | | ctx.projectRoot | string | Absolute path to the project root directory | | ctx.scopedFiles | string[] | Files matching the ADR’s files globs, or all project files if no globs are set | | ctx.changedFiles | string[] | Files changed in git (auto-detected from branch diff, or from --staged/--base) |

| Method | Returns | Description | | -------------------- | ------------------- | ---------------------------------- | | ctx.glob(pattern) | Promise<string[]> | Find files matching a glob pattern | | ctx.readFile(path) | Promise<string> | Read a file’s content as a string | | ctx.readJSON(path) | Promise<unknown> | Read and parse a JSON file |

| Method | Returns | Description | | ---------------------------------- | ---------------------- | -------------------------------------------- | | ctx.grep(file, pattern) | Promise<GrepMatch[]> | Search a single file with a regex pattern | | ctx.grepFiles(pattern, fileGlob) | Promise<GrepMatch[]> | Search across multiple files matching a glob |

Both grep and grepFiles return an array of GrepMatch objects:

interface GrepMatch {
file: string; // Relative path from project root
line: number; // 1-based line number
column: number; // 1-based column number
content: string; // The full line content
}

The ctx.report object provides three methods for reporting findings:

ctx.report.violation({ message, file?, line?, fix? });
ctx.report.warning({ message, file?, line?, fix? });
ctx.report.info({ message, file?, line?, fix? });

Each method accepts an object with:

| Property | Type | Required | Description | | --------- | ------ | -------- | ------------------------------------ | | message | string | Yes | What the problem is | | file | string | No | Relative path to the offending file | | line | number | No | Line number where the problem occurs | | fix | string | No | Suggested fix for the violation |

Use ctx.report.violation() for problems that must block merges. Use ctx.report.warning() for issues worth flagging but not blocking. Use ctx.report.info() for purely informational output.

Each rule has a 30-second execution timeout. If a rule’s check function does not complete within 30 seconds, it is terminated and reported as an error. This prevents runaway rules from blocking the pipeline indefinitely.

Here is a complete rules file that checks for a banned import pattern. It enforces that no source file imports directly from node:fs (the project requires using a wrapper instead).

/// <reference path="../rules.d.ts" />
export default {
rules: {
"no-direct-fs-import": {
description:
"Source files must not import directly from node:fs; use the fs wrapper",
severity: "error",
async check(ctx) {
const sourceFiles = ctx.scopedFiles.filter(
(f) => f.endsWith(".ts") && !f.endsWith(".test.ts")
);
for (const file of sourceFiles) {
const matches = await ctx.grep(file, /from ["']node:fs["']/);
for (const match of matches) {
ctx.report.violation({
message: `Direct import from "node:fs" is not allowed. Use the fs wrapper from "src/helpers/fs" instead.`,
file: match.file,
line: match.line,
fix: 'Replace the import with: import { readFile, writeFile } from "../helpers/fs"',
});
}
}
},
},
},
} satisfies RuleSet;

When this rule runs against a file containing import { readFileSync } from "node:fs", the output looks like:

ARCH-007/no-direct-fs-import ERROR
src/services/config.ts:3 — Direct import from "node:fs" is not allowed. Use the fs wrapper from "src/helpers/fs" instead.
Fix: Replace the import with: import { readFile, writeFile } from "../helpers/fs"

Rules execute with the following guarantees:

  • Parallel across ADRs — Rules from different ADRs run concurrently for faster execution.
  • Sequential within an ADR — Rules belonging to the same ADR run one after another, so earlier rules can establish context for later ones.
  • Scoped files are pre-resolved — The ctx.scopedFiles array is populated before your check function is called, based on the ADR’s files globs.
  • Changed files auto-detectedctx.changedFiles is automatically populated with the branch diff against the base branch (e.g., main). Use --staged for pre-commit hooks (staged files only) or --base <ref> for an explicit base. This enables cross-file dependency rules to work locally, not just in CI.