Skip to content

Rules

A rule is one markdown file in .ai-rulez/rules/ (or .ai-rulez/domains/<domain>/rules/) with instructions the assistant should follow. Its frontmatter says when it applies: always, only for matching files, when the model finds it relevant, or only when invoked by hand. generate writes each rule where the tool loads it: a native rules folder (.claude/rules/, .cursor/rules/, .github/instructions/, …) with that tool’s own activation frontmatter, or the root instruction file (AGENTS.md, GEMINI.md, …) with the activation kept as a text line.

Two rules, one always on and one scoped to TypeScript files:

.ai-rulez/rules/commit-messages.md
---
type: Decision
title: Commit Messages
x-ai-rulez:
kind: rule
id: commit-messages
metadata:
priority: high
---
Use Conventional Commits: `feat:`, `fix:`, `chore:`.
.ai-rulez/rules/typescript.md
---
type: Decision
title: Typescript
x-ai-rulez:
kind: rule
id: typescript
metadata:
paths:
- "src/**/*.{ts,tsx}"
---
Prefer `type` over `interface` for unions. No `any`.
.ai-rulez/config.toml
presets = ["claude", "cursor", "copilot", "codex"]
Terminal window
ai-rulez generate

The always-on rule goes into the shared AGENTS.md, which every one of these tools reads (Claude Code through the @AGENTS.md import in CLAUDE.md). The scoped rule gets a file per rules folder:

.claude/rules/typescript.md
---
paths:
- src/**/*.{ts,tsx}
---
<!--
Generated by ai-rulez from .ai-rulez/rules/typescript.md. Edit the source, not this file.
Content-Hash: blake3:12a7a45c...
-->
# typescript
Prefer `type` over `interface` for unions. No `any`.
.cursor/rules/typescript.mdc
---
alwaysApply: false
globs: src/**/*.ts,src/**/*.tsx
---
...
.github/instructions/typescript.instructions.md
---
applyTo: src/**/*.ts,src/**/*.tsx
---
...

Codex has no rules folder, so AGENTS.md carries the scoped rule too, with its scope as a line of text:

AGENTS.md
...
# acme
## Rules
### commit-messages
**Priority:** high
Use Conventional Commits: `feat:`, `fix:`, `chore:`.
### typescript
_Applies to: `src/**/*.{ts,tsx}`_
Prefer `type` over `interface` for unions. No `any`.

Because the other three tools read AGENTS.md as well, they now load the TypeScript rule in every session. generate warns about this; Glob-scoped rules and AGENTS.md shows how to avoid it.

  • The file name is the rule’s identity. In rules folders and root files the rule’s heading is its id (typescript), and the first # heading of the body is dropped, so do not rely on it for the title. The skill fallback uses the title instead.
  • ai-rulez add rule <name> creates a rule in the form shown above: the OKF keys type and title, and the ai-rulez keys under x-ai-rulez.metadata. The same keys also work at the top level of the frontmatter. See OKF for the tree format and ai-rulez migrate okf to convert an older tree.
  • type is required in a tree created by init: without it validate reports AR9B1. A file you write by hand is also missing from the index.md listings (AR9B0, AR9B4) until ai-rulez generate refreshes them. Create rules with ai-rulez add rule, or run ai-rulez generate after writing them by hand.
  • Context files (.ai-rulez/context/) take the same activation keys as rules. Unscoped context always stays in the root file; scoped context is written to rules folders as context-<id>.

These keys go under x-ai-rulez.metadata, or at the top level of the frontmatter.

Key Type Default Meaning
priority string medium critical, high, medium, low or minimal. Orders rules in root files; an explicit value is printed as **Priority:** (not with compact = true).
activation string derived always, glob, auto or manual; see Activation.
paths list or string none Globs of the files the rule applies to. globs is a synonym.
description string none What the rule is for. Required for auto; the model uses it to decide.
targets list every output Outputs the rule is written to; see Targets.
fallback boolean true false keeps a scoped rule out of AGENTS.md and out of the skill fallback; see Glob-scoped rules and AGENTS.md.

