Configuration Reference¶
Configuration reference for .ai-rulez/config.toml (YAML and JSON configs are no longer read; ai-rulez migrate v5 converts them).
File-Based Configuration¶
ai-rulez uses a file-based approach where you edit files directly with your editor or use CRUD commands:
- Configuration: Edit
.ai-rulez/config.toml(TOML format) with any text editor - Rules: Add/edit
.ai-rulez/rules/*.mdfiles or useai-rulez add rule - Context: Add/edit
.ai-rulez/context/*.mdfiles or useai-rulez add context - Skills: Add/edit
.ai-rulez/skills/{name}/SKILL.mdfiles or useai-rulez add skill - Commands: Add/edit
.ai-rulez/commands/{name}.md(flat form) or.ai-rulez/commands/{name}/COMMAND.md(directory form with optionalreferences/subdirectory) - Checks: Add/edit
.ai-rulez/checks/{name}.mdcode-review guidelines (frontmatterdescription,severity,tools,targets) or useai-rulez add check; see Checks - Agents: Add/edit
.ai-rulez/agents/*.mdfiles or useai-rulez add agent(add commandlikewise creates commands). Theclaudepreset passes the documented Claude Code subagent keys (disallowedTools,permissionMode,memory,maxTurns,mcpServers,hooks,background,isolation,color,initialPrompt,omitClaudeMd) through with their YAML types; other presets ignore them.validateandgeneratewarn about an agent key no tool reads (with a suggestion) and about askills:entry that names no skill;validatereports the same findings asAR303andAR302 - Domains: Add/edit
.ai-rulez/domains/{name}/{rules,context,skills,agents,commands,checks}/*.mdfiles or useai-rulez domain add - MCP Servers: Inline in
.ai-rulez/config.toml - Machine-local configuration: Personal content under
.ai-rulez/local/and aconfig.local.tomloverlay, both gitignored. See Local overlay
You can either directly edit files with your editor or use CRUD commands for programmatic modification. After changes, run ai-rulez generate to create tool-specific outputs.
Configuration Discovery¶
When no explicit path is given, the CLI discovers configuration by walking up from the current directory to the nearest directory holding a configuration, stopping at the repository root (never above the home directory), and trying in each directory, in order:
.agents/ai-rulez.toml— the configuration of a project whose content is.agentsandAGENTS.md.ai-rulez/config.toml— the tool-specific directory (default).config/ai-rulez/config.toml— the project-level.config/conventionai-rulez.tomlat the project root — a configuration without a.ai-rulez/folder; a config directory at the same level wins, with a warning that the file is ignored
config.toml is the only config format read, and its version must be "5.0". A project that has only a
config.yaml, config.yml or config.json (or a config.local.yaml, .yml or .json overlay), a flat ai-rulez.yaml
(also .ai-rulez.yaml, ai_rulez.yaml and their .yml forms), or a config.toml with an older version, fails with an
error that names the file; run ai-rulez migrate v5 to convert it (a 2.x or 3.x project goes through ai-rulez 4.x
first). See Migrating to v5.
.agents/ai-rulez.toml wins over .ai-rulez/ when both exist at the same level, and .ai-rulez/ wins over .config/ai-rulez/. --config <file> selects an exact file, and --config-dir <path> selects a non-default directory (for example --config-dir .config/ai-rulez). To scaffold the .config/ layout, run ai-rulez init --config-dir .config/ai-rulez; generated outputs and the managed .gitignore block then reference .config/ai-rulez/ instead of .ai-rulez/.
Checks generate runs before it writes¶
generate runs the same schema check as ai-rulez validate over config.toml and the machine-local
config.local.toml. A key the schema does not know (delivry, [lock] enforc) has no effect, so generate
prints a warning naming the file and the key, with the nearest known key when there is one:
WARN Configuration problem: .ai-rulez/config.toml: unknown key "lock.enforc" (did you mean "enforce"?)
generate --strict-config, or AI_RULEZ_STRICT=1 in CI, fails with exit code 1 instead. validate and generate report
the same unknown keys.
--strict means something different on each command, and they do not imply one another:
| Command | --strict adds |
|---|---|
generate --strict-config |
Schema check only: an unknown or invalid configuration key fails the run instead of warning. |
validate |
Deep content checks (dead links and references, globs that match nothing, size and duplicate checks, findings AR###). Unknown keys already fail validate without it. |
Run both in CI: ai-rulez validate && ai-rulez generate --strict-config.
generate also lists the commands it is about to write that your tools will run: [[hooks]] commands (and the
content digest of a hook script file, so a changed script behind the same path is listed again), http and prompt
hooks, command-based [[mcp_servers]], [permissions] allow rules for every harness that takes them,
[claude.settings.managed] env entries (NODE_OPTIONS, ANTHROPIC_BASE_URL, ...), and plugin enablement and
marketplace registration. Only commands that are new or changed since the previous run on this machine are listed
(every command on a first run in a fresh clone), one item per line. It only warns, it is printed even with --quiet,
and --yes or AI_RULEZ_ACK_COMMANDS=1 silences it. The previous run is remembered in ~/.cache/ai-rulez/commands/,
never in the repository.
The text is repository-controlled, so it is made safe to print: control characters (ESC, CR, LF, other C0/C1 codes),
bidirectional overrides and zero-width characters appear as visible escapes (\x1b, \u202e), and anything that
looks like a credential is replaced by <redacted> (secret-named flags such as --token=..., Bearer/Authorization
values, URL user information and query values, NAME=value with a secret-looking name, well-known token prefixes, long
base64 or hex runs). An env value is shown unless its name or value looks like a credential. generate --watch prints
the same list on every regeneration that changes a command, and the MCP generate_outputs tool returns it as
new_commands (and writes it to stderr) because it has no terminal.
Basic Structure¶
The minimal valid configuration:
A typical production configuration:
version = "5.0"
name = "my-project"
description = "My project description"
presets = ["claude", "cursor", "gemini"]
default = "full"
gitignore = true
[profiles]
full = ["backend", "frontend", "qa"]
backend = ["backend", "qa"]
frontend = ["frontend", "qa"]
[[mcp_servers]]
name = "ai-rulez"
command = "npx"
args = ["-y", "ai-rulez@latest", "mcp"]
Required Fields¶
version¶
The config schema version. Must be "5.0"; "4.0" and older are rejected with a pointer to ai-rulez migrate v5.
name¶
The project name. Used in generated files and displayed in headers.
Every project has one name; the value is used verbatim in generated headers.
Lint-only configuration¶
presets is optional. A configuration with no preset (omitted or presets = []) and no [plugin] block is a lint-only configuration: validate, scan and [[lint.external]] run against the files already in the repository, while generate prints No presets are configured: nothing to generate, writes nothing (not AGENTS.md, not .mcp.json) and exits 0. generate --check and doctor report no drift. It works in the directory layout and in the root ai-rulez.toml layout.
Content Layout¶
Symlinks in content¶
A symlinked file or directory under your project's .ai-rulez/ (including domains/ and local/) is followed only
when its fully resolved target is inside the project (the git top-level, else the directory holding .ai-rulez/). Any
other symlink, including a dangling one, is refused: generate fails with an error naming it and
ai-rulez validate reports it as an error. Content from includes, installed skills, skill sources and OKF bundles
never follows symlinks; see Includes.
Skills¶
Skills use a directory form with supporting resources:
.ai-rulez/skills/deployment-checklist/
├── SKILL.md # Main skill content
├── references/ # Markdown documentation (optional)
│ └── api-endpoints.md
├── scripts/ # Executable scripts (optional)
│ └── deploy.sh
└── assets/ # Binary assets (optional)
└── diagram.png
The skill directory name becomes the skill id. Resources under references/, scripts/, and assets/ are emitted as separate files in the generated output, preserving the Agent Skills progressive-disclosure model. Subdirectories outside these three produce a warning naming the skill and the unrecognized directory. Build artifacts (.venv*, __pycache__, node_modules, anything .gitignore ignores, ...) are not bundled; see bundle_exclude.
Commands¶
Commands support both flat and directory forms:
Flat form (single file):
Directory form (with supporting resources):
.ai-rulez/commands/deploy/
├── COMMAND.md # Main command content
└── references/ # Markdown documentation (optional)
└── deployment-guide.md
The directory form mirrors the skill layout and supports references/, scripts/, and assets/ subdirectories. Use it when a command needs bundled reference material.
Optional Fields¶
description¶
Brief description of the project or configuration.
presets¶
Specifies which tools to generate configuration for. Can be built-in preset names or custom preset objects.
Built-in Presets¶
presets = [
"claude", # → CLAUDE.md, .claude/ (rules/, skills/, agents/)
"cursor", # → .cursor/rules/, .cursor/commands/, .cursor/agents/, .agents/skills/
"gemini", # → GEMINI.md, .gemini/ (settings.json, agents/), .agents/skills/
"copilot", # → .github/copilot-instructions.md, .github/instructions/, .github/{skills,agents,commands}/
"devin", # → AGENTS.md, .devin/ (rules/, skills/, agents/, mcp_config.json)
"cline", # → .clinerules/, .cline/
"codex", # → AGENTS.md, .agents/skills/ and .codex/
"amp", # → AGENTS.md and .agents/ (.amp/settings.json when an effort resolves)
"junie", # → AGENTS.md and .junie/ (rules/, skills/, agents/)
"opencode", # → AGENTS.md, .opencode/, opencode.json (when MCP servers are set)
"hermes", # → .hermes.md
"antigravity", # → .agents/ (rules/, skills/, agents/), GEMINI.md
"xum", # → AGENTS.md, .xum/skills, .xum/agents, .xum/mcp.jsonc (stdio with env as a shell prefix, http and sse MCP servers)
"pi", # → AGENTS.md, .agents/skills, .pi/agents, .pi/mcp.json (stdio and http MCP servers)
"baz", # → AGENTS.md (root and nested), .agents/skills, .claude/agents; see baz.md
"okf" # → docs/okf/, an Open Knowledge Format bundle of the content; see okf.md
]
These are 15 of the 52 built-in presets. The complete list, with the features each supports, is in Supported harnesses; the names are:
aiassistant, amp, antigravity, augment, baz, bob, claude, cline, codebuddy, codebuff,
codewhale, codex, commandcode, copilot, copilot-cli, cortex, crush, cursor, deepagents, devin,
dsh, factory, gemini, gitlab-duo, goose, grok, hermes, junie, kilo, kimi, kiro, letta,
mimocode, muse, omp, openclaw, opencode, pi, poolside, qoder, qwen, reasonix, replit,
rovodev, takt, trae, vibe, warp, xum, zcode, zed, zoocode.
The windsurf preset is now devin and continue-dev was removed; ai-rulez doctor reports both. See
Migrating to v5.
With agents_md = true, one shared AGENTS.md and .agents/skills/ replace the per-tool copies; see
agents_md.
With the default [rules] mode = "split", rules are written to each tool's native rules folder
(.claude/rules/, .cursor/rules/, .github/instructions/, .junie/rules/, .agents/rules/, and so
on) and the root file keeps context and the agent roster. See Rules for what each
preset writes in each mode.
mcp is a built-in preset too, but it is a shared utility: it writes the generic .mcp.json and is
invoked automatically when MCP servers are configured, so you normally do not name it. It is the only
preset not produced by a coding-tool adapter, and presets = ["mcp"] alone generates nothing else.
Custom Presets¶
For tools not in the built-in list:
[[presets]]
name = "my-tool"
type = "markdown" # or: directory, json
path = "docs/MY_TOOL.md"
template = """
# {{ .Name }}
{{ range .Rules }}
- **{{ .Name }}**: {{ .Content }}
{{ end }}
"""
The template uses Go's text/template with only its built-in functions; a helper such as where,
truncate, or now is not defined and fails to parse. See Custom Presets for the
available template data.
Provider-backed Presets (full parity)¶
A custom preset may reference a declarative provider spec instead of a
template. A provider spec has the same expressive power as the built-in presets
— root instructions file, skills/agents/commands, frontmatter, effort/model, and
MCP sidecars — and is validated against
schema/provider.schema.json.
The provider path is relative to the project root and must not escape it. The
spec's name must match the preset's name. Example spec:
name = "my-tool"
directories = [".my-tool"]
[root]
file = "MY_TOOL.md"
sections = ["title", "rules_inline", "context_inline"]
[outputs.skills]
mode = "per_item_file"
dir = ".my-tool/skills"
filename = "{id}/SKILL.md"
[[sidecars]]
kind = "mcp_json"
path = ".my-tool/mcp.json"
emit_when = "has_mcp_servers" # also: always, has_plugins, has_resolved_effort, has_mcp_json_entries, has_mcp_servers_or_plugin_settings, has_resolved_effort_or_mcp_servers, has_hooks
Built-in presets are written as plain strings (presets = ["claude", "xum"]);
provider-backed presets use the inline-table form shown above.
Output path rules¶
A configuration comes from the repository, so every output path it names (a
custom preset path, a provider spec's root.file, outputs.*.dir,
sidecars[].path and directories, okf.dir, marketplace.output_dir,
codex_skills_dir) must be a relative path inside the project. validate and
generate reject an absolute path, a .. segment, a drive or UNC prefix, any
.git segment (any case), and a path under .ai-rulez/, .config/ai-rulez,
.hg or .svn. A custom preset that writes a file CI or a developer tool runs
(.github/workflows/, Makefile, .vscode/tasks.json, ...) is allowed, and the
generate command summary lists it as exec-file before writing. Generation
also refuses any write that resolves outside the project or inside .git.
Native tool and model names in a provider spec¶
A tool with its own tool and model vocabulary can be given a translation instead of Claude's names.
These keys go under [outputs.<kind>.frontmatter]:
| Key | Description |
|---|---|
tool_names |
Table of Claude tool name to the tool's own name for the tools list (matched ignoring case). A tool the table does not name is dropped; an agent left with none writes no tools key |
tool_case |
"lower" writes every tool name lower-case (after tool_names) |
model_aliases |
Table of resolved model (matched ignoring case) to the tool's own id; "" drops the model |
drop_bare_aliases |
Drops a model that is sonnet, opus, haiku or inherit, so the agent inherits the session model; runs after model_aliases |
[outputs.<kind>.body] takes replace (a table of placeholder to replacement in the item's content, such as
{ "$ARGUMENTS" = "$prompt" }) and replace_flag (a frontmatter key written true when a replacement happened).
An mcp sidecar takes transports (["stdio"] leaves remote servers out), env_ref_syntax ("dollar",
"env_prefix", "braced" or "opencode_env": a value that came from a ${VAR} placeholder is written as $VAR,
${env:VAR}, ${VAR} or {env:VAR} instead of the resolved secret), and elements.project_only (the elements are project-relative and skipped in the user scope).
A hooks sidecar renders the top-level [[hooks]] into the tool's own hooks file. Its dialect names the
harness whose hook format is used (claude, qwen, factory, kiro, vibe, ...; the harnesses and what
each writes are in Hooks and permissions), which fixes the key and the layout, so a hooks
sidecar takes no key; it is emitted when [[hooks]] are declared (emit_when = "has_hooks"):
[[sidecars]]
kind = "hooks"
dialect = "qwen"
path = ".qwen/settings.json"
global_path = ".qwen/settings.json"
Sidecars that name one path (an mcp and a hooks sidecar of one settings file) are merged into a single
document, so neither replaces the other.
Split rule files in a provider spec¶
A provider can honour the [rules] mode setting like the built-in claude and junie presets by
adding a split rules output:
[root]
file = "MY_TOOL.md"
sections = ["title", "rules_inline", "context_inline"]
[outputs.rules]
mode = "per_item_file"
dir = ".my-tool/rules"
filename = "{id}.md"
split = true
dialect = "claude" # claude | cursor | trigger | copilot | cline | continue | junie
inline_filter = "path_scoped"
| Field | Description |
|---|---|
split |
When true, the output follows [rules] mode: split mode writes every rule (and path-scoped context item) to a file, inline mode writes only the items inline_filter selects |
dialect |
Required with split. The frontmatter vocabulary of the rule files; cannot be combined with body or frontmatter |
inline_filter |
Optional, path_scoped only. Items that still get a file in inline mode; omit to keep everything inline |
dir |
Required with split: a relative path inside the project |
filename |
Flat {id}<ext> template, no / |
split also requires rules_inline in root.sections and cannot be combined with filter. The
folder is treated like the built-in rules folders: hand-written files are never overwritten, hashes go
in the banner, and, with gitignore = true, generated files are gitignored one by one. A provider without split inlines all
rules in its root file. Custom providers get no local output.
default¶
The default profile name used when ai-rulez generate is run without --profile.
It may name several profiles to compose, the same as --profile:
When default is not set, the built-in default profile applies and its meaning depends on whether
any profiles are defined:
- If no profiles are defined at all,
defaultincludes root content and all domains. - If profiles are defined and one is named
default, that definition is used. - If profiles are defined but none is named
default,defaultincludes root content, globally-active builtin domains, and domains sourced from external includes — but not profile-only domains.
profiles¶
Named profiles that specify which domains to include in generation.
[profiles]
full = ["backend", "frontend", "qa"]
backend = ["backend", "qa"]
frontend = ["frontend", "qa"]
qa = ["qa"]
Each profile specifies a list of domain names. When generating with a profile:
- All root content (
.ai-rulez/rules/,.ai-rulez/context/,.ai-rulez/skills/,.ai-rulez/agents/,.ai-rulez/commands/) is included - Content from the specified domains (
.ai-rulez/domains/{name}/) is included - Globally-active builtin domains and every domain sourced from an external include are always included, whatever the profile
- A builtin named as
builtin:<name>in the profile is loaded for that profile only
An entry may also be the wildcard *, which selects every domain the config knows, or an exclusion
!name, which removes a domain from the selection. Resolution starts from the positive entries
(expanding *), then removes the negated ones, so order does not matter and the result is
deterministic. This is how a monorepo keeps most domains in the root output while a [[scopes]]
subdirectory renders the rest:
[profiles]
# Every domain except "s"; "s" is rendered only in its own scope.
all-but-s = ["*", "!s"]
A profile made only of exclusions — ["!a"] with no positive entry — is an error, because there is
nothing to subtract from; write ["*", "!a"] to select everything except a. An exclusion that
names an unknown domain is a warning, exactly as an unknown positive name is, and is ignored.
Several profiles can be selected at once by separating them with commas — --profile
base,backend, or default = "base,backend" — which generates the union of their
domains. A profile name may therefore not contain a comma, and a profile's value lists
domains only, never other profiles. See
Domains and Profiles and, for how profiles differ from
roles, Profiles vs roles.
roles and role_manifest¶
[[roles]] maps a job to a slice of the content (domains, per-kind include/exclude selectors, inheritance, a
per-skill skill_mode rendered as Claude Code skillOverrides), selected with ai-rulez generate --role <name>.
Like --profile, --role accepts a comma-separated list to compose several roles; see
Composing roles. [role_manifest] enabled = true writes <config dir>/roles.json; skill_mode_fallback = "drop" | "serve" says what an off skill does on a harness with no setting for it. See Roles.
[role_manifest]
enabled = true
[[roles]]
name = "backend"
domains = ["backend"]
[roles.skills]
exclude = ["deploy-*"]
[roles.skill_mode]
migrate = "name-only"
lock¶
[lock] tunes how strictly ai-rulez.lock is enforced: enforce (default true whenever ai-rulez.lock exists, enforce = false opts out) makes validate
report content drift (or an unreadable lock) as AR981 / AR982, makes generate refuse a remote include or installed skill the lock does not pin (and AR010 an error), and makes generate --locked require content pins (lock --check always does); include_outputs
(default true) pins generated outputs; scope is all (default) or skills. See Lock file.
An organization policy can force enforce and include_outputs on.
scopes¶
Generate additional assistant files in subfolders with their own profile.
[profiles]
frontend = ["frontend"]
[[scopes]]
path = "packages/web"
profile = "frontend"
presets = ["codex", "claude"]
A scoped file contains only the scope's own content (its profile's domains that the root run does
not already render), not the root rules and context: the target tools load a subdirectory CLAUDE.md/AGENTS.md on top of the root file, so
repeating the root content would duplicate the always-loaded text. The default scoped presets are
codex and claude, producing subfolder AGENTS.md and CLAUDE.md files.
A domain that the root output already contains (a builtin, an include, or one selected by the root profile)
is never repeated in a scope, and a scope left with nothing writes no files. Scope paths must be
relative, stay inside the project and contain no glob characters (* ? [ ] { } , !); validate rejects
anything else.
Rule files of a scope are written to the root rules folders with the scope path as a qualifier and glob prefix, not into the scope directory; see Scoped Rule Files.
gitignore¶
Opt-in: controls whether ai-rulez maintains a managed block in .gitignore with generated output patterns.
It is off by default, because committed outputs should not flip-flop in and out of the ignore block.
gitignore = true # Maintain the managed .gitignore block (or pass `generate --gitignore`)
# gitignore = false # Default: leave .gitignore alone
When true, ai-rulez adds the specific generated files and owned subdirectories to .gitignore —
AGENTS.md/CLAUDE.md/.mcp.json and owned subtrees such as .claude/skills/, .claude/agents/,
.codex/, .opencode/skills/. It never ignores an assistant directory wholesale, so a hand-authored
.opencode/settings.json beside the generated files stays tracked. GitHub output is narrower:
ai-rulez ignores generated .github/copilot-instructions.md, .github/agents/, .github/prompts/,
and .github/skills/ without ignoring all of .github/.
ai-rulez adds only what git does not already ignore. Before writing, it asks git (git check-ignore) about each
entry against every ignore source (all .gitignore files, .git/info/exclude, core.excludesFile) with its own
block left out, and skips an entry when a rule of yours already ignores it, or when your last matching rule is a
negation (!CLAUDE.md), which is read as a deliberate override and never re-ignored. The managed
# BEGIN ai-rulez / # END ai-rulez block holds only the remainder and is removed when nothing is left. A negated
machine-local or secret output (*.local.*, config.local.*, .ai-rulez/local/, a generated MCP config holding
secrets) is also left out of the block, with a warning; generation still refuses to write a secret-bearing MCP config
that ends up unignored. Outside a git repository, or when git is unavailable, every entry is added.
Your lines are yours. ai-rulez edits only between # BEGIN ai-rulez and # END ai-rulez; every byte outside
the block stays as it was, blank lines included (nothing is inserted under # END ai-rulez). A negation of a
directory entry (!.claude/skills/, !/.claude/skills/) is honored the same way as one of a file entry
(!CLAUDE.md): the entry is left out of the block. ai-rulez doctor does not report an output you re-included this
way, since generate leaves it alone; it still reports a machine-local or secret output a rule un-ignores, and says
that generate will not re-ignore it.
The machine-local manifest, .ai-rulez/.generated-manifest.local.json, is the one entry that is always written: it
is ignored whenever it exists in a repository (and whenever a run writes it), even with gitignore = false, without
adding .ai-rulez/local/ or the overlay patterns.
Machine-local outputs and sources are always gitignored, even when gitignore = false; an entry is added
when the file or directory exists, so a project without a config.local.* overlay or .ai-rulez/local/ tree gets
no line for it: the local outputs (CLAUDE.local.md, AGENTS.local.md, GEMINI.local.md, AGENTS.override.md,
<rulesdir>/*.local.* including ai-rulez.local.*), the .ai-rulez/local/ source tree, the
config.local.* overlay with its .config.local.* lock and temp files, and
.ai-rulez/.generated-manifest.local.json. Overlay-derived outputs whose names differ per machine are
excluded through .git/info/exclude instead. See Local Configuration.
gitignore defaults to false, so generated files are normally committed; set it to true when the team
keeps generated output out of git and regenerates it locally.
gitignore_exclude¶
Keeps generated outputs you commit on purpose out of the managed .gitignore block, while every other entry stays
managed. Use it instead of a hand-written !pattern after the block.
gitignore = true
gitignore_exclude = ["AGENTS.md", "**/CLAUDE.md", ".claude/skills/", ".claude/agents/", ".opencode/skills/"]
Entries use gitignore notation, matched against the patterns the block would list: an entry without /
(AGENTS.md, *.md) matches at any depth, one with a / is anchored at the root, ** crosses directories
(**/AGENTS.md also reaches the nested svc/AGENTS.md of a [[scopes]] entry), and a trailing / names a directory
and covers what is below it. An entry is a plain pattern: an empty one, a comment or a !negation fails validation.
The knob has no effect unless the block is written (gitignore = true or generate --gitignore); gitignore itself stays a
plain boolean. Machine-local and secret outputs (*.local.*, config.local.*, .ai-rulez/local/,
.ai-rulez/.generated-manifest.local.json, an MCP config holding secrets) are never excluded; an entry that only
reaches them is ignored with a warning. In a recursive run each root applies its own list to its own outputs.
Excluded paths are not reported by ai-rulez doctor.
generate warns instead of letting the block quietly win or lose:
- A tracked file matches a line of the block: git keeps tracking it, but new files next to it (a new skill under
.claude/skills/) are ignored and needgit add -f. The warning names the files and the fix, agitignore_excludeentry orgit rm --cached, and calls out an unanchoredAGENTS.md/CLAUDE.mdline that also hides every nested root's file. - A
!patternof yours un-ignores a line the block would list: it wins, as documented above, and the warning names the negation'sfile:line. Agitignore_excludeentry for the path states the intent and silences it.
compact¶
Controls whether generated inline rule sections omit per-rule **Priority:** annotations.
compact = false # Default: include Priority lines
# compact = true # Omit Priority annotations, reduce output size
When true, generated presets (CLAUDE.md, GEMINI.md, copilot-instructions.md, etc.) omit the per-rule
**Priority:** lines, reducing file size for large rule sets. Applies to all configured presets.
source¶
Where the content is read from.
source = "ai-rulez" # Default: the .ai-rulez content tree
# source = "agents" # AGENTS.md and the .agents directory
"agents" reads the open standards instead of the .ai-rulez content tree: the root AGENTS.md, Agent Skills in
.agents/skills/<name>/SKILL.md, and the ai-rulez layout under .agents/ for what the standards lack. ai-rulez
translates that content to every other tool and never writes AGENTS.md or anything under .agents/. A configuration
kept in .agents/ai-rulez.toml implies source = "agents". It forces per-tool files (agents_md off) and rejects an explicit agents_md = true. See .agents as a source.
agents_md_split¶
With the agents source, reads the root AGENTS.md as one context item per ## heading instead of one item.
Items are named agents-<heading slug>, and list context and remove context address them. It needs
source = "agents" or a configuration in .agents/ai-rulez.toml. See
Splitting AGENTS.md.
agents_md¶
Renders the files several tools share once instead of once per tool. On by default: AGENTS.md is the canonical
instruction file and CLAUDE.md imports @AGENTS.md.
agents_md = true # Default: one shared AGENTS.md and .agents/skills
# agents_md = false # Every preset writes its own files
When true (the default), always-on rules and context go into one AGENTS.md (plus a nested <scope>/AGENTS.md for each
[[scopes]] entry) and skills go into one .agents/skills/<name>/SKILL.md. The presets that read these files
(codex, opencode, amp, xum, pi, claude, gemini, antigravity, hermes, cursor, copilot, devin,
cline and junie) stop writing their own AGENTS.md copy, root file and skills directory.
Turning the flag off regenerates the per-tool files and removes the shared ones that no preset writes itself.
Quick reference:
- Rules folders (
.cursor/rules,.claude/rules, ...) keep only the items that are not always-on; the scoped items are also inlined intoAGENTS.mdwhen a configured preset has no folder for them (duplicated for tools that have one). claudekeepsCLAUDE.mdas a banner plus@AGENTS.md;geminigetsAGENTS.mdadded tocontext.fileNamein.gemini/settings.json;GEMINI.md,.hermes.mdand.github/copilot-instructions.mdare not written. Set[claude] claude_md = "none"to drop theCLAUDE.mdshim and leaveAGENTS.mdas Claude's only file.- Skills with
targets, machine-local files and agents, commands and MCP files stay per-preset. - If another preset (including a custom provider) also writes
AGENTS.mdor a file under.agents/skills, the shared output wins.
Tool support, per-preset file lists, the duplication trade-off, targets, scopes and the on/off behavior are in
AGENTS.md and .agents/skills.
skip_outputs¶
Output kinds ai-rulez never writes, overwrites or deletes, so a project can hand-write them and still let ai-rulez emit the rest (installed skills, plugins, MCP servers, hooks, permissions). The default is empty: nothing is skipped.
| Value | Skips |
|---|---|
instructions |
Every root instruction file: CLAUDE.md, AGENTS.md, GEMINI.md and the other presets' root files, the *.local.md and AGENTS.override.md variants, and scope roots. Overrides agents_md and claude_md. |
agents_md |
Only the shared AGENTS.md (and AGENTS.override.md) and the shared .agents/skills tree. |
claude_md |
Only CLAUDE.md (root and scopes). |
rules |
Rule files in every preset (.claude/rules, .cursor/rules, ...). |
skills |
Skill directories in every preset, installed skills included. A command a harness runs as a skill (.claude/skills/<command>, .agents/skills/<command>) is a command, not a skill. |
agents |
Agent definition files. |
commands |
Command and prompt files, and the skills that carry a command (including agents/openai.yaml for Codex). |
An unknown value fails validation and names the valid ones. config.local.toml accepts the key too. Skipped files leave
the manifest, the managed .gitignore block, generate --check, the lock output digests and clean, so none of them
reads or removes a hand-written file. A file an earlier run generated and that is now skipped is left in place and
reported once as no longer managed. See Managing only remote skills and plugins.
claude_md¶
Controls whether the claude preset writes its CLAUDE.md when agents_md is on. The default, "shim", keeps the
CLAUDE.md that imports @AGENTS.md; "none" omits it and writes only AGENTS.md. It is a key of the [claude]
table, alongside [claude.settings] and [claude.skills].
[claude]
claude_md = "shim" # Default: CLAUDE.md imports @AGENTS.md
# claude_md = "none" # Write only AGENTS.md; no CLAUDE.md at the root or in a scope
Claude Code reads AGENTS.md itself from 2.1.277 on, so "none" lets a repository use AGENTS.md as its only
instruction file. With the option set, generate writes AGENTS.md (root and each [[scopes]] directory) and no
CLAUDE.md, and it removes a CLAUDE.md a previous run generated — even when the generated manifest is gone, because
the shim's own Content-Hash header proves ai-rulez wrote it. A hand-authored CLAUDE.md carries no such header and is
left alone. generate --check reports a leftover generated shim as an orphan, and clean removes it.
Shadowing caveats: Claude Code reads AGENTS.md only when no CLAUDE.md, .claude/CLAUDE.md or CLAUDE.local.md
exists in the working directory or any ancestor. A hand-authored CLAUDE.md anywhere on that path, or a
CLAUDE.local.md (which ai-rulez writes for machine-local content under .ai-rulez/local/), therefore shadows
AGENTS.md and makes the option ineffective for that tree. Remove those files, or do not use machine-local content,
if AGENTS.md must be the only instruction file. Nested AGENTS.md files are read lazily, when Claude Code works
inside that directory.
codex_skills_dir¶
Where the codex preset writes skills, relative to the project root. The default is .agents/skills, the
directory Codex documents for repository skills (it scans .agents/skills from the working directory up to the
repository root; .codex/skills is not read). Set it to .codex/skills to write there instead:
After the default changes, generate writes the new tree and removes the files it previously wrote to
.codex/skills (only manifest-tracked files; hand-written skills stay). The path must stay inside the project.
With agents_md on (the default), skills are written once to .agents/skills whatever this is set to.
bundle_exclude¶
Extra patterns for files that are left out of a skill's or command's bundled resources (references/, scripts/,
assets/): they are neither listed in the generated SKILL.md ## Resources section nor copied. Independently of
this key the bundle skips .git, .venv*, venv, __pycache__, *.pyc and node_modules, and, when the project
is in a git work tree, everything .gitignore ignores (tracked files and untracked files that are not ignored are
kept). A skill directory that is itself gitignored is bundled without the .gitignore filter.
A pattern without / matches any path segment (*.log, tmp); one with / matches the path relative to the skill
directory, or a directory on it. Patterns use glob syntax (*, ?, [a-z]). Skills fetched through includes and
installed_skills get the built-in list and .gitignore only.
mcp¶
Project-level MCP generation options.
[mcp]
self_server = true # default; add ai-rulez's own MCP server to .mcp.json
# self_server_version = "5.0.0" # default: latest, so a project picks up new releases
# self_server_launcher = "binary-then-npx" # use an installed ai-rulez, else npx
# self_server_command = ["ai-rulez", "mcp"] # replace the whole launch command instead of npx
# skills_instructions = false # leave the "Unlisted skills" section out of instruction files
# skills_instructions_text = "..." # replace that section; {{server}}, {{skills_dir}}
# serve_skills = true # a bare `ai-rulez mcp` serves skills, as --serve-skills does
# discover_skills = "repo" # mcp --serve-skills also serves the skills already in the repository
# index_cache = "user" # "user" (default), "repo" or "off": where the repository index is cached
# read_only = true # mcp registers only its read-only tools (--read-only)
# tools = ["find_skill", "load_skill"] # mcp registers only these tools (--tools)
# allow = ["review-*"] # mcp --serve-skills serves only matching skills (--allow)
# deny = ["draft-*"] # mcp --serve-skills never serves matching skills (--deny)
# daemon = true # mcp --serve-skills shares one server per repository (--daemon)
# no_watch = true # mcp --serve-skills does not reload on file changes (--no-watch)
# daemon_idle_timeout = "15m" # the shared daemon exits this long after its last client
# authoring = true # keep the authoring tools on a read-only skills server
# exclude = ["third_party/**"] # globs of paths repository discovery never reads (--exclude adds to them)
| Field | Description |
|---|---|
self_server |
When true (the default in v5), generate adds "ai-rulez": {"type": "stdio", "command": "npx", "args": ["-y", "ai-rulez@latest", "mcp"]} to the project .mcp.json, so a project is MCP-ready out of the box. When the project serves skills (a delivery other than static, a [[skill_sources]] entry or a role delivery) the entry runs mcp --serve-skills instead: one process serves the skills and the authoring tools, so no separate skills server is needed. Set false (or pass generate --no-self-mcp) to opt out. |
self_server_version |
Version (or dist-tag) to pin. Defaults to latest, so a project picks up new releases without regenerating. Only applies while the self server is on. |
self_server_launcher |
npx (the default) or binary-then-npx: run an installed ai-rulez when one is on PATH, otherwise npx (pinned by self_server_version). Generated as a sh -c script with --serve-skills passed as an argument. Mutually exclusive with self_server_command. |
self_server_command |
Executable followed by its arguments, replacing the npx launch. When the project serves skills, --serve-skills is appended unless it is already there; a shell launch (sh -c <script>) is never appended to, because the flag would become $0, so put --serve-skills in the script and end the command with a dummy "sh" argument (exec ai-rulez mcp "$@"). Mutually exclusive with self_server_version. Only applies while the self server is on. |
skills_instructions |
While skills are served, every instruction file gets a short "Unlisted skills" section telling the agent to call find_skill, then load_skill, on the MCP server that serves them, with the on-disk fallback (.ai-rulez/skills/<name>/SKILL.md). Default true; false leaves it out. Written when a configured preset can call MCP, and only into the root file of a harness that can; the shared AGENTS.md carries it once. |
skills_instructions_text |
Replaces the default section (write your own heading). {{server}} is the MCP server that serves the skills, {{skills_dir}} the config directory's skills folder (.ai-rulez/skills). Unknown variables, blank text, and text beside skills_instructions = false are configuration errors. |
serve_skills |
true makes a bare ai-rulez mcp behave as mcp --serve-skills, so a client entry needs no flag (default false). The flag overrides it: --serve-skills=false turns it off. generate still writes --serve-skills into the self-server entry when the project serves skills; the flag and the key agree. Skills-only keys (discover_skills, allow, deny, exclude, no_watch, daemon) that are set while skills are not served are ignored, and the server logs one warning naming them at startup. |
discover_skills |
off (the default), repo or all: whether mcp --serve-skills also serves the skills the repository already keeps (skills/, .claude/skills at any depth, the configured presets' skill directories). all adds the installed and included skills whatever their delivery. The --discover-skills flag overrides it. See Discovering skills already in the repository. |
exclude |
Globs, relative to the project root, of the paths repository discovery never reads: skill directories and the context index (CLAUDE.md, AGENTS.md, rules, agents) alike. ** crosses directories, a pattern without a slash matches at any depth. The mcp --exclude flag (repeatable) adds to the list. See Excluding paths. |
index_cache |
user (the default), repo or off: where the repository index of the discovery keeps its cache of item summaries and scan verdicts, never file bodies. user is the user cache directory, with a gitignored directory in the project (.ai-rulez/.cache/, or .ai-rulez-cache/ in the root layout) as the fallback when it is not writable; repo always uses that directory; off keeps none. See The index cache. |
read_only |
true registers only the read-only tools, as mcp --read-only does; the server instructions and prompts follow. The --read-only flag overrides it. An organization policy with [mcp] read_only = true cannot be loosened by it (AR740). |
tools |
The only tools the server registers, as mcp --tools does, for example ["find_skill", "load_skill", "find_context", "load_context"]. An unknown name stops the server and lists the valid ones. The --tools flag overrides it. |
allow, deny |
Glob patterns on the served skill names (mcp --serve-skills --allow and --deny); deny wins. The flags override them. |
daemon_idle_timeout |
How long the shared daemon outlives its last client, as a Go duration ("2m"); default 15 minutes. The daemon also exits when its repository directory is removed. |
authoring |
true keeps the authoring tools on a read-only skills server. Without it, read_only with serve_skills registers only find_skill, load_skill, search_skills, read_skill_file and list_skill_resources, a few KB of schema instead of about 92 KB. tools wins over both. |
daemon, no_watch |
mcp --serve-skills --daemon (one shared server per repository) and --no-watch (no live reload). The flags override them. |
The mcp serve keys are read when the server starts. See Settings in the configuration.
Behavior:
- On by default: a project gets the
ai-rulezMCP server without declaring anything;self_server = falseorgenerate --no-self-mcpturns it off. - Merged, not replaced: with
self_serverand no[[mcp_servers]], ai-rulez owns only themcpServers.ai-rulezentry. Other servers in an existing.mcp.json(and every other key) are preserved, so runninggeneratetwice produces no diff. - Every MCP writer agrees: the root
.mcp.jsonis rendered identically by themcppreset and the cursor/copilot writers, and theantigravitypreset adds the same entry to.agents/mcp_config.json. - With
[[mcp_servers]]: declared servers keep their existing behavior (ai-rulez owns the wholemcpServersobject, see Settings document merge behavior), and the ai-rulez entry is added to it unless you declare a server namedai-rulezyourself, which wins. - Per root: each config root decides independently, including under
generate --recursive. - The pinned version changes when you upgrade ai-rulez, so the generated
.mcp.jsonchanges with it. - Turning it off:
self_server = false(orgenerate --no-self-mcp) stops ai-rulez from owning the entry but does not delete it; removemcpServers.ai-rulezfrom.mcp.jsonyourself.ai-rulez cleanlikewise leaves a.mcp.jsonthat holds hand-authored servers in place.
Keeping directories out of generate --recursive¶
generate --recursive (and validate --recursive) searches every directory below the working directory for
configuration, except build output, dependency and hidden directories and testdata. A repository that keeps
deliberately invalid configurations elsewhere (test fixtures, golden corpora) lists them in the configuration of the
directory the run starts from (.ai-rulez/config.toml, .agents/ai-rulez.toml or .config/ai-rulez/config.toml):
recursive_exclude = ["tests/fixtures", "svc/*/gen"] # directories or globs, relative to the project root
generate --recursive --exclude <dir> adds to the list for one run.
mcp_servers¶
Inline MCP (Model Context Protocol) server definitions. A separate mcp.yaml, mcp.toml or mcp.json is no longer read.
[[mcp_servers]]
name = "ai-rulez"
command = "npx"
args = ["-y", "ai-rulez@latest", "mcp"]
[[mcp_servers]]
name = "grafana"
command = "uvx"
args = ["mcp-grafana"]
env = { GRAFANA_URL = "http://localhost:3000", GRAFANA_SERVICE_ACCOUNT_TOKEN = "${GRAFANA_SERVICE_ACCOUNT_TOKEN}" }
Each entry supports:
| Field | Required | Description |
|---|---|---|
name |
Yes | Unique server identifier |
description |
No | Human-readable description of the server |
command |
No | Command to run for local stdio servers (npx, uvx, ai-rulez, etc.). May contain ${PROJECT_ROOT}. |
args |
No | Array of command arguments for local servers. Elements may contain ${PROJECT_ROOT}. |
env |
No | Environment variables as key-value pairs. Values may contain ${VAR} placeholders. |
transport |
No | stdio, http, or sse. Defaults to stdio. |
type |
No | The harness's type value (stdio, http, or sse), an alias of transport. When both are set they must agree; an unset type falls back to transport. |
url |
No | Remote MCP server URL for http or sse transports. |
headers |
No | HTTP headers for http or sse servers (e.g. auth). Values may contain ${VAR} placeholders. |
oauth |
No | Pre-registered OAuth client of a hosted (http/sse) server: client_id, callback_port (1–65535) and scopes. Rendered only by harnesses whose MCP format has an oauth block (Claude Code's .mcp.json, VS Code's .vscode/mcp.json, Codex's .codex/config.toml); a harness that cannot express it skips the server with a warning. |
enabled |
No | Set to false to skip the server in generated MCP outputs. Defaults to true. |
profiles |
No | Restrict the server to the named profiles. Omit to include it in every profile. |
domain |
No | Restrict the server to a domain: it is included only when the active profile or role selects that domain. Omit to include it in every selection. |
Local stdio servers normally need command; remote http and sse servers normally use url,
plus headers when the server needs auth:
[[mcp_servers]]
name = "remote-api"
transport = "http"
url = "https://mcp.example.com/mcp"
headers = { Authorization = "Bearer ${REMOTE_API_TOKEN}", X-Team = "platform" }
A hosted server that needs a pre-registered OAuth client (Slack, for example) carries an oauth block. The
type key is the same setting as transport, spelled the way a harness's file spells it:
[[mcp_servers]]
name = "slack"
type = "http"
url = "https://mcp.slack.com/mcp"
[mcp_servers.oauth]
client_id = "1234567890.1234567890"
callback_port = 8080
scopes = ["channels:read", "chat:write"]
oauth is written as { "clientId": ..., "callbackPort": ..., "scopes": [...] } in Claude Code's .mcp.json
and VS Code's .vscode/mcp.json, and as an [mcp_servers.<name>.oauth] table with client_id and
callback_port (plus a sibling scopes array) in Codex's .codex/config.toml. A harness whose MCP format
has no oauth block (Gemini, OpenCode, Zed, ...) leaves the server out of its file and warns, rather than
writing a server that could not authenticate. oauth requires a remote transport; a client_id is not
required by validate (a callback-port-only block relies on dynamic client registration), but scan reports
one that is missing as AR602.
headers is rejected on a stdio server, header names must be valid HTTP tokens (and unique ignoring
case), and values must not contain line breaks. Each preset writes them where its tool reads them
(headers in every MCP file ai-rulez generates). Headers are not included in distributable plugin
bundles: generate --plugin warns for each affected server and does not require header placeholders
to resolve.
A command or args value may use the ${PROJECT_ROOT} placeholder, which resolves to the
project root (the directory containing .ai-rulez/) during ai-rulez generate:
This is the portable way to pass an absolute project path to a server that requires one, without
hardcoding a machine-specific path. Because it resolves to an absolute path, the generated file
carrying it is machine-specific: keep the output gitignored or regenerate it per machine. In env
values, ${PROJECT_ROOT} also resolves this way unless a real PROJECT_ROOT is supplied via
--env, the process environment, or a dotenv file, in which case that value wins.
For Claude Code specifically, a project-scoped .mcp.json can instead use Claude's own
${CLAUDE_PROJECT_DIR:-.} token in command/args; ai-rulez passes it through unchanged, and it
stays portable across machines.
MCP env and header placeholders use ${VAR} syntax, where VAR must match [A-Za-z_][A-Za-z0-9_]*. They are
resolved during ai-rulez generate from repeated --env KEY=VALUE flags, process environment
variables, then dotenv files. By default, ai-rulez loads .env from the generation base directory.
If any --env-file PATH flags are supplied, the default .env is not loaded; files are merged in
flag order, with later files winning. Generation fails if a placeholder cannot be resolved.
Generated MCP config files contain resolved values. If any resolved MCP env or header value comes
from a placeholder, or if an env key or header name looks sensitive (TOKEN, SECRET, PASSWORD,
KEY, CREDENTIAL, or the Authorization, Proxy-Authorization and Cookie headers), generation
fails before writing unless the generated MCP config path is covered by .gitignore or by the
patterns ai-rulez is about to add. The check looks at what is rendered: an MCP config file
(.mcp.json, .gemini/settings.json, .agents/mcp_config.json, opencode.json, a scoped variant such as
packages/web/.mcp.json, ...) is protected only when it holds a resolved secret value. A file that only refers to the
variable (${env:TOKEN}) and a merged document that is not an MCP config (a review check file) need no ignore entry.
Resolved secret values are redacted before source-hash calculation, but generated MCP config files contain the actual
resolved values. Claude Code reads project servers from .mcp.json, which references ${VAR}; .claude/settings.json
carries no mcpServers, and a mcpServers entry an earlier version wrote there is removed on the next generate.
Environment references. Where a tool expands environment references in its own MCP config, a value that
came from a ${VAR} placeholder resolved from the process environment is written as that tool's reference
instead of the secret. A value from --env, --env-file, .env, ${PROJECT_ROOT} or an unresolved lenient
placeholder is always written resolved, and a file that holds only references is not made owner-only.
| Tool (preset) | Reference | Where | Documentation |
|---|---|---|---|
Claude Code and the readers of the root .mcp.json (mcp, codebuddy, commandcode, reasonix, cursor, copilot) |
${VAR} |
.mcp.json env and headers |
https://code.claude.com/docs/en/mcp |
Gemini CLI (gemini) |
${VAR} |
.gemini/settings.json env and headers |
https://geminicli.com/docs/tools/mcp-server/ |
Amp (amp) |
${VAR} |
.amp/settings.json env and headers |
https://ampcode.com/docs/customize/mcp |
Pi (pi) |
${VAR} |
.pi/mcp.json env and headers |
https://pi.dev/docs/latest/mcp |
Factory Droid (factory) |
${VAR} |
.factory/mcp.json env and headers (not url, args) |
https://docs.factory.com/cli/configuration/mcp |
OpenCode (opencode), Kilo (kilo) |
{env:VAR} |
environment and headers |
https://opencode.ai/docs/config/ |
Cursor (cursor), Devin (devin) |
${env:VAR} |
.cursor/mcp.json, .devin/mcp_config.json |
https://docs.devin.ai/desktop/cascade/mcp |
Codex (codex) |
env_vars, env_http_headers, bearer_token_env_var |
~/.codex/config.toml / .codex/config.toml |
Codex configuration reference |
Codebuff (codebuff), Crush (crush) |
$VAR (whole value only) |
env and headers | tool documentation |
A reference with a delimiter (${VAR}, ${env:VAR}, {env:VAR}) may sit inside a longer value
(Bearer ${TOKEN}); $VAR is written only when the value is exactly one placeholder, because $TOKEN_v2 would
read another variable.
The root .mcp.json is one file shared by several presets, so every writer renders the same bytes. It carries
${VAR} references only for upper-case names (CodeBuddy expands no others). qoder documents no expansion, so it
writes the resolved value (owner-only). Alone, or with presets that do not read .mcp.json, the whole file stays
resolved. Together with a preset that expands references (claude, cursor, copilot, codebuddy, commandcode,
reasonix), the two renderings differ and generate fails naming both presets, rather than putting a resolved secret
into a file a reference-reading tool shares; supply the secret with --env or .env to use both. Tools whose documentation
promises no expansion (junie, antigravity, zed, trae, cline, xum, Claude Desktop, VS Code's .vscode/mcp.json)
and tools that expand only after an opt-in (kiro: "Mcp Approved Env Vars") keep the resolved value.
Settings document merge behavior¶
Files such as .claude/settings.json, .mcp.json, .amp/settings.json, .gemini/settings.json,
.agents/mcp_config.json and .pi/mcp.json are shared documents: ai-rulez owns specific top-level keys
(mcpServers for MCP config, amp.anthropic.effort for Amp) and the consumer owns everything else.
Generation replaces only the owned keys and preserves every other member byte-for-byte, including
the document's original indentation. A hand-formatted document (a one-line nested object, for example) is edited in
place, so clean restores its original bytes. The manifest records what ai-rulez merged as digests, never as values,
so a resolved secret in a merged server entry is not copied into .generated-manifest.local.json.
Important implications:
- MCP servers are owned one by one: ai-rulez owns the servers it writes into
mcpServers(mcp.<name>inopencode.json,serversin.xum/mcp.jsonc), not the whole object. A server you added by hand under a name that is not inconfig.tomlsurvivesgenerateandclean. A server dropped fromconfig.tomlis removed on the nextgenerate, and only while it still holds the value ai-rulez wrote: an entry you edited is yours, stays, and is reported once. If a hand-written server has the same name as a configured one, the configured value wins ongenerateand is the one recorded. - Hooks, permissions and managed keys: top-level
[[hooks]],[permissions]and[claude.settings.managed]addhooks,permissions.allow|ask|deny,envandskillOverridesto the shared documents, owned element by element (hook groups, rules) or entry by entry (env, skill overrides). Hand-authored keys, groups and rules in the same arrays survivegenerateandclean. See Hooks and permissions. - Plugin keys of
.claude/settings.json: with[claude.settings] manage = true, ai-rulez also ownsextraKnownMarketplaces.<marketplace.name>and the listedenabledPlugins.<plugin>@<marketplace>entries, each entry on its own; other marketplaces and plugins in those objects survive. - Written only when there is something to contribute: A settings document is emitted only when
the config declares MCP servers (or, for Amp, a resolved effort tier; for Claude, managed plugin keys). The
geminiandantigravitypresets previously wrote their settings document on every run purely to self-register the ai-rulez MCP server; they no longer do, so a project with no[[mcp_servers]]keeps whatever is already at.agents/mcp_config.jsonuntouched. The exceptions are.gemini/settings.json(context.fileName) andopencode.json(instructions), which carry the entry that loads machine-local content. - JSONC: A document with comments or trailing commas is merged in place: ai-rulez adds or updates
only the keys it owns (the MCP servers, the Gemini
context.fileNameor OpenCodeinstructionsentry) and every comment and the rest of the document stay as written. - Taking keys back out:
ai-rulez clean, andgenerateafter a preset or server is removed, remove the keys ai-rulez merged in, as long as they still hold the value it wrote, and keep the rest of the document; see Settings documents shared with you. - Gitignore behavior: A document still holding keys ai-rulez does not own is treated as the
user's file: it is NOT added to the managed
.gitignoreblock and is NOT deleted as stale. This preserves hand-authored settings such as Claude'spermissions,env,model, andstatusLine. A document holding only ai-rulez's own keys remains a generated artifact and is still gitignored (keeping resolved MCP secret values out of git).
plugins¶
Plugins to install from a configured marketplace. This is the consumer side of the plugin
config. It is distinct from the producer [plugin] block
(see Authoring Plugins) that packages this project as a plugin.
[[plugins]]
marketplace = "official" # name of a [[marketplaces]] entry
name = "my-plugin"
scope = "project" # project or user; defaults to project
enabled = true # defaults to true
No effect
[[plugins]] no longer writes .claude/plugins.json or .codex/plugins.json: Claude Code and Codex do not
read them. The table is still accepted, and generate warns when it is set. Claude Code records installs in
.claude/settings.json (enabledPlugins, extraKnownMarketplaces); Codex enables plugins with
[plugins."name@marketplace"] enabled = true in .codex/config.toml. Use
[claude.settings] (manage = true with enable_plugins) for Claude Code, and keep the
Codex entry yourself; ai-rulez does not write .codex/config.toml plugin entries because it owns that file
outright. Delete the files an earlier version left behind. The Copilot
enabledPlugins settings syntax is not shown in the vendor documentation, so it is not generated.
plugin¶
The producer block: packaging metadata for a distributable plugin bundle (generate --plugin, publish,
--emit agent-plugins). Skills, commands and agents come from the content tree; this block only names and describes
the package. name and version are required. See Authoring Plugins and
Agent Plugins.
[plugin]
name = "acme.tools" # required
version = "1.2.0" # required, semantic version
description = "Acme review tooling."
runtimes = ["agent-plugins"] # default: all runtimes
spec = "1.1.0" # Agent Plugins version: "1.0.0" (default) or "1.1.0"
| Key | Default | Meaning |
|---|---|---|
name, version |
required | Package name and semantic version |
display_name, description, homepage, repository, license, category, brand_color, icon, logo |
empty | Manifest metadata |
keywords, tags |
empty | Search terms |
runtimes |
all | Runtimes to build: claude, cursor, codex, gemini, kimi, opencode, factory, hermes, agent-plugins, copilot |
spec |
1.0.0 |
Agent Plugins specification of the agent-plugins, copilot and root-layout codex packages: 1.0.0 or 1.1.0 |
content_root |
content tree | Project-relative directory holding plugin-only skills/, commands/ and agents/ |
include_domains |
none | Domains (names or globs) whose skills, commands and agents are bundled; a root item wins over a same-named domain item |
include_evals |
false |
Bundle each skill's evals/ directory and the project-level evals/ tree |
skills_dir_owned |
false |
Declare the bundle's skills directory fully owned: verify --plugin reports entries it did not generate as extra and generate --plugin removes them |
To publish several plugins from one root, use [[plugins_authoring]] instead of [plugin] (and
[[marketplaces_authoring]] for several marketplace indexes); see
Several plugins from one root. A definition file
.ai-rulez/plugins/<name>.toml or .ai-rulez/marketplaces/<name>.toml holds one entry's keys.
Sub-tables: [plugin.author] (name, email, url), [[plugin.mcp]], [[plugin.hooks]], [plugin.statusline],
[plugin.codex] (manifest = legacy, root or both; marketplace), [plugin.cursor], [plugin.gemini],
[plugin.kimi], [plugin.hermes] and [plugin.interface].
plugin.interface¶
The rich UI block of the Codex and Kimi manifests (the Codex root layout writes it under
extensions["com.openai"].interface, keys sorted). No key is required and unknown keys are rejected.
| Key | Type |
|---|---|
display_name, short_description, long_description |
string |
developer_name, category |
string |
capabilities, default_prompt, screenshots |
list of strings |
website_url, privacy_policy_url, terms_of_service_url |
string |
brand_color, composer_icon, logo, logo_dark |
string |
llms_txt¶
Controls the llms.txt written by the opt-in llms-txt preset and linted by validate --strict (AR9P0 to
AR9P6). The table has no effect without the preset. See llms.txt.
presets = ["llms-txt"]
[llms_txt]
dir = "" # default: the project root
title = "Acme" # default: name
summary = "Acme coding rules." # default: description
full = true # also write llms-full.txt (default: false)
include = ["rules", "context", "skills"] # default; also agents, commands
agents and commands are listed in the Optional section. dir must not be inside .git or the configuration
directory.
marketplaces¶
Plugin marketplaces to register. ai-rulez records these sources but does not currently emit any
marketplace output for them; a [[plugins]] entry references a marketplace by name.
[[marketplaces]]
name = "official"
source = "https://github.com/org/marketplace" # GitHub repo, git URL, local path, or URL
type = "github" # github, git, local, or url
placement¶
Decides whether skills and commands are generated into .claude/skills (core, the default) or
shipped only through a plugin. Absent block: nothing changes. See
Keep a skill out of .claude/skills.
[placement]
default = "core"
plugin = ["domains/*"]
core = ["domains/backend/python-conventions"]
honor_targets = false
claude.settings¶
Opt-in ownership of extraKnownMarketplaces.<marketplace.name> and enabledPlugins.<plugin>@<marketplace>
in .claude/settings.json, merged like mcpServers (see
Settings document merge behavior). See
Register the marketplace in .claude/settings.json.
claude.skills¶
By default a skill is user-invocable and appears in Claude Code's / menu. hide_from_menu = true writes
user-invocable: false on every skill that does not set the key itself, so only the model loads it. A user-invocable
value in a skill's frontmatter always wins. See Skill Frontmatter.
codex¶
project_doc_max_bytes is the limit your Codex is configured with (default 32 KiB). generate warns when the
AGENTS.md files Codex concatenates exceed it, naming the largest sections; 0 turns the warning off. See
Rules: size limits.
[claude.settings.managed] additionally owns env.<NAME> and skillOverrides.<skill> entries without
manage = true; [[hooks]] and [permissions] render the hooks and permission rules. See
Hooks and permissions.
hooks and permissions¶
Top-level [[hooks]] (lifecycle hooks for 36 harnesses, from claude, codex, cursor, gemini and copilot to
qwen, kiro and the plugin-based opencode, kilo, pi and amp) and
[permissions] (allow, ask, deny rules, translated for 24 harnesses with a native permission
surface) are rendered into each harness's native settings file outside any plugin, merged key by key so
hand-authored content survives, comments in JSONC, TOML and YAML included. See
Hooks and permissions, Permissions and the per-harness
feature matrix.
ai-rulez generate --user renders a user config into the per-user directories each harness reads; see
User-level configuration.
session_start¶
[session_start] is on by default for a project that generates for Claude Code: generate adds a SessionStart hook
to .claude/settings.json that runs ai-rulez session-start. In a remote (cloud) session, where CLAUDE_CODE_REMOTE
is true, it regenerates the outputs of the fresh clone and injects a short briefing; in a local session it does
nothing. Keys: generate (false removes it), when (remote or always), mode (auto, locked, frozen,
plain), timeout, version, command, prompt and prompt_text. See
Run generate when a remote session starts.
builtins¶
Enables built-in domains that ship embedded in the ai-rulez binary. These provide opinionated rules,
skills, agents, context, and commands without needing external includes.
Omit the builtins field entirely and no builtin content is loaded, auto-included domains included.
Auto-inclusion applies only once the field is present, so the minimum opt-in is builtins = true or any
array value.
Enable all builtins¶
Disable all builtins (including auto-includes)¶
Enable specific builtins¶
An unknown positive entry (a typo such as builtins = ["rsut"]) fails validate and the load with the valid
choices; unknown !exclusions stay warnings.
Scope a builtin to a profile¶
A builtin pack can be named in a profile's domain list with the builtin: prefix. The pack
is then loaded for that profile only, instead of every profile:
backend sees the docker pack; frontend does not. builtin:docker/container-standards scopes just that item. This works even when the root
builtins field is absent or set to false, because a profile reference is an explicit
opt-in. A pack the root builtins field already enables stays global — the prefix selects
a pack, it does not un-globalize one. Prefixing also keeps a builtin (builtin:rust) from
colliding with a local domain of the same name.
Precedence over a builtin skill¶
A builtin ranks below every skill the project has. A skill under .ai-rulez/skills/ replaces the builtin of the same
name; so does a hand-written repository skill such as .claude/skills/testing-conventions when
mcp --serve-skills --discover-skills serves it. validate reports either case as AR703 (silence it with
lint.allow_overrides = ["testing-conventions"]); generate logs the same collapse as a Duplicate skill collapsed
warning and honours the same list. generate never overwrites the hand-written file: it stops and names
the file, so the project skill is not clobbered by the generated builtin; to use the generated builtin, remove the
hand-written copy, otherwise exclude the builtin item (!<domain>/<item>).
Skills and commands share one output namespace (.claude/skills/{id}/SKILL.md), so the same holds across the two kinds:
a project skill replaces a builtin command of the same id, and a project command replaces a builtin skill. The builtin
is dropped, generate logs Duplicate command collapsed (or skill) and validate reports AR703, both silenced by
lint.allow_overrides. A skill and a command that are both project items, or both builtins, with one id remain an error.
Exclude auto-included builtins¶
ai-governance is auto-included whenever builtins are configured. Exclude it with !:
Drop the agents roster from root files¶
agent-delegation is auto-included, and it is what makes every root instructions file
(CLAUDE.md, AGENTS.md, …) end in an ## Agents section listing each agent's name and
description. Exclude the domain to drop that section:
The per-agent files (.claude/agents/*.md) are still generated, so no capability is lost — the
roster is a second copy of text the agent files already carry, and the root file is the copy read
on every request. On a tree with 32 agents the roster measures about 1,100 always-loaded tokens;
ai-rulez tokens reports it as the agents_delegation line so you can see the figure for your own
tree before deciding.
Exclude specific items from a builtin domain¶
When using the array form of builtins, you can exclude a single item from a domain while keeping the rest:
This loads everything from git-workflow except the commit-messages rule. The syntax is !domain/name, and
name matches an item of any type (rule, skill, agent, command or context file) by its bare name;
ai-rulez builtins show <domain> lists the names. The exclusion also applies to a pack loaded through a
builtin:<domain> profile reference. Per-item selection only works with the array form; builtins = true is a
boolean, so use the array form with explicit pack names instead.
Select a single item from a builtin domain¶
A positive domain/name loads only that item of the pack:
builtins = ["!ai-governance", "!agent-delegation", "!security", "!git-workflow", "!code-quality",
"!testing", "!token-efficiency", "docker/container-standards"]
This emits the container-standards skill and nothing else; docker is not loaded as a pack. The rules, in order:
domain/nameloads only that item of a pack that is not otherwise loaded whole.!domainremoves the pack, and adomain/nameentry still applies afterwards, in either order:["security/secrets-handling", "!security"]loads onlysecrets-handling.domainwins overdomain/name: the whole pack loads. An auto-included pack (security) loads whole unless it is excluded with!domain.!domain/namealways removes the item, whichever entry selected it.- A positive entry that names no pack or item (
builtins = ["nope"],"docker/container-standard") failsvalidatewith the valid choices and a did-you-mean. An unknown exclusion stays a warning.
In a profile, builtin:<domain>/<name> scopes one item the same way (default = ["builtin:docker/container-standards"]);
builtin:<domain> wins over item references to the same pack. A pack the root builtins field selects, even item by
item, stays global and wins over a profile reference to the same pack.
Available Built-in Domains¶
Run ai-rulez builtins list to see all available domains.
Universal (language-agnostic):
| Domain | Description |
|---|---|
ai-governance |
AI agent behavior governance (auto-included) |
agent-delegation |
Agent delegation instructions and listing (auto-included) |
security |
Security best practices and OWASP reference (auto-included) |
git-workflow |
Git workflow and commit conventions (auto-included) |
code-quality |
Code readability, error handling, and complexity (auto-included) |
testing |
Testing conventions and best practices (auto-included) |
token-efficiency |
Output efficiency and task automation (auto-included) |
cicd |
CI/CD pipeline standards and GitHub workflow conventions |
docker |
Container build, security, and deployment best practices |
observability |
Logging, metrics, health checks, and observability standards |
documentation |
Documentation standards and maintenance |
polyglot-bindings |
Cross-language binding and native FFI conventions |
default-commands |
Built-in slash commands (/iterate, /parallelize) |
Languages (per-language conventions):
rust, python, typescript, go, java, ruby, php, elixir, csharp, r
Bindings (FFI binding conventions):
pyo3, napi-rs, magnus, ext-php-rs, rustler, wasm, jni-rs, extendr, cgo, vite-plus
What Builtins Provide¶
Universal builtins carry opinionated content for their domain, split between always-on rules
(under the default split mode, written to .claude/rules/*.md; with [rules] mode = "inline",
inlined into CLAUDE.md) and on-demand skills (loaded only when relevant):
ai-governance— rules: read-before-write, minimal changes, verification before completion, systematic debugging, agent workflow, generated files, communication style, no AI signatures, explain reasoning. Skills:config-placement,skill-authoringsecurity— rules: input validation, secrets handling, least privilege, customer data boundary. Skills:owasp-quick-reference,dependency-awareness(with per-language audit tool recommendations)git-workflow— rules: conventional commits, atomic commits, branch hygiene, safe operationscode-quality— skills only:code-quality-standards(readability, complexity limits, anti-patterns, duplication, dead code, suppressions, markers),error-handlingtesting— rule: tests ship with the behaviour change. Skills:tdd-workflow,testing-conventions(naming, assertions, independence, anti-patterns)token-efficiency— rules: context preservation, output awareness. Skills:task-runner,incremental-approachagent-delegation— context: delegation instructions and the generated agent listingcicd— rules: pipeline standards, GitHub workflow conventionsdocker— skill only:container-standardsobservability— skill only:observability-standardsdocumentation— rules: inline docs, README standards, docs-with-code updates, ADR immutability. Skill:adr-workflowpolyglot-bindings— skills only: Rust-core/native ABI boundaries, FFI ownership, cross-language error conversion, binding paritydefault-commands— commands:/iterate(implementation + review cycles) and/parallelize(subagent task splitting)
Language builtins each provide a comprehensive conventions rule covering:
- Target language version and edition
- Linting and formatting tools (e.g.,
rufffor Python,oxfmt/oxlintfor TypeScript,clippyfor Rust) - Static analysis and type checking (e.g.,
mypy --strict,PHPStan level 9,Dialyzer) - Security/SAST tools (e.g.,
bandit,gosec,cargo audit,bundler-audit) - Testing framework and coverage tools with 80%+ threshold
- Package manager and lockfile conventions
- Benchmarking and profiling tools
- Anti-patterns specific to the language
Binding builtins provide FFI-specific conventions for Rust binding crates:
- Macro usage patterns (e.g.,
#[pyclass],#[napi],#[rustler::nif]) - Error mapping between Rust and target language
- Build and distribution workflow
- Performance considerations (GIL release, scheduler safety, bundle size)
- Anti-patterns (no panics, no blocking, thin wrapper principle)
Builtins as On-Demand Agent Skills¶
Convention-heavy builtins no longer put their content into CLAUDE.md, .claude/rules/ (or the other
always-loaded surfaces). Instead they emit as Agent Skills — .claude/skills/<id>/SKILL.md files with YAML
name: / description: frontmatter — that the assistant loads on demand only when the work is
relevant. This keeps the always-loaded governance file small while still shipping the full
conventions.
Builtins that emit as skills:
- Language domains:
rust,python,typescript,go,java,ruby,php,elixir,csharp,r - Binding domains:
pyo3,napi-rs,magnus,ext-php-rs,rustler,wasm,jni-rs,extendr,cgo,vite-plus polyglot-bindingsconventionsai-governance: theconfig-placementandskill-authoringentriesdocumentation: theadr-workflowentrysecurity: theowasp-quick-referenceanddependency-awarenessentriescode-quality, entirely:code-quality-standardsanderror-handlingtesting, all but one rule:tdd-workflowandtesting-conventionstoken-efficiency:task-runnerandincremental-approachdockerandobservability, entirely:container-standardsandobservability-standards
What stays inline is behavioural governance that has to land before the first file is read, where a skill
loaded after the fact is too late: the rules of ai-governance, all of git-workflow, security's
secrets-handling, input-validation, least-privilege and customer-data-boundary, token-efficiency's context-preservation
and output-awareness, and testing's test-alongside-code. Guidance that only matters once you are
already inside a specific activity — writing a test, writing an error path, writing a Dockerfile — is a
skill, because its description can name that trigger and its body then costs nothing until it fires.
Exclusions still work exactly the same for these now-skill entries. !domain drops a whole domain and
!domain/name drops a single item within it (array form of builtins only):
Merge Priority¶
Builtins have the lowest priority. Content is merged in this order:
- Builtins (lowest) — embedded in binary
- Includes — from git repos or local paths
- Project content (highest) — in your
.ai-rulez/directory (not to be confused with machine-local content under.ai-rulez/local/, see Local overlay)
If a local domain has the same name as a builtin, the local domain is used and the builtin is skipped entirely. The builtin pack is not merged into the
domain, so its other items are lost; generate and validate log a Builtin pack skipped warning once per pack that names
them. To get a pack item, define it in your own domain or rename the domain.
defaults¶
Top-level defaults that propagate into generated outputs when individual content files do not override them.
[defaults]
effort = "medium" # low | medium | high | xhigh | max | inherit
# Suppress agent frontmatter fields that a target tool would reject.
omit_agent_fields = ["model", "tools"]
[defaults.effort_by_preset]
codex = "high"
claude = "xhigh"
amp = "max"
[defaults.model_by_preset]
claude = "opus"
copilot = "gpt-5"
cursor = "claude-3.7-sonnet"
defaults.omit_agent_fields suppresses named agent frontmatter fields for every preset, so an
agent stays loadable where a field would be invalid — an unconfigured model or provider, or a tool
name the target tool does not recognize. Recognized values are model, effort, tools, and
description; an omitted field is simply not written. This trades strictness for loadability, which
is usually the right call when the same agent is generated for many tools.
defaults.effort sets the reasoning effort applied to every preset that supports it. Per-agent overrides (via agent frontmatter) win where the preset accepts per-agent effort; if neither is set, the field is omitted entirely.
defaults.effort_by_preset lets you override defaults.effort for specific presets. Per-agent metadata still wins. Useful when, for example, you want Codex to reason harder than Claude on the same project.
defaults.model_by_preset sets the agent model value per preset. Model strings are provider-specific (opus makes sense for Claude, gpt-5 for Copilot) so there is no provider-neutral defaults.model scalar — every entry is preset-scoped. Per-agent <preset>_model frontmatter still wins; the legacy single-value model field on an agent acts as the lowest-priority fallback. Presets that do not emit a per-agent model frontmatter (codex, antigravity) ignore entries for their preset.
OpenCode models must be provider/model (for example anthropic/claude-sonnet-4-5). OpenCode reads a bare alias such as sonnet as a provider with no model: it drops the agent file or fails the session with Model not found. When the resolved model for opencode is not provider-qualified, ai-rulez warns, omits model, and the agent inherits the session's model. To pin a model for OpenCode only, set opencode_model in the agent frontmatter or [defaults.model_by_preset] opencode = "provider/model"; either wins over the shared model field, so the same agent can keep model: sonnet for Claude. OpenCode agent hidden is written as a boolean, and temperature and top_p as top-level numbers; values that do not parse are omitted with a warning.
Resolution order (per preset, per agent):
- Per-agent
effortin agent frontmatter (Claude, Codex, Devin, Opencode, Xum, pi — presets that support per-agent effort) defaults.effort_by_preset[<preset>]defaults.effort- Omit
For models the order is:
- Per-agent
<preset>_modelin agent frontmatter (e.g.claude_model,copilot_model) defaults.model_by_preset[<preset>]- Per-agent legacy
modelfield - Omit
Per-preset support matrix — each preset accepts a different vocabulary, and ai-rulez maps your value to the closest tier the preset supports:
| Preset | Where it's emitted | Field | Notes |
|---|---|---|---|
claude |
.claude/agents/<id>.md frontmatter |
effort |
Per-agent. Full vocabulary including max. inherit is not a Claude effort value and is dropped. |
codex |
.codex/agents/<id>.toml (per-agent) and .codex/config.toml (global default) |
model_reasoning_effort |
Per-agent override beats global .codex/config.toml. max → high; inherit dropped. |
amp |
.amp/settings.json |
amp.anthropic.effort |
Global only. xhigh → high. |
devin |
.devin/agents/<id>.md frontmatter |
reasoning_effort |
Per-agent. max → high; inherit dropped. |
opencode |
.opencode/agents/<id>.md frontmatter |
variant |
Per-agent. A separate variant: key beside a plain provider/model (the model#variant form is opencode.json only); a #variant in the source model is split off. xhigh and max → high; inherit dropped. |
xum |
.xum/agents/<id>.md frontmatter |
ai.thinkingLevel |
Per-agent. xhigh and max → high; inherit dropped. |
pi |
.pi/agents/<id>.md frontmatter |
thinking |
Per-agent. Full vocabulary; inherit dropped. |
cursor, copilot, gemini, junie, antigravity, cline |
— | — | These tools either gate effort behind UI toggles or read it from user-managed config files. ai-rulez does not emit anything for them; configure effort in the tool's own settings. |
Per-preset model matrix — presets that emit a model value in their agent frontmatter:
| Preset | Per-agent frontmatter key | Emitted field |
|---|---|---|
claude |
claude_model |
model in .claude/agents/<id>.md |
copilot |
copilot_model |
model in .github/agents/<id>.agent.md |
cursor |
cursor_model |
model in .cursor/agents/<id>.md |
cline |
cline_model |
modelId in .cline/agents/<id>.yaml |
junie |
junie_model |
model in .junie/agents/<id>.md |
opencode |
opencode_model |
model in .opencode/agents/<id>.md |
devin |
devin_model |
model in .devin/agents/<id>.md |
gemini |
gemini_model |
model in .gemini/agents/<id>.md (Gemini) |
xum |
xum_model |
ai.model in .xum/agents/<id>.md |
pi |
pi_model |
model in .pi/agents/<id>.md |
A bare Claude alias (sonnet, opus, haiku) is not a Gemini model, so gemini omits it with a warning and the agent inherits the session model; gemini_model and defaults.model_by_preset.gemini are written as given.
rules¶
Controls how rules are written to generated outputs.
rules.mode:split(the default) writes one file per rule in the tool's native rules folder;inlineembeds rules in the root file, moving only path-scoped rules to the folder.rules.mode_by_preset: per-preset override that beatsrules.mode. Keys are built-in, custom, or provider preset names.rules.baz_scoped:nested(the default) writes the path-scoped rules and context of thebazpreset to theAGENTS.mdof the directory their globs point into;rootkeeps them in the rootAGENTS.md. See Baz.rules.baz_default:all(the default) nests every path-scoped item into the Baz directory files;nonenests only items whosetargetsselectbaz, leaving the rest in the rootAGENTS.md.rules.baz.include: withbaz_default = "none", the rule and context names (list) that are nested for Baz in addition to the items whosetargetsselectbaz;baz: truein an item's frontmatter does the same. A!bazentry intargetsalways keeps an item out of the Baz nested files. See Baz.rules.baz_rest:root(the default) leaves a glob-activated item that baz does not nest (unselected underbaz_default = "none", or excluded by!baz) inlined in the rootAGENTS.md;skipleaves it out of that file whenbazis the only preset writing it, so an opt-in baz surface stays small. See Baz.rules.baz_unscoped:root(the default) inlines a glob-activated item that cannot be nested (**/*.go,**/BUILD,.mcp.json) into the rootAGENTS.md;skipleaves it out of that file whenbazis the only preset writing it. See Baz.rules.baz_dedupe:truewrites an item whose globs point into several sibling directories under one parent once, to that parent'sAGENTS.md, instead of one copy per directory. Defaultfalse.rules.baz_dedupe_max: when above0, an item that would be copied into more than that many directories is written once to the directory covering all of them, up to the repository root (the rootAGENTS.md). Works withoutbaz_dedupe; withbaz_unscoped = "skip"the root fallback is not used and the copies stay. Default0(off).rules.baz_budget: byte budget of the BazAGENTS.mdsurface (root plus nested);generatewarns when it is exceeded, naming the largest files. Default0(off).
Set mode = "inline" to restore the pre-4.22.0 output. Per-tool output, fallbacks and caveats are in Rules and native rules folders.
okf¶
Settings for the opt-in okf preset, which keeps an OKF bundle in sync with the sources:
presets = ["claude", "okf"]
[okf]
dir = "docs/okf" # bundle directory, relative to the project root (default)
include = ["rules", "context", "skills"] # kinds exported; default all of rules, context, skills, agents, commands, checks
spec = "0.2" # the only OKF spec version implemented
dir must stay inside the project and outside the configuration directory and .git. The bundle is committed
documentation, so gitignore never ignores it, and every file is written without a generated-by banner because
OKF requires frontmatter at the start of each concept.
lint¶
Tunes ai-rulez validate: severities, ignores, allow-lists, description bounds, size budgets
([lint.budgets.<kind>]), tolerated findings per rule ([lint.ratchet], formerly [lint.budget] and [lint.tolerate]) and required
frontmatter keys.
[lint]
fail_on = "warning"
analyzers = ["security", "references"] # run only these analyzers
ignore = ["AR803"]
[lint.severity]
AR401 = "error"
[lint.ratchet] # tolerated findings per rule: AR401 may have up to 12
AR401 = 12
[lint.budgets.skill] # size budget of a content kind
max_lines = 400
[lint.require_metadata]
skill = ["owner"]
Every key, the finding codes and the exit codes are in Strict validation.
An organization policy bounds severity, ignore and security from outside the repository: a repository can raise them, not lower them.
verifiers¶
Deterministic, read-only repo checks, run by ai-rulez verifiers run. One [[verifiers]] table per check; the guide with
semantics and examples is Verifiers.
[[verifiers]]
name = "node-version"
description = "package.json pins Node"
type = "key_equals"
path = "package.json"
key = "engines.node"
equals = ">=20"
severity = "error"
| Field | Applies to | Meaning |
|---|---|---|
name |
all | Required. Letters, digits, ., _, -; unique. The merge key of the machine-local overlay |
type |
all | Required: file_exists, file_absent, glob_count, regex, forbid, key_equals, generated_in_sync |
description |
all | Free text shown by list and in reports |
severity |
all | error (default), warning or info. Only errors, and warnings with --strict, fail the run |
path |
file_exists, file_absent, key_equals |
Project-relative path that stays inside the root |
glob |
glob_count, regex, forbid |
Files to select; **, *, ?, {a,b} (at most 64 expanded alternatives per glob) |
exclude |
glob_count, regex, forbid |
Globs removed from the selection |
pattern |
regex, forbid |
RE2 expression, matched against the whole file |
min, max |
glob_count |
Inclusive, non-negative bounds on the match count; at least one is required |
key |
key_equals |
Dotted path; \. is a literal dot, ["a.b"] quotes a segment, [0] or .0 indexes a list |
equals |
key_equals |
Expected scalar, compared as text |
profile |
generated_in_sync |
A defined profile (or default); empty selects the default |
A field that does not apply to the type is a configuration error, as is a glob that does not compile, a key_equals path
that is not .json, .yaml, .yml or .toml, and a profile that is not defined. config.local.toml may declare
[[verifiers]] too; entries merge by name.
verifiers_settings¶
Limits and policy of ai-rulez verifiers run (the name differs from the array because TOML cannot use one key as both).
[verifiers_settings]
max_timeout_s = 300 # cap on a command predicate's timeout_s (1 to 900)
max_file_bytes = 5242880 # largest file a content predicate reads (default 5 MiB)
require_examples = false # AR9H6 for a spec verifier without examples
warn_dead = false # AR9H5 for a when_changed that matches no file, on every run
trust_exec_from = [] # includes whose verifiers may use the command predicate (also pin them in the lock)
command_env = [] # extra environment variable names passed to command predicates
Credential-looking and proxy names in command_env and unknown includes in trust_exec_from are configuration errors.
The table is pinned in the lock. See Verifiers.
header¶
Configures the style of headers in generated files. Headers provide context about ai-rulez, explain the folder structure, and instruct AI agents on proper usage.
[header]
style = "minimal" # Default: bare minimum header
# style = "compact" # Shorter header with key information
# style = "detailed" # Comprehensive header with full documentation
# timestamp = true # Emit the "Generated:" line (default: false)
# text = "..." # Override the generated prose (see "Custom header")
# hashes = "full" # Freshness lines: "content" (default), "full", or "none"
# mcp_line = false # Drop the "Use MCP server: ..." line (default: true)
# nested = "compact" # Header of a root inside another root: "compact" (default), "omit" or "inherit"
The default is minimal. Every style — including minimal — carries the "DO NOT EDIT"
warning and the injected Content-Hash freshness line (plus Source-Hash with hashes = "full"; see
hashes) that ai-rulez uses to detect whether a generated file changed since the last generate.
Only the amount of explanatory prose differs between styles.
hashes¶
Every generated file carries freshness lines in its header (a YAML comment inside the frontmatter for
skills and agents, an HTML comment in CLAUDE.md). hashes selects which ones:
| Value | Lines written | Effect |
|---|---|---|
content (default) |
Content-Hash |
Each header depends only on that file's own body. |
full |
Content-Hash, Source-Hash |
Adds the project-wide Source-Hash. |
none |
neither | No hash lines at all. |
Content-Hash is a hash of the file's own body and is stable. Source-Hash is a hash of the entire
source set (config, every rule, skill, agent, command, MCP server), so under full one edit changes the
Source-Hash line in every generated file. That is harmless for gitignored output, but when generated
files are committed it makes every change to .ai-rulez/ rewrite hundreds of files and makes concurrent
branches conflict, which is why it is not the default. With content or none, editing one skill changes only
that skill's output (and AGENTS.md/CLAUDE.md when the edit changes what they render).
With content or none, generate decides whether to rewrite a file by comparing the whole rendered
file to what is on disk (ignoring the Generated: text when timestamp = true), so a changed header
style, custom header or config directory still re-renders. Consequently a formatter that edits generated
files is undone by the next generate, as it would be for any committed-output check. clean does not
read these lines in any mode. verify --plugin uses its own per-bundle provenance (the
.ai-rulez-generated.json sidecar) and is independent of this setting. Switching modes rewrites every
file once. Any other value is rejected by validate.
mcp_line¶
minimal, compact and detailed headers tell agents which MCP server to use. The command is derived
from [mcp], the same settings that write the .mcp.json entry, so it matches the server the
project actually launches: npx -y ai-rulez@latest mcp by default, npx -y ai-rulez@4.1.0 mcp with
self_server_version, the self_server_command as written (ai-rulez mcp), and --serve-skills appended
whenever skills are served. The line is left out while self_server = false, because no ai-rulez server is
configured to point at. Set mcp_line = false to drop it from every header. A custom text is
never touched.
timestamp¶
Headers carry no Generated: line by default, so generated output is byte-reproducible: the same
sources generate the same bytes, output can be verified by content hash, and two files rendered from
the same sources — CLAUDE.md and AGENTS.md, which are otherwise identical — cannot drift apart.
Set timestamp = true to stamp each header with the time of the run:
One run resolves the timestamp once and stamps every file it writes with that value, so sibling
outputs always agree even when a large project takes more than a second to render. Set
SOURCE_DATE_EPOCH to a Unix second count
to pin it, keeping output reproducible with the line in place:
A value that is not a parsable integer is ignored and the wall clock is used.
Header Styles¶
minimal (default)
- Only critical information
- Brief "DO NOT EDIT" warning
- MCP server reference
Content-Hashfreshness line (andSource-Hashwithhashes = "full")- Best for: keeping always-loaded files small (the default)
- Size: ~10 lines
compact
- Condensed version with essential information
- Uses symbols (✗/✓) for clarity
- Brief structure overview
- Best for: projects that want a short structure overview
- Size: ~20 lines
detailed
- Comprehensive explanation of ai-rulez
- Complete folder organization documentation
- Full AI agent instructions with MCP server promotion
- Best for: projects where AI agents need thorough context
- Size: ~50 lines
Custom header¶
When the predefined styles do not match your environment — for example the default banner tells
agents to use npx, but your project manages tools with mise — set text to replace the
generated prose with your own. It overrides style entirely (the style value is ignored while
text is set) and is written verbatim, wrapped in the output's comment syntax:
[header]
text = """
Built by the platform team.
Set up with: mise run setup
Regenerate with: ai-rulez generate
"""
A few details worth knowing:
- The freshness lines selected by
hashesare still appended inside the banner, so hash-based skip detection keeps working. Do not add a closing comment marker of your own. timestamp = truestill does nothing to a custom header; add aGenerated:line intextyourself if you want one.- Surrounding blank lines are trimmed; interior line breaks are preserved. Standard TOML multi-line
escaping applies (
"""for a literal block).
Nested roots¶
A root below the repository root names its own config in the header by its path from the git work tree
root, for example Source: products/em/webapps/.ai-rulez/config.toml, so "edit the source" leads to the
right file. The path comes from the work tree (the nearest ancestor holding a .git entry), so
generate, generate --recursive and generate --check render the same header. The repository root,
and a project outside a git work tree, keep the bare .ai-rulez/config.toml.
nested¶
A root that sits inside another ai-rulez root of the same git repository (some directory between it and
the repository root holds a config) would otherwise repeat the outer root's whole header in every
instruction file an agent loads. nested chooses what the inner root writes:
| Value | Header of the nested root |
|---|---|
compact (default) |
One line: Generated by ai-rulez from <path to its config>; do not edit. |
omit |
No header block. |
inherit |
The header of the configured style, as for the outer root. |
The outer root is never affected. compact still carries the freshness lines selected by hashes, so
the file keeps its Content-Hash. A custom text is kept for nested roots unless
nested is set explicitly. With omit the file has no Content-Hash: generate --check still compares
the whole rendered file and generate recognises its own output by the recorded manifest, but a
hand-edited file is then reported as blocked instead of edited.
Header Example (detailed)¶
<!--
🤖 AI-RULEZ :: GENERATED FILE — DO NOT EDIT DIRECTLY
Project: My Project
Generated: 2026-01-03 09:27:19
Source: .ai-rulez/config.toml
Target: CLAUDE.md
Content: rules=5, sections=0, agents=2
WHAT IS AI-RULEZ
AI-Rulez is a directory-based AI governance tool. All configuration lives in
the .ai-rulez/ directory. This file is auto-generated from source files.
.AI-RULEZ FOLDER ORGANIZATION
Root content (always included):
.ai-rulez/config.toml Main configuration (presets, profiles)
.ai-rulez/rules/ Mandatory rules for AI assistants
.ai-rulez/context/ Reference documentation
.ai-rulez/skills/ Specialized AI prompts
.ai-rulez/agents/ Agent definitions
Domain content (profile-specific):
.ai-rulez/domains/{name}/rules/ Domain-specific rules
.ai-rulez/domains/{name}/context/ Domain-specific documentation
.ai-rulez/domains/{name}/skills/ Domain-specific AI prompts
Profiles in config.toml control which domains are included.
INSTRUCTIONS FOR AI AGENTS
1. NEVER edit this file (CLAUDE.md) - it is auto-generated
2. ALWAYS edit files in .ai-rulez/ instead:
- Add/modify rules: .ai-rulez/rules/*.md
- Add/modify context: .ai-rulez/context/*.md
- Update config: .ai-rulez/config.toml
- Domain-specific: .ai-rulez/domains/{name}/rules/*.md
3. PREFER using the MCP Server (if available):
Command: npx -y ai-rulez@latest mcp
Provides safe CRUD tools for reading and modifying .ai-rulez/ content
4. After making changes: ai-rulez generate
5. Complete workflow:
a. Edit source files in .ai-rulez/
b. Run: ai-rulez generate
c. Commit both .ai-rulez/ and generated files
Documentation: https://github.com/Goldziher/ai-rulez
-->
Header Example (compact)¶
<!--
🤖 AI-RULEZ :: GENERATED FILE — DO NOT EDIT
Project: My Project | Generated: 2026-01-03 09:28:08
Source: .ai-rulez/config.toml | Target: CLAUDE.md
Content: rules=5, sections=0, agents=2
WHAT IS AI-RULEZ: Directory-based AI governance. Config in .ai-rulez/
STRUCTURE:
.ai-rulez/config.toml, rules/, context/, skills/, agents/ (root)
.ai-rulez/domains/{name}/ (profile-specific)
AI AGENT INSTRUCTIONS:
✗ NEVER edit CLAUDE.md (auto-generated)
✓ EDIT .ai-rulez/rules/*.md, .ai-rulez/context/*.md, .ai-rulez/config.toml
✓ USE MCP server: npx -y ai-rulez@latest mcp (provides CRUD tools)
✓ REGENERATE: ai-rulez generate
✓ COMMIT: both .ai-rulez/ and generated files
Docs: https://github.com/Goldziher/ai-rulez
-->
Header Example (minimal)¶
<!--
🤖 AI-RULEZ :: GENERATED FILE — DO NOT EDIT
Project: My Project
Generated: 2026-01-03 09:28:27
Source: .ai-rulez/config.toml
NEVER edit this file - modify .ai-rulez/ content instead
Use MCP server: npx -y ai-rulez@latest mcp
Regenerate: ai-rulez generate
Docs: https://github.com/Goldziher/ai-rulez
-->
installed_skills¶
Named skills installed from external repositories. Skills are fetched dynamically during ai-rulez generate and included in outputs. See Installed Skills for full details.
[[installed_skills]]
name = "kreuzberg"
source = "https://github.com/kreuzberg-dev/kreuzberg"
[[installed_skills]]
name = "ai-rulez"
source = "https://github.com/Goldziher/ai-rulez"
ref = "main"
[[installed_skills]]
name = "custom-lib"
source = "https://github.com/org/repo"
path = "libs/custom" # defaults to skills/<name>
local_override = "../local" # use local path for development
Each entry supports:
| Field | Required | Description |
|---|---|---|
name |
Yes | Unique skill identifier |
source |
Yes | Git URL, owner/repo or github:owner/repo, file:// URL, or local directory (~/path only from config.local.toml or the user config); see source forms |
path |
No | Path within repo to the skill directory, or to a directory of skill directories (defaults to skills/<name>; when omitted and nothing is there, the conventional harness skill directories are searched) |
ref |
No | Git ref (branch, tag, commit) |
local_override |
No | Local path override for development |
domain |
No | Tie the skill to a domain so profiles and roles select it by domain like local content. Omit for global. |
include |
No | Keep only skills whose directory name matches one of these globs |
exclude |
No | Drop skills whose directory name matches one of these globs; wins over include |
name_prefix |
No | Prepend this to every resolved skill name and output directory |
resources |
No | What is bundled besides references/, scripts/ and assets/: the whole skill directory by default (not SKILL.md, evals/, agents/, hidden files); setting it replaces that. See Installed skills |
trust |
No | Scan level of the imported skills: error (default) or warn |
Manage via CLI: ai-rulez skill install/remove/list.
installed_marketplaces and installed_plugins¶
Plugin marketplaces and the plugins imported from them (or straight from a repository). Skills, agents, commands,
rules, hooks and MCP servers become ordinary content, pinned in ai-rulez.lock. See
Installed Plugins for the formats, source forms, hook and MCP policy and the lock flow.
[[installed_marketplaces]]
name = "acme"
source = "acme/plugins"
version = "^1.2"
[[installed_plugins]]
name = "formatter"
marketplace = "acme"
exclude = ["hooks"]
| Field (marketplace) | Description |
|---|---|
name, source |
Required. source is a repository URL, GitHub owner/repo, a local directory inside the project or an https marketplace.json URL |
path, ref, version, tag_prefix, include_prerelease, min_release_age |
As on includes |
trust |
Scan level of the plugins read through it: error (default) or warn |
| Field (plugin) | Description |
|---|---|
name |
Required, unique |
marketplace, plugin |
Install the marketplace entry plugin (default name) from an installed marketplace; exclusive with source |
source, path, ref, version, tag_prefix, include_prerelease, min_release_age |
A plugin repository or directory, as on installed_skills |
format |
Force the layout: claude, codex, gemini, cursor, agent-plugins |
include, exclude |
Component kinds: skills, agents, commands, hooks, mcp, rules |
name_prefix, domain, profiles, trust |
As on installed_skills |
enable_hooks |
Import the plugin's hooks (default false) |
enable_mcp |
Import the plugin's MCP servers (default true) |
Manage via CLI: ai-rulez marketplace add/remove/list/update, ai-rulez plugin install/remove/list/update.
[skills], [domains.<name>], [[skill_sources]], [lock]¶
Dynamic skill loading (see Dynamic skill loading). All optional.
| Key | Meaning |
|---|---|
[skills] delivery |
Global default delivery: static (default), served or both. |
[domains.<name>] delivery |
Default delivery of a domain's skills. A skill's delivery frontmatter wins. |
[[skill_sources]] |
name, url, ref, path, include, exclude, name_prefix, trust (error or warn), domain (served only when the active profile or role selects it), max_skills (default 200), max_bytes (default 64 MiB), max_clone_bytes (default 256 MiB), max_clone_files (default 20000; each entry counts as at least 4 KiB toward max_clone_bytes; AI_RULEZ_MAX_CLONE_FILES sets it globally): skills served from a git repository or directory. A source over a limit is an error. |
[lock] enforce |
The skills server refuses a served skill whose digest is not pinned in ai-rulez.lock. |
[delivery.stub] |
Customises the generated dynamic-skills stub, see below. |
[delivery.stub]¶
The stub skill that tells an MCP-capable agent to call find_skill and load_skill is generated, and
its description is listed in every session. Each field is optional; an unset one keeps the built-in text:
[delivery.stub]
description = "Find and load team skills on demand." # frontmatter description
body_file = "stub.md" # relative to .ai-rulez/, or use body = "..."
| Key | Meaning |
|---|---|
description |
Replaces the frontmatter description. Must not be whitespace-only; an empty string is the same as leaving the key out. |
body |
Replaces the stub's text. Multi-line TOML (""") works. |
body_file |
Reads the text from a file relative to the config directory (.ai-rulez/); the path must stay inside it. A missing file is a load error. Mutually exclusive with body. |
The body may use one variable, {{server}}: the MCP server that serves the skills (the ai-rulez self
entry, or the [[mcp_servers]] entry running --serve-skills). Other {{...}} names are rejected, and the
body must still name find_skill and load_skill, since the stub is how an agent learns to call them.
Use it for local guidance such as which domains are served or how to rank matches. A skill you author
named dynamic-skills still replaces the stub, and AR991 is unaffected: the customised stub is the
same skill. Editing the file behind body_file shows as drift in generate --check.
[ard]¶
The Agentic Resource Discovery manifest written by publish --emit ard. Optional.
| Key | Meaning |
|---|---|
publisher |
Required. Fully qualified domain name the identifiers are anchored to. |
namespace |
Required. Identifier segment between publisher and name; colons separate sub-segments. |
base_url |
https location of skills/<name>/SKILL.md (written next to ard.json). |
plugin_type |
Media type of plugin entries. |
queries |
representativeQueries (1 to 5 each) by resource name. |
[publish]¶
What ai-rulez publish ships and its policy gates. All optional; it has no credential keys.
| Key | Meaning |
|---|---|
runtimes |
Plugin runtimes to publish (default: the [plugin] runtimes). --runtime overrides it. |
require_signature |
Fail unless the archive is signed (AR9N7). |
require_approved |
Fail unless every item [governance] selects has a valid approval (AR9N8). |
allow_dirty |
Publish from a tree with uncommitted changes. |
[publish.github_release] repo |
OWNER/REPO of the release. |
[publish.oci] ref |
Repository host/path (no tag) of the OCI target. |
[publish.npm] |
scope (@acme, required for the npm target), access (restricted or public), registry (https). |
[publish.marketplace.channels] |
Channel name to the git ref its pinned marketplace index points at. |
[[publish.emitters]] |
name (template, cursor-team-marketplace, port, aws-agent-registry, kiro-steering, agent-plugins, ard, mcp-server-card), template and output for the template emitter, options (for example blueprint for port). |
Layered configuration¶
Split the configuration across config.<name>.toml files beside config.toml and choose which apply. The active
layers are merged over config.toml in the order they are named, so a later layer wins; config.local.toml is applied
after all of them. config.toml always loads.
# .ai-rulez/config.ai.toml
[[mcp_servers]]
name = "basemind"
command = "basemind"
args = ["serve"]
[[installed_skills]]
name = "cloudflare"
source = "https://github.com/cloudflare/skills"
include = ["cloudflare", "workers-best-practices"]
# .ai-rulez/config.custom.toml
[[installed_skills]]
name = "cloudflare"
source = "https://github.com/cloudflare/skills"
include = ["cloudflare*"]
AI_RULEZ_LAYERS=ai,custom ai-rulez generate # custom layers after ai, so its include wins
ai-rulez config layers # list the layers and which are active
Layers become active in this order, each merged over the one before:
default_layersinconfig.toml: committed, always active, no environment variable needed. A layer listed there must have itsconfig.<name>.toml; a missing file is an error that names it. The key is read fromconfig.tomlonly and is ignored (with a warning) inside a layer or the local overlay.AI_RULEZ_LAYERS, a comma-separated list, orAI_RULEZ_PROFILEwhen that is not set, so one value can pick a profile and the layers named like it. The--profileflag does not select layers. A name with no file is skipped (sodefaultand profile names without a layer are harmless).config.local.toml, the machine-local overlay, always last.localis a reserved layer name.
A name repeated across these keeps its first position. Use default_layers to give a team-owned concern (lint
allow-lists, builtin exclusions, MCP servers) its own file and CODEOWNERS entry:
ai-rulez config layers prints every layer with its position and origin (default, env), in text and with
--format json. Errors for a layer name the layer file (unknown key(s) in layer config.lint.toml: bogus).
Layers merge like the local overlay: tables merge per key, named lists (mcp_servers, installed_skills, plugins,
includes, marketplaces, scopes, roles, verifiers, plugins_authoring, marketplaces_authoring) merge entry by
entry on name (remove = true drops one), [[marketplace.plugins]] merges the same way inside the [marketplace]
table, and any other value is replaced. [governance] and [signing] are shared policy and are ignored in a layer. A layered configuration is a
merged view and is never written back by a command that edits config.toml.
Local overlay¶
A config.local.toml file beside config.toml is a machine-local, gitignored overlay
that is merged onto this configuration in memory at load time. Use it for personal presets, profiles,
MCP servers and secrets. It accepts the same keys as config.toml (it is validated against
schema/ai-rules-local.schema.json; see Schema Reference) and merges as follows: scalars
and lists are replaced by the local value, tables merge per key, presets is an ordered union where
"!name" drops a shared preset, and the named lists (mcp_servers, plugins, includes,
installed_skills, marketplaces, scopes) merge by name, with remove = true deleting a shared
entry. Unknown keys, a mismatched version and more than one overlay file are errors, and plugin
bundles (generate --plugin) ignore the overlay.
The full merge table, the ai-rulez local commands, --local content under .ai-rulez/local/ and
the drift guard are documented in Local Configuration, which is the single
source of truth for this feature.
Directory Structure¶
Root Content (Always Included)¶
Content in these directories is always included in every generation:
.ai-rulez/
├── rules/ # Mandatory rules and constraints
├── context/ # Reference documentation
├── skills/ # AI skills/prompts
└── agents/ # Agent prompts
Rules directory: rules/
- Files:
*.mdmarkdown files - Purpose: Mandatory constraints, standards, do's and don'ts
- Included: in all generated outputs
Context directory: context/
- Files:
*.mdmarkdown files - Purpose: Reference documentation, architecture, guidelines
- Included: in all generated outputs
Skills directory: skills/
- Structure:
skills/{skill-name}/SKILL.md - Purpose: Specialized AI prompts and expert prompts
- Included: in all generated outputs
Agents directory: agents/
- Files:
*.mdmarkdown files - Purpose: Agent prompt files for supported tools
- Included: in all generated outputs
Domain Content (Profile-Specific)¶
Content in domain directories is included only when that domain is in the active profile:
.ai-rulez/domains/
├── backend/
│ ├── rules/
│ ├── context/
│ ├── skills/
│ └── agents/
├── frontend/
│ ├── rules/
│ ├── context/
│ ├── skills/
│ └── agents/
└── qa/
├── rules/
├── context/
└── agents/
Domain directories mirror the root structure:
domains/{name}/rules/- Domain-specific rulesdomains/{name}/context/- Domain-specific documentationdomains/{name}/skills/- Domain-specific AI skillsdomains/{name}/agents/- Domain-specific agent prompts
File Formats¶
Markdown Files¶
All .md files are treated as content. Optional YAML frontmatter is supported:
---
priority: high
targets:
- claude
- ".cursor/rules/"
custom_field: value
---
# Rule or Context Title
Your content here. Can include any markdown formatting.
Frontmatter Fields¶
priority (optional, string)
- Values:
critical,high,medium,low,minimal - Default:
medium - Controls the order of the Rules and Context sections in generated files (higher priority first, name order breaking ties)
targets (optional, array of strings)
- Selects which generated outputs include this content (not which source files it applies to; use
globs/pathsfor that): preset names, root files, file paths or base names, directory prefixes (.cursor/rules/), globs, or*. Applies to rules-folder files and root files. - If empty, included in all outputs
- See Targets for the match rules
globs / paths (optional, array of strings — rules)
- The files a rule applies to. The two keys are synonyms; either sets the same path scope. A
comma-separated string is split into several globs (commas inside
{}are kept). - A path-scoped rule is not inlined into the root instructions file. Each preset with a rules
folder (
.claude/rules,.cursor/rules,.github/instructions, ...) writes it there with native frontmatter; presets without one keep it inline under an_Applies to: ..._line. activation(always,glob,auto,manual) anddescriptionselect other modes. See Rules and native rules folders.
effort (optional, string — agents only)
- Values:
low,medium,high,xhigh,max,inherit - Sets the reasoning effort for a Claude Code subagent in its generated
.claude/agents/<name>.mdfrontmatter - Available levels depend on the model
- Falls back to
defaults.effortinconfig.tomlwhen not set, then to the session-level default - Presets that don't emit effort —
cursor,copilot,gemini,junie,cline,antigravity— omit it from their outputs. See the per-preset support matrix.
extends (optional, string — agents only)
- Inherits a lower-precedence agent of the given name and appends this agent's body to it
- See Extending Agents for resolution rules
Custom fields (optional)
- Any other YAML fields are preserved and available in custom templates
---
priority: high
author: engineering-team
review_date: 2025-01-01
tags: [security, performance]
---
SKILL.md Format¶
Skills should follow this structure:
---
priority: high
description: "Code reviewer expert for quality assurance"
targets: ["CLAUDE.md"]
---
# Code Reviewer Expert
You are an expert code reviewer with deep knowledge of:
- Code quality and maintainability
- Testing best practices
- Performance optimization
## Your Responsibilities
1. Review pull requests for correctness
2. Suggest improvements and refactoring
3. Verify test coverage
Content Merge Strategy¶
When generating with a profile, content is merged in this order:
- Root rules (
.ai-rulez/rules/) - Root context (
.ai-rulez/context/) - Root skills (
.ai-rulez/skills/) - Domain rules (for each domain in profile)
- Domain context (for each domain in profile)
- Domain skills (for each domain in profile)
Within each category, files are sorted by:
- Priority (critical → high → medium → low → minimal)
- Filename (alphabetical)
Deduplication by Name¶
When the same rule, context, skill, agent or command name appears in multiple sources, the generated output includes it only once. Precedence (highest to lowest):
- Root content (
.ai-rulez/rules/,.ai-rulez/context/, etc.) - Domain content (
.ai-rulez/domains/{name}/rules/, etc.) - Include-sourced content (
FromIncludedomains from external includes) - Builtin content (auto-included domains)
Example: If both a builtin git-workflow domain and a local rule define commit-messages, the local version is used and the builtin version is dropped.
Rules and context are inlined, so a duplicate there would render the same section twice. Skills and
commands are per-item files (.claude/skills/{id}/SKILL.md), so a duplicate is worse than doubled
output: both copies are written to one path and whichever lands last wins. Deduplication makes the
precedence above the one that actually reaches disk.
Shadowing a builtin skill with a project skill of the same name is a supported, intended pattern — it is a warning, never an error.
During ai-rulez generate and ai-rulez validate, a warning is logged for each deduplicated item:
Duplicate rule collapsed name=commit-messages kept=.ai-rulez/rules/commit-messages.md dropped=builtin://universal/git-workflow/rules/commit-messages.md
Duplicate skill collapsed name=go-conventions kept=.ai-rulez/skills/go-conventions/SKILL.md dropped=builtin://languages/go/skills/go-conventions/SKILL.md
Name Collision Handling¶
If a name appears in both root and a domain:
The root version takes precedence and the domain copy is dropped — the same precedence order as above. To keep a domain-specific version, give it a name no other layer uses, or remove the root copy.
Output ID Collisions¶
Deduplication resolves collisions across precedence layers. It cannot resolve two items in the
same layer: there is no precedence between them. Skills and commands both render to
.claude/skills/{id}/SKILL.md, differing only in the user-invocable frontmatter a command gets, so two
items in one scope resolving to one id means one silently overwrites the other. ai-rulez validate
(and generate) refuse instead, with the two source paths named.
Two skills, or two commands, in the same directory:
duplicate output ids: command "deploy" (.ai-rulez/commands/deploy.md) vs command "deploy" (.ai-rulez/commands/deploy/COMMAND.md)
The flat form (commands/deploy.md) and the directory form (commands/deploy/COMMAND.md) resolve to
the same id, so keeping both is the most common way to hit this. Delete one, or rename one side.
A skill and a command sharing an id:
skill and command ids collide in the output namespace: skill "review" (.ai-rulez/skills/review/SKILL.md) vs command "review" (.ai-rulez/domains/qa/commands/review.md)
This check pools root and every domain, because the output layout has no domain segment: a skill in
one domain and a command in another still land on the same path for any profile that activates both.
Ids are compared case-insensitively, since a macOS or Windows checkout treats Review/ and review/
as one directory.
Root shadowing a domain is not an error — it is the documented resolution above, and it applies to a command regardless of which form each side uses.
Extending Agents¶
Built-in and shared-module agents (code-reviewer, docs-writer, security-auditor, language
specialists, ...) give you a solid base. When you only need to add project-specific guidance, don't
copy the whole agent — extend it.
Agent precedence¶
Same-named agents resolve deterministically by layer, highest precedence first:
- Project — agents under your
.ai-rulez/agents/ - Include — agents pulled in from external includes
- Builtin — agents shipped with ai-rulez builtin domains
A higher-precedence agent replaces a lower-precedence one of the same name unless it uses extends.
Append a message with extends¶
Create .ai-rulez/agents/<name>.md with the same name, set extends to that name, and put your
additional instructions in the body:
---
name: code-reviewer
extends: code-reviewer
---
Also enforce, for this repo:
- No `.unwrap()`/`.expect()` in library code.
- Every `unsafe` block carries a SAFETY comment.
The generated code-reviewer is the base agent's full instructions plus your message appended. You
did not restate the base checklist — you added to it.
Frontmatter fields you set win over the base; fields you omit are inherited:
---
name: docs-writer
extends: docs-writer
model: opus # upgrade just this agent's model
effort: high
# tools omitted → inherited from the base docs-writer
---
When documenting the public API, include a runnable example in every supported language.
How resolution works¶
extends: <name> binds to the same-named (or explicitly named) agent resolved from the layers below
the current one:
- A project agent extends an include or builtin agent.
- An include agent extends a builtin agent.
- Chains compose in resolution order (builtin → include → project), across as many layers as extend one another.
If no lower-layer agent of that name exists, the agent degrades to a plain agent (your body only) with
the extends directive stripped, and generate logs a warning naming the missing target so a typo does
not pass unnoticed. An extends cycle degrades the same way.
Extend vs. redefine¶
- Extend (
extends:) — you want the base plus a few additions. Preferred; no duplication. - Redefine — omit
extendsand write a complete agent to fully replace the base of that name. A higher-precedence agent withoutextendsalways wins over the base regardless of frontmatter.
Configuration Examples¶
Small Project (Single Team)¶
version = "5.0"
name = "My Startup"
description = "Early-stage SaaS with React + Go"
presets = ["claude", "cursor"]
gitignore = true
Directory structure:
.ai-rulez/
├── config.toml
├── rules/
│ ├── code-style.md
│ └── testing.md
├── context/
│ └── architecture.md
└── skills/
└── code-reviewer/
└── SKILL.md
Medium Project (Multiple Teams)¶
version = "5.0"
name = "Enterprise Platform"
description = "Multi-team SaaS platform"
presets = ["claude", "cursor", "gemini"]
default = "full"
gitignore = true
[profiles]
full = ["backend", "frontend", "qa", "devops"]
backend = ["backend", "qa"]
frontend = ["frontend", "qa"]
qa = ["qa"]
devops = ["devops"]
Directory structure:
.ai-rulez/
├── config.toml
├── rules/
│ ├── general-standards.md
│ └── security.md
└── domains/
├── backend/
│ ├── rules/
│ │ ├── api-design.md
│ │ └── database.md
│ └── context/
│ └── backend-architecture.md
├── frontend/
│ ├── rules/
│ │ ├── component-guidelines.md
│ │ └── performance.md
│ └── context/
│ └── design-system.md
├── qa/
│ └── rules/
│ └── testing-strategy.md
└── devops/
├── rules/
│ └── deployment.md
└── context/
└── infrastructure.md
Complex Project (Multiple Presets)¶
version = "5.0"
name = "Advanced ML Platform"
description = "Research platform with team separation"
presets = [
"claude",
"cursor",
"gemini",
"devin",
{ name = "internal-guide", type = "markdown", path = "docs/AI_DEVELOPMENT_GUIDE.md" },
]
default = "full"
gitignore = true
[profiles]
full = ["research", "ml-ops", "infrastructure", "frontend"]
research = ["research"]
ml-ops = ["ml-ops", "infrastructure"]
frontend = ["frontend"]
Profile Design Patterns¶
Single Team (No Domains)¶
For projects with a single team, skip domains entirely:
Multi-Team Monorepo¶
For monorepos with multiple independent teams:
version = "5.0"
name = "platform"
presets = ["claude", "cursor"]
default = "full"
[profiles]
full = ["backend", "frontend", "mobile"]
backend = ["backend"]
frontend = ["frontend"]
mobile = ["mobile"]
Environment-Based Profiles¶
For different behavior in dev, staging, production:
version = "5.0"
name = "saas-app"
presets = ["claude"]
default = "production"
[profiles]
development = ["dev-guidelines"]
staging = ["staging-guidelines"]
production = ["production-guidelines", "security-hardened"]
Validation¶
ai-rulez validate checks the raw config file against the JSON schema
(schema/ai-rules.schema.json) — so an unknown key or a value outside an enum is reported rather
than silently dropped — and then runs the structural checks below. TOML is converted to JSON for
the schema check.
The structural checks cover:
versionis"5.0"nameis present and non-empty- All preset names are valid
builtin:<name>andbuiltin:<name>/<item>references in profiles name a real builtin or pack item- Positive
builtinsentries (<name>or<name>/<item>) name a real builtin or pack item - Profile references resolve (a missing domain is a warning, not a failure)
- File paths are valid
- MCP server definitions are well-formed
- A
config.local.*overlay, if present, passesschema/ai-rules-local.schema.jsonand merges into a valid config (--no-localskips this)
Programmatic Modification with CRUD Operations¶
ai-rulez provides CRUD (Create, Read, Update, Delete) commands to programmatically modify your configuration. This is useful for:
- Automation and scripting
- Integration with CI/CD pipelines
- Programmatic domain and rule management
- Integration with AI assistants via MCP tools
Manual File Editing vs CRUD Commands¶
Manual file editing:
- Direct control over content
- Use any text editor
- Better for complex content
- Version control friendly
CRUD commands:
- Automated directory structure creation
- Frontmatter generation
- Validation built-in
- Easier for scripting and automation
- Better for programmatic access
Domain Structure with CRUD¶
When you create a domain with ai-rulez domain add, the following structure is automatically created:
.ai-rulez/domains/my-domain/
├── rules/ # Domain-specific rules
├── context/ # Domain-specific documentation
├── skills/ # Domain-specific AI skills
├── agents/ # Domain-specific agents
└── commands/ # Domain-specific commands
This mirrors the root structure and allows you to organize content by ownership.
CRUD Command Categories¶
Domain Management:
ai-rulez domain add <name> # Create a domain
ai-rulez domain remove <name> # Delete a domain
ai-rulez domain list # List all domains
Content Management:
# Add content to root or domain
ai-rulez add rule <name> # Create a rule
ai-rulez add context <name> # Create context
ai-rulez add skill <name> # Create a skill
ai-rulez add agent <name> # Create an agent
ai-rulez add command <name> # Create a command
# Remove content
ai-rulez remove rule <name> # Delete a rule
ai-rulez remove context <name> # Delete context
ai-rulez remove skill <name> # Delete a skill
ai-rulez remove agent <name> # Delete an agent
ai-rulez remove command <name> # Delete a command
# List content
ai-rulez list rules # List all rules
ai-rulez list context # List all context
ai-rulez list skills # List all skills
ai-rulez list agents # List all agents
ai-rulez list commands # List all commands
Add --local to any add, remove or list command to work on the machine-local tree
.ai-rulez/local/ instead (see Local Configuration).
Include Management:
ai-rulez include add <name> <source> # Add an include source
ai-rulez include remove <name> # Remove an include
ai-rulez include list # List all includes
Profile Management:
ai-rulez profile add <name> <domains> # Create a profile
ai-rulez profile remove <name> # Delete a profile
ai-rulez profile set-default <name> # Set default profile
ai-rulez profile list # List all profiles
include add|remove, skill install|remove and profile add|remove|set-default also take --local,
which writes to the config.local.* overlay.
Frontmatter Generation¶
When adding content via CRUD commands, frontmatter is automatically generated:
You can override defaults:
Best Practices for Configuration Management¶
- Use CRUD for automation: Scripts, CI/CD pipelines, and programmatic changes
- Use file editing for complex content: When you need fine control over formatting
- Organize by domain: Put domain-specific rules in
domains/name/directories - Validate after changes: Run
ai-rulez validateto check configuration - Regenerate after changes: Run
ai-rulez generateto create tool-specific outputs - Commit sources and outputs together:
gitignoredefaults tofalse, so commit.ai-rulez/and the generated files; setgitignore = trueonly when the team keeps generated files out of git
Programmatic Workflow Example¶
#!/bin/bash
# Example: Create a new domain with rules
# Create the domain
ai-rulez domain add backend --description "Backend services"
# Add a rule
ai-rulez add rule database-standards --domain backend --priority high
# Add context
ai-rulez add context architecture --domain backend
# Validate
ai-rulez validate
# Generate
ai-rulez generate
# Commit (generated files too, unless gitignore = true)
git add .ai-rulez/
git commit -m "chore: add backend domain with database standards"
Best Practices¶
Domain Names¶
Use names that indicate ownership or responsibility:
backend,frontend,mobile(service boundaries)api,database,queue(technical components)auth,payments,search(feature areas)
Avoid: team1, team2, single letters, overly broad names
Organizing Content¶
Put content in the domain that owns it. Example:
domains/backend/rules/database-standards.mddomains/frontend/rules/accessibility.md
Single Responsibility¶
Each domain should represent one area:
# Bad: domains that bundle unrelated concerns
[profiles]
full = ["api-with-db", "frontend-with-build", "infrastructure-and-monitoring"]
Document Domain Purposes¶
Add comments to config.toml:
# Domains:
# - backend: Go services, REST APIs
# - frontend: React web app
# - mobile: React Native apps
# - devops: Infrastructure, deployment
[profiles]
full = ["backend", "frontend", "mobile", "devops"]
Troubleshooting¶
"Profile not found"¶
# Check which profiles are defined
ai-rulez validate
# Try generating with an explicit profile
ai-rulez generate --profile backend
"No presets specified"¶
At least one preset is recommended. Add to config:
"Domain not included"¶
Check your profile configuration:
# If this profile doesn't include "backend", backend content won't appear
[profiles]
myprofile = ["frontend", "qa"] # backend is missing!
Content not appearing in output¶
- Verify domain is in profile:
- Check file location:
- Root:
.ai-rulez/rules/,.ai-rulez/context/,.ai-rulez/skills/ -
Domain:
.ai-rulez/domains/{name}/rules/, etc. -
Check for frontmatter errors:
- Invalid YAML in
---blocks will skip the file -
Remove frontmatter to test
-
Regenerate explicitly:
Next Steps¶
- Getting Started: Quick start and common patterns
- CLI Reference: All commands and flags
- Domains & Profiles: Team organization patterns