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.
Example
Section titled “Example”Two rules, one always on and one scoped to TypeScript files:
---type: Decisiontitle: Commit Messagesx-ai-rulez: kind: rule id: commit-messages metadata: priority: high---
Use Conventional Commits: `feat:`, `fix:`, `chore:`.---type: Decisiontitle: Typescriptx-ai-rulez: kind: rule id: typescript metadata: paths: - "src/**/*.{ts,tsx}"---
Prefer `type` over `interface` for unions. No `any`.presets = ["claude", "cursor", "copilot", "codex"]ai-rulez generateThe 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:
---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`.---alwaysApply: falseglobs: src/**/*.ts,src/**/*.tsx---...---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:
...# 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.
Rule files
Section titled “Rule files”- 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 thetitleinstead. ai-rulez add rule <name>creates a rule in the form shown above: the OKF keystypeandtitle, and the ai-rulez keys underx-ai-rulez.metadata. The same keys also work at the top level of the frontmatter. See OKF for the tree format andai-rulez migrate okfto convert an older tree.typeis required in a tree created byinit: without itvalidatereportsAR9B1. A file you write by hand is also missing from theindex.mdlistings (AR9B0,AR9B4) untilai-rulez generaterefreshes them. Create rules withai-rulez add rule, or runai-rulez generateafter 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 ascontext-<id>.
Frontmatter
Section titled “Frontmatter”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
Section titled “Activation”| 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:
- Devin’s
trigger(always_on,glob,model_decision,manual), withglobs. - Cursor’s
alwaysApply:trueisalways;falseisglobwith paths, elseautowith a description, elsemanual. globifpathsis set, otherwisealways.
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.
Output per tool
Section titled “Output per tool”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.
generatelogs one warning naming every rule it downgraded; nothing is dropped. - Copilot applies an instructions file without
applyToonly on request, soautoandmanualrules stay in its root file. AnapplyTo: '**'file is applied only when Copilot has file context; set Copilot toinlineif 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-Hashline (andSource-Hashwith[header] hashes = "full"), whichgenerate --checkuses to detect edits.
Tools without a rules folder
Section titled “Tools without a rules folder”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
Section titled “[rules] mode”[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.
Glob-scoped rules and AGENTS.md
Section titled “Glob-scoped rules and AGENTS.md”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) | skillAGENTS.md then keeps only the always-on items, and each glob-scoped item becomes a skill in .agents/skills/:
---name: rule-typescriptdescription: "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>(orcontext-<id>), its heading is the item’stitle, its description is the item’sdescriptionor the generated sentence above (at most 250 characters), and itspathsare 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.mdalso 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.
Keeping the skill listing short
Section titled “Keeping the skill listing short”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.
Tool notes
Section titled “Tool notes”- Antigravity and Gemini both read
GEMINI.md. Withagents_md = falseand both presets enabled, Antigravity keeps all rules inline soGEMINI.mdstays complete; settingmode_by_preset.antigravityoverrides 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 oninlineto limit the overlap. - Junie prefers
.junie/AGENTS.md, then the rootAGENTS.mdtogether with.junie/rules/, then.junie/guidelines.md. When another preset writes a rootAGENTS.md, usesplitso Junie’s rules load from.junie/rules/.
Targets
Section titled “Targets”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 apath.Matchglob against the full path or the base name, where*does not cross/. A malformed glob ([x) never matches;validateandgeneratewarn 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,opencodeorAGENTS.mdselects the sharedAGENTS.md, and naminggeminiorantigravityselectsGEMINI.md. Withagents_md = true, every preset that readsAGENTS.mdselects it. - A rule targeted only at a rules folder (
.junie/rules/) is written there even ininlinemode; it is omitted where that mode writes no rule files.
File names
Section titled “File names”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.
Local rules
Section titled “Local rules”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.
Scopes
Section titled “Scopes”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.
Hand-written rule files
Section titled “Hand-written rule files”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.
Size limits
Section titled “Size limits”| 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 = 65536To shrink the file, move rules into skills, scope them with paths and
scoped_fallback = "skill", set compact = true, or split the repository with [[scopes]].