paths and globs accept a YAML list or a comma-separated string ("src/**, tests/**"). A comma inside braces (*.{ts,tsx}) or brackets does not split, and a backslash-escaped comma is kept. Entries are trimmed and deduplicated. When both keys are set, globs wins. A leading ! negates a glob.

Activation Applies Requires
always in every session no paths
glob when files matching paths are in play paths
auto when the model judges the description relevant description
manual only when you invoke it nothing

When activation is absent, the first of these that applies decides:

  1. Devin’s trigger (always_on, glob, model_decision, manual), with globs.
  2. Cursor’s alwaysApply: true is always; false is glob with paths, else auto with a description, else manual.
  3. glob if paths is set, otherwise always.

A description alone never makes a rule auto, so rule files copied from Cursor or Devin keep their meaning.

validate and generate reject an unknown activation, glob without paths, auto without a description, and activation: always together with paths. A copied alwaysApply: true or trigger: always_on with paths is only a warning: the paths are ignored and the rule applies everywhere.

Frontmatter each preset writes for each activation:

Preset File always glob auto manual
claude .claude/rules/<id>.md none paths: [...] always on always on
cursor .cursor/rules/<id>.mdc alwaysApply: true globs: a,b, alwaysApply: false description, alwaysApply: false alwaysApply: false
copilot .github/instructions/<id>.instructions.md applyTo: '**' applyTo: a,b stays in the root file stays in the root file
devin .devin/rules/<id>.md trigger: always_on trigger: glob, globs: a,b trigger: model_decision, description trigger: manual
antigravity .agents/rules/<id>.md trigger: always_on trigger: glob, globs: a,b trigger: model_decision, description trigger: manual
kiro .kiro/steering/<id>.md none inclusion: fileMatch, fileMatchPattern inclusion: auto, name, description inclusion: manual
trae .trae/rules/<id>.md alwaysApply: true globs, alwaysApply: false description, alwaysApply: false alwaysApply: false
augment .augment/rules/<id>.md type: always_apply always on type: agent_requested, description type: manual
cline .clinerules/<id>.md none paths: [...] always on always on
junie .junie/rules/<id>.md none always on, with an _Applies to:_ line always on always on

Other presets with a rules folder (qwen, qoder, codebuddy, kilo, bob, grok, omp, aiassistant, …) are listed in Supported harnesses.

  • Always on means the tool has no way to express the mode, so the rule is written as always-on. generate logs one warning naming every rule it downgraded; nothing is dropped.
  • Copilot applies an instructions file without applyTo only on request, so auto and manual rules stay in its root file. An applyTo: '**' file is applied only when Copilot has file context; set Copilot to inline if a rule must also reach chat without files.
  • Comma-joined globs. Cursor, Copilot, Devin and Antigravity take one comma-separated string, so brace patterns are expanded (*.{ts,tsx} becomes *.ts,*.tsx). Cursor gets the bare list its own rule files use; a value that starts with * is quoted for the YAML parsers of Copilot, Devin and Antigravity (applyTo: '**/*.go').
  • Negated globs (!vendor/**) cannot be expressed in any rules-folder frontmatter. They are dropped with a warning. A rule whose globs are all negated stays in the root file, or is written as always-on where the preset has no root file.
  • Every generated file has a banner comment after the frontmatter with a Content-Hash line (and Source-Hash with [header] hashes = "full"), which generate --check uses to detect edits.

codex, gemini, opencode, amp, pi, xum, hermes and other single-file tools get every rule inline in their root file. The activation stays visible as a line under the heading:

### typescript
_Applies to: `src/**/*.{ts,tsx}`_

auto rules get _When relevant: <description>_. manual rules are written as always-on, with the downgrade warning. Negated globs are kept in the text line.

[rules]
mode = "split" # split (default) | inline
[rules.mode_by_preset]
claude = "inline"
Mode Rules folder receives Root file keeps
split every rule, and scoped context unscoped context, no rules
inline scoped rules and scoped context only all other rules, and unscoped context

mode_by_preset overrides mode for one preset. Switching modes removes the stale files on the next generate.

With agents_md = true (the default), the always-on rules and unscoped context of every preset that reads AGENTS.md are written once into AGENTS.md instead of each preset’s root file or rules folder; the rules folders receive the scoped, auto and manual rules. With agents_md = false, each preset follows the table:

Preset split inline
claude every rule in .claude/rules/ scoped rules in .claude/rules/, the rest in CLAUDE.md
copilot rules in .github/instructions/; auto and manual in copilot-instructions.md scoped rules only; the rest in copilot-instructions.md
antigravity every rule in .agents/rules/ scoped rules in .agents/rules/
junie every rule in .junie/rules/ no rule files; everything in AGENTS.md
cursor, devin, cline, trae one file per rule; mode has no effect (no root file for rules) same
kiro, augment one file per rule no rule files; everything in AGENTS.md
codex, gemini, opencode, … rules inline same
baz root and nested AGENTS.md; see Baz same

Custom provider presets follow mode when their outputs.rules sets split = true.

With agents_md = true, the presets share one AGENTS.md. When one of them has no rules folder (codex, opencode, pi, amp, …), that file inlines every glob-scoped rule and context item with an _Applies to:_ line, because the folderless tool has nowhere else to find them. Every other tool that reads AGENTS.md then loads those items at the start of every session too, beside its own path-scoped copy. generate warns when this happens.

Serve them as path-gated skills instead:

[rules]
scoped_fallback = "skill" # inline (default) | skill

AGENTS.md then keeps only the always-on items, and each glob-scoped item becomes a skill in .agents/skills/:

.agents/skills/rule-typescript/SKILL.md
---
name: rule-typescript
description: "Typescript: conventions for files matching src/**/*.{ts,tsx}. Use when reading or editing those files."
paths:
- src/**/*.{ts,tsx}
---
# Typescript
_Applies to: `src/**/*.{ts,tsx}`_
Prefer `type` over `interface` for unions. No `any`.
  • The skill is named rule-<id> (or context-<id>), its heading is the item’s title, its description is the item’s description or the generated sentence above (at most 250 characters), and its paths are the item’s positive globs.
  • Codex and the other folderless tools list the skill and load it on demand. Presets with a rules folder keep their own path-scoped files.
  • The fallback applies only when every folderless preset that reads AGENTS.md also reads .agents/skills (zcode, for example, does not), and not inside a scope.
  • An authored skill or command that already uses the name rule-<id> keeps it; the rule is left out with a warning.

fallback: false on a rule or context item keeps it out of both forms: it is neither inlined into AGENTS.md nor written as a skill, so only tools with path-scoped rules folders see it. A value that is not a YAML boolean is AR304.

Codex budgets the combined names and descriptions of all skills (about 8,000 characters when the context window is unknown). Many scoped rules mean many rule-<id> skills. Group them:

[rules]
scoped_fallback = "skill"
scoped_fallback_group = "directory" # none (default) | include | directory
Value Skills written
none one rule-<id> skill per item
include one scoped-<include> skill per include or builtin pack (scoped-builtin-<name>); your own items stay one per item
directory one scoped-<dir> skill per directory the globs point into (pkg/api/**/*.go is pkg/api; **/*.go is scoped-root)

A grouped skill holds each item under its own heading, and its paths are the union of their globs, so any matching file loads the whole group. A group with one item keeps its rule-<id> skill. validate reports AR964 when the listing exceeds the [lint.load_budgets] codex-skill-listing limit.

  • Antigravity and Gemini both read GEMINI.md. With agents_md = false and both presets enabled, Antigravity keeps all rules inline so GEMINI.md stays complete; setting mode_by_preset.antigravity overrides this with a warning.
  • Claude and Copilot together: VS Code Copilot also reads .claude/rules/, so a rule can load twice. Keep one of the two presets on inline to limit the overlap.
  • Junie prefers .junie/AGENTS.md, then the root AGENTS.md together with .junie/rules/, then .junie/guidelines.md. When another preset writes a root AGENTS.md, use split so Junie’s rules load from .junie/rules/.

targets restricts where a rule or context file is written. It applies to rules folders and to root files (CLAUDE.md, AGENTS.md, GEMINI.md, .github/copilot-instructions.md, and their *.local.md variants). A rule without targets goes everywhere.

targets: ["cursor", ".claude/rules/"]
Form Example
preset name claude, cursor
root file (path or base name) CLAUDE.md, copilot-instructions.md
exact path .claude/rules/go.md
base name go.md
directory prefix .cursor/rules/
whole tree .cursor/rules/*, .cursor/**
glob .claude/rules/*.md
everything *
  • A target naming a preset’s root file (CLAUDE.md) also selects that preset’s rule files.
  • <dir>/* and <dir>/** with a literal directory match everything below it, like <dir>/. Any other pattern is a path.Match glob against the full path or the base name, where * does not cross /. A malformed glob ([x) never matches; validate and generate warn about it.
  • Paths compare case-insensitively; \ and a leading ./ or / are accepted.
  • A shared file is selected for all its writers at once: naming codex, opencode or AGENTS.md selects the shared AGENTS.md, and naming gemini or antigravity selects GEMINI.md. With agents_md = true, every preset that reads AGENTS.md selects it.
  • A rule targeted only at a rules folder (.junie/rules/) is written there even in inline mode; it is omitted where that mode writes no rule files.

A rule file is named <id><ext>. The id is the source file name with spaces, _ and path separators turned into -, every character outside [A-Za-z0-9-] dropped, and leading and trailing dashes trimmed. Case is kept (Go-Style.md stays Go-Style.md). A name with no ASCII letter or digit becomes rule-<8 hex>, from the SHA-1 of the name.

When two sources map to the same file name (compared case-insensitively), the source whose path sorts first keeps it and the other is written as <id>-<6 hex> with a warning. Only if that name is taken too does generate fail, naming both sources. Two [[scopes]] whose paths produce the same qualifier also fail; see Monorepo.

Rules in .ai-rulez/local/rules/ are machine-local and gitignored. Where a preset writes rule files, a local rule becomes <rules folder>/<id>.local<ext> (for example .claude/rules/my-notes.local.md). Local rules a preset keeps inline, and local context, go to the tool’s machine-local root file (CLAUDE.local.md, AGENTS.local.md, AGENTS.override.md for Codex, …). The per-preset list is in Local configuration.

For [[scopes]], rule files are written to the root rules folder with the scope path as a name qualifier and glob prefix: <dir>/<scope-slug>/<id> for Claude, Cursor and Copilot, <scope-slug>--<id> for Devin, Cline, Antigravity and Junie. auto and manual rules are not limited to the scope; generate warns once per scope about them. See Monorepo.

You can keep your own files in the same rules folders. Generated rule files are gitignored one by one (with gitignore = true), never the whole folder, and ai-rulez never overwrites a file it did not generate. If a hand-written file has the name of a generated rule, the generated rule is written as <id>.ai-rulez<ext> instead (<id>.ai-rulez.instructions.md for Copilot) and generate warns. Rename or delete your file to give the rule its plain name back. Names matching *.local.* are reserved for local rules.

Tool Limit
Devin 12,000 characters per rule file
Antigravity 24,000 bytes per rule file

generate warns about a file over the limit. Content is never truncated; split the rule instead.

Codex limits the combined AGENTS.md content: it concatenates the files from the project root down to the working directory and stops at project_doc_max_bytes (32 KiB by default). With the codex preset, generate and tokens warn when the root AGENTS.md, or a nested one plus the files above it, exceeds the limit, and name the five largest sections. Set the limit your Codex uses, or 0 to turn the warning off:

[codex]
project_doc_max_bytes = 65536

To shrink the file, move rules into skills, scope them with paths and scoped_fallback = "skill", set compact = true, or split the repository with [[scopes]].