Migrating to v5
Upgrade a 4.x project to ai-rulez 5. ai-rulez migrate v5 rewrites the configuration for you; the breaking changes
below are the ones you handle yourself. The complete list is in the changelog.
Upgrade in four steps
Section titled “Upgrade in four steps”- Commit or stash your work, then preview the change list:
ai-rulez migrate v5 --dry-run. Nothing is written. - Run
ai-rulez migrate v5. It rewrites the project in place. Add--recursivefor a repository with several.ai-rulez/directories. - Run
ai-rulez doctor. It reports removed presets with their replacement, drifted outputs and generated paths git does not ignore. - Run
ai-rulez generate, review the diff, and commit the sources and outputs together.
Then run ai-rulez lock once if the project has an ai-rulez.lock (see Lock file), and update CI
scripts for the changed commands and flags.
migrate v5 reads only 4.x projects. Migrate a 2.x or 3.x project to 4.0 with ai-rulez 4.x first
(npx ai-rulez@4 migrate v4), then run ai-rulez migrate v5. Until a project is migrated, every v5 command stops with
the next step:
Error: config version "4.2" is no longer supported: run `ai-rulez migrate v5`Error: config version "3.0" is no longer supported; install ai-rulez 4.x to migrate it to 4.0, then run `ai-rulez migrate v5`What migrate v5 changes
Section titled “What migrate v5 changes”A 4.x project:
# acme rulesversion: "4.0"name: acmepresets: - claude - windsurflint: budget: AR401: 3ai-rulez migrate v5. (.ai-rulez): migrated [convert-format] .ai-rulez/config.yaml: converted to config.toml with keys, order and comments kept; config.yaml is removed [version] .ai-rulez/config.toml: version 4.0 -> 5.0 [lint-ratchet] .ai-rulez/config.toml: [lint.budget] / [lint.tolerate] renamed to [lint.ratchet] (per-rule finding counts); size limits stay in [lint.budgets] [preset-rename] .ai-rulez/config.toml: preset windsurf renamed to devin (outputs move from .windsurf/ to .devin/; delete the old directory) [pin-default] .ai-rulez/config.toml: agents_md = false (v5 default: true, AGENTS.md is the canonical instruction file) [pin-default] .ai-rulez/config.toml: gitignore = true (v5 default: false, no managed .gitignore block) [pin-default] .ai-rulez/config.toml: [header] hashes = "full" (v5 default: "content", only the per-file Content-Hash) [pin-default] .ai-rulez/config.toml: [mcp] self_server = false (v5 default: true, generate adds the ai-rulez MCP server to .mcp.json)migrated 1, unchanged 0, errors 0The resulting .ai-rulez/config.toml:
# acme rulesversion = "5.0"name = "acme"presets = ["claude", "devin"]# Pinned by `ai-rulez migrate v5`: the 4.x default. Remove this line to take the v5 default.agents_md = false# Pinned by `ai-rulez migrate v5`: the 4.x default. Remove this line to take the v5 default.gitignore = true
[lint]
[lint.ratchet]AR401 = 3
# Pinned by `ai-rulez migrate v5`: the 4.x default. Remove this line to take the v5 default.[header]hashes = "full"
# Pinned by `ai-rulez migrate v5`: the 4.x default. Remove this line to take the v5 default.[mcp]self_server = falseEach change has a rule name, shown in brackets in the report:
| Rule | Rewrite |
|---|---|
version |
version = "4.x" becomes version = "5.0"; the rest of the line, including a comment, is kept. |
convert-format |
config.yaml, config.yml or config.json becomes config.toml with keys, order and comments kept, and the old file is removed. $schema becomes schema. |
local-overlay |
config.local.yaml, .yml or .json becomes config.local.toml (mode 0600). |
mcp-merge |
A separate mcp.toml, mcp.yaml or mcp.json is merged into [[mcp_servers]] in config.toml and removed. If config.toml already defines mcp_servers, the file is left in place with a warning; move the servers by hand. |
lint-ratchet |
[lint.budget] and [lint.tolerate] become [lint.ratchet] (per-rule finding counts). [lint.budgets.<kind>] (size limits) is unchanged. |
preset-rename |
The windsurf preset becomes devin and the removed continue-dev preset is dropped. With --write, windsurf_model in agent frontmatter becomes devin_model. |
skills-table |
A [[skills]] array is removed; skills in .ai-rulez/skills/<name>/SKILL.md are discovered without it. |
pin-default |
The four defaults that changed in v5 are pinned to their 4.x value so the generated output does not move: agents_md = false, gitignore = true, [header] hashes = "full" and [mcp] self_server = false. A key you already set is never touched. |
gitignore-block |
With --adopt-defaults, the managed .gitignore block is removed unless the configuration sets gitignore = true. |
command-rename |
ai-rulez usage ... and ai-rulez report usage|evals inside hook, verifier and script commands become ai-rulez telemetry .... |
frontmatter-alias |
permission_mode and user_invocable become permissionMode and user-invocable. Reported only; rewritten with --write. |
Options
Section titled “Options”| Flag | Effect |
|---|---|
--dry-run, -n |
Print the change list; write nothing. |
--check |
Like --dry-run, and exit 2 while a project still needs migration. Use it in CI. |
--adopt-defaults |
Take the v5 defaults instead of pinning the 4.x ones. Regenerate and review the diff. |
--write |
Also rewrite frontmatter aliases in the Markdown sources. |
--recursive |
Migrate every project found below the selected directory. |
--format json |
A machine-readable report: schema_version and one entry per project with status, changes, warnings and error. |
-C <dir or file> |
Migrate the project at that configuration directory or file, for example -C plugin/.ai-rulez. |
The rewrite is text-level, so comments survive, and the result is loaded again before it is written; a file that would
not load is reported and left alone. Running migrate v5 again on a migrated project changes nothing. Exit codes: 0
migrated or nothing to do, 1 a project could not be migrated, 2 --check found work.
Breaking changes
Section titled “Breaking changes”Configuration files
Section titled “Configuration files”- Only
version = "5.0"is accepted. - Only TOML is read.
config.yaml,config.yml,config.json, theirconfig.local.*forms and separatemcp.*files stop every command withError: found <path>: YAML and JSON configs are no longer read; run `ai-rulez migrate v5` to convert it to config.toml. Markdown frontmatter is still YAML. init --format yaml|jsonis removed;initwritesconfig.toml.[lint.budget]and[lint.tolerate]are refused; the table is[lint.ratchet]. The JSON report key isratchet_exceeded.[[skills]]is not a configuration key.skillsis the[skills]dynamic-loading table. Keep skills in.ai-rulez/skills/<name>/SKILL.md, install them withai-rulez skill install, or use[[skill_sources]].[[plugins]]has no effect andgeneratewarns about it. List the plugins to enable as[[enabled_plugins]]withid = "name@marketplace";generatewrites them to.claude/settings.jsonand.codex/config.toml. Delete any.claude/plugins.jsonor.codex/plugins.jsonan earlier version left.- The
compressionoption is removed.generatewarns about it as an unknown key;validateandgenerate --strict-configfail. ~/.ai-rulez.{toml,yaml,json}and./.ai-rulez.*are not read.
Defaults
Section titled “Defaults”| Setting | v5 default | 4.x behavior | Keep the 4.x behavior with |
|---|---|---|---|
agents_md |
true: AGENTS.md is the single instruction file, CLAUDE.md imports @AGENTS.md, skills go to .agents/skills where the tool reads them |
One root file per tool | agents_md = false |
gitignore |
false: generated files are committed |
Managed .gitignore block |
gitignore = true |
[header] hashes |
"content": only the per-file Content-Hash line |
Also a project-wide Source-Hash line, which rewrote every file on any edit |
hashes = "full" ("none" drops both) |
[mcp] self_server |
true: generate adds the ai-rulez MCP server to the MCP files it writes |
Not added | self_server = false, or generate --no-self-mcp |
[lock] enforce |
true whenever ai-rulez.lock exists |
Off | enforce = false |
[lint.security] scan_imports |
On: includes and installed skills are scanned before anything is written | Off | scan_imports = "off" (or "warn") |
migrate v5 pins the first four unless you pass --adopt-defaults. Machine-local files (config.local.*,
.ai-rulez/local/) are ignored by git whatever gitignore says.
Commands and flags
Section titled “Commands and flags”-
validateruns the content checks by default: dead links, globs that match nothing, missing hooks, oversize content and the security rules. A CI job that passed withai-rulez validatecan now exit 2. Fix the findings, lower a rule with[lint.severity], record the current findings with--update-baseline, or keep the old configuration-only check withvalidate --config-only. -
--strictmeans “warnings fail” (--fail-on warning), and exists only onvalidate,doctorandverifiers run. Replacevalidate --strictwithvalidatein scripts unless warnings should fail. -
verifychecks only signatures, approvals and plugin provenance (--attestation,--approvals,--self,--plugin). Usegenerate --checkto compare generated files with their sources; a bareverifyexits 1 and says so. -
scanruns only the security analyzer. Hook and configuration findings are reported byvalidate. -
usageandreportare replaced bytelemetry, with no aliases. Regenerate hook blocks you wrote by hand withai-rulez telemetry hook.4.x 5.0 ai-rulez usage hookai-rulez telemetry hookai-rulez usage recordai-rulez telemetry record(skill and item loads in one command)ai-rulez usage feedbackai-rulez telemetry feedbackai-rulez report usage <log>ai-rulez telemetry report [log]ai-rulez report evalsai-rulez telemetry report evals -
Seven short flags, each with one meaning everywhere:
-C--config,-D--debug,-T--token,-q--quiet,-y--yes,-n--dry-runand-o--output. Every other short flag is removed; using one is an unknown-flag error. -
Renamed and removed flags:
4.x 5.0 --json,-j--format json-n <dir>,--config-dir <dir>on individual commands--config-dir <dir>, a global flag with no short form-d(--dry-run)-n,--dry-runai-rulez validate path/.ai-rulez(positional configuration path, on every command)ai-rulez -C path/.ai-rulez validategenerate --no-fetch,-fgenerate --offlinegenerate --strict(unknown keys fail)generate --strict-config(orAI_RULEZ_STRICT=1)generate --update-gitignoregenerate --gitignoreclean --force,remove --forceand the other confirmation skips--yes,-ylock --strictlock --refuse-findingssign --policy <file>(sign a policy)sign --org-policy <file>sbom --format cyclonedx|spdx-jsonsbom --type cyclonedx|spdx-jsontelemetry hook --format json|tomltelemetry hook --syntax json|tomlexport okf --out <dir>,eval run --out,eval import --out,publish emit --out--output-dir <dir>review --out,search --out,verifiers run --outand similar--output,-o--verbose,-V--debug,-D--no-configure-cli-mcp,--skip-cli-mcpRemoved (they had no effect) mcp --transport,--address,--portRemoved (the server is stdio only) convert --force,eval run --forceandimport okf --force(overwrite or re-run) are unchanged. The--policyflags exist only on the commands that evaluate policy;AI_RULEZ_POLICYworks everywhere. See Flag conventions. -
Removed command aliases:
g(generate),clear(clean),vandcheck(validate) andcheck(list checks).genandvalremain. -
migratehas subcommands,migrate v5andmigrate okf;migratealone prints help andmigrate v4is an unknown command. Group commands such aslistprint their help and exit 0 when given no subcommand. -
Stricter content names, also through the MCP tools: a name must not end in
.md, contain whitespace or start with.or-;--targetsmust name presets, paths or globs.domain removerefuses while a profile lists the domain. -
An unknown
builtinsentry failsvalidatewith the valid choices. -
The content commands honor the global
--config-dirand-C(add,remove,show,edit,list,domain,profile,include,skill), so a script that passed one now acts on that directory. -
eval runprints text by default; pass--format markdownfor the previous output.
Output, errors and JSON
Section titled “Output, errors and JSON”- Results go to stdout, diagnostics to stderr. Lists (
list,domain list,profile list,include list,skill list),clean --dry-runandlock --checkprint on stdout;-qnever hides them.-qhides progress and information but no longer hides warnings. Confirmation prompts are on stderr. - The content commands print the path they changed on stdout (
add,remove,edit,domain add|remove); the “added successfully” line is on stderr. They also take--format json. - One error format:
Error: <message>, an optionalValidation errors:list andHint: <hint>on stderr. Under--format json, a failure also prints an error document on stdout. - Every
--format jsondocument carriesschema_version. List commands and multi-profiletokensreturn{"schema_version": 1, "items": [...]}instead of a bare array;publish verify --format jsonalways returns{"schema_version": 1, "results": [...]}. The schemas are inschema/; see JSON contracts. schema/catalog.schema.jsondescribes catalog version 2. Version 1 isschema/catalog.v1.schema.json, andcatalog --format jsonprints version 1 unless you pass--schema-version 2.- Unprefixed environment variables are ignored. Use
AI_RULEZ_DEBUG=1andAI_RULEZ_QUIET=1instead ofDEBUGandQUIET.
Exit codes
Section titled “Exit codes”Every command follows one contract: 0 success, 1 the command could not run, 2 findings, drift or a failed gate,
3 only for lock (written, with served skills left unpinned). See Exit codes for each command.
Changes that affect scripts:
validateexits 2 on content findings (see above).lock --checkwithout a lock file exits 1; runai-rulez lockfirst.- Declining the
generate --user --cleanprompt exits 1. - An include that cannot be resolved (no network, deleted repository, no cached copy) makes
validate,doctorandgenerateexit 1 and name it.generate --offlineskips it with a warning.
Generation
Section titled “Generation”generaterefuses to overwrite a file it did not write, such as a hand-writtenCLAUDE.md. Import it withai-rulez convert --write(the nextgeneratereplaces it), move it, or passgenerate --force. It never writes through a symlinked output.- Divergent shared outputs fail. When two presets render different content to the same path,
generatefails and names both. Make them agree, for example withagents_md = trueor[rules] mode = "inline". - Claude Code MCP servers are written only to
.mcp.json, which keeps${VAR}references;.claude/settings.jsonno longer receivesmcpServers. An entry an earlier version wrote there is removed on the nextgenerate. generatewarns about unknown configuration keys and names the nearest known key;--strict-configfails instead.generatelists new or changed commands it writes into tool configuration (hooks, command-based MCP servers,[permissions] allowrules). It only warns; pass--yesor setAI_RULEZ_ACK_COMMANDS=1in CI.generate --dry-runprintsunchanged:andedited:for files that need no write, besideswrite-file:.cleankeeps hand-edited generated files with a warning;clean --include-editedremoves them.- Check outputs are committed: files generated from checks are never added to the managed
.gitignoreblock. tokenstotals include the skill, command and agent listing, so--budgetcan fail where it passed.- Skill
evals/directories are not bundled into plugins unless[plugin] include_evals = true. - Custom preset and provider output paths are validated:
.., absolute paths and paths naming.git,.ai-rulez,.hgor.svnare rejected. init --fromrunsconvert --write: it takes importer names (auto,native,rulesync, …) or project paths (.claude,CLAUDE.md), imports MCP files, hooks and permissions too, and turns a root file such asCLAUDE.mdinto one context item (ai-rulez convert --split-headingssplits it by heading).
Presets
Section titled “Presets”| Change | Action |
|---|---|
windsurf is renamed devin: outputs move from .windsurf/ to .devin/ and windsurf_model becomes devin_model. There is no alias. |
migrate v5 renames the preset; run it with --write for the frontmatter key, and delete .windsurf/. |
continue-dev is removed, with no replacement. |
migrate v5 drops it; doctor reports it. |
codex and antigravity write commands as skills (.agents/skills/<id>/SKILL.md). |
None; the old files are removed on generate. |
codex writes skills to .agents/skills. |
Set codex_skills_dir = ".codex/skills" for the old location. |
cursor user-level skills go to ~/.agents/skills. |
Run generate --user; clean --user removes the old copies. |
antigravity no longer adds the ai-rulez MCP server itself. |
None; [mcp] self_server (on by default) adds it. |
amp no longer writes .agents/agents, which Amp does not read. |
None. |
opencode writes MCP servers as mcp.<name> with enabled. |
None; the old mcp.servers entries are removed. |
zoocode inlines rules into AGENTS.md instead of .roo/rules. |
None. |
Lock file
Section titled “Lock file”ai-rulez.lockpins content. Besides remote sources, it pins the ai-rulez version, every authored rule, context file, skill, agent, command, hook and settings source, the generated outputs and served skills, all assha256:digests. Runai-rulez lockonce after upgrading, review the diff and commit it; a lock from 4.x or an earlier v5 build reads as stale until you do.[lock] enforceis on whenever the lock exists. A remote include or installed skill the lock does not cover makesgeneratefail,AR010andAR012become errors, andvalidatereports source and output drift (AR981,AR982).generate --checkalso verifies authored content against an enforced lock.lockandupdatescan what they pin. A remote tree pinned to something new is scanned first; an error finding refuses the pin unless you pass--accept-findings. A served skill the scan refuses is left unpinned andlockexits 3;--refuse-findingsmakes it fail instead.generate --checkreportsblocked: <path>for a shared file that a machine-local input would change;--allow-local-driftaccepts it.
See Lock file.
Supply-chain defaults
Section titled “Supply-chain defaults”http://andgit://remotes are rejected. Usehttps://,ssh:///git@host:path, or a local path.- The git token is sent only to allowlisted hosts:
github.comby default, or the hosts inAI_RULEZ_GIT_TOKEN_HOSTS(which replaces the default, so listgithub.comtoo). The forge client (release dates, review-linked approvals) has its own allowlist,AI_RULEZ_FORGE_HOSTS; add your GitHub Enterprise host there. See Forge client. - A committed configuration cannot point outside the project. A local include or
[[skill_sources]]path that resolves outside the project is an error. Put such paths inconfig.local.tomlor the user configuration. - Symlinked content must resolve inside the repository. Other links are refused with a warning, and
validatereports them. Symlinks inside includes, installed skills, skill sources and OKF bundles are never followed. - Repository content read at load time is capped at 8 MiB per file.
- Staged external scanners run confined under
isolation = "auto"where a sandbox backend exists. A scanner that writes outside its scratch directory fails withAR9E3; point it atTMPDIR/HOMEor setisolation = "none". See Isolation. - An organization policy is read from the managed path (
/etc/ai-rulez/policy.toml,/Library/Application Support/ai-rulez/policy.toml,%ProgramData%\ai-rulez\policy.toml) besides--policyandAI_RULEZ_POLICY. Nothing changes on a machine without one. See Organization policy.
Egress settings come from the user, not the repository
Section titled “Egress settings come from the user, not the repository”allow_network, base_url, api_key_env and the price overrides of [llm], and the egress keys of [telemetry]
(allow_network, otlp_endpoint, headers_env, service_name, resource, …), are honored only from the user
configuration (~/.config/ai-rulez/config.toml) or the AI_RULEZ_LLM_* / AI_RULEZ_TELEMETRY_* environment
variables. A repository that sets them is ignored and reported (AR9L1, AR9K1). Move these settings to your user
configuration. See Trust model.
Validation rules
Section titled “Validation rules”- New rule codes can fail a CI job that passed on 4.x. Every code is listed in
Strict validation. Lower or disable a rule with
[lint.severity], or accept the current findings withvalidate --update-baseline. Rule codes are never renumbered. - Top-level
[[hooks]]are stricter: ascriptpath outsideA-Za-z0-9._/-is rejected, and a missing or non-executable script isAR504/AR505. - Eval results are signed per user. A record without a valid signature, such as one committed from another
machine, is re-run by
eval run. In CI, useeval run --force. See Evals. - The usage log is version 3: the session field is a salted hash. Version 1 and 2 logs are still read.
OKF is the format of .ai-rulez/
Section titled “OKF is the format of .ai-rulez/”.ai-rulez/ is an OKF bundle: each content file carries a type (and usually title and x-ai-rulez)
in its frontmatter and each directory has an index.md. init, add and the MCP tools write this layout. A migrated 4.x tree keeps loading
in the earlier layout, with a deprecation notice. Convert it with:
ai-rulez migrate okf --dry-run # list what would changeai-rulez migrate okf # convert in placeai-rulez generate --check # the generated files are unchanged[converted] rules/style.md[index] index.md[index] rules/index.mdwould convert 1, indexes 2, unchanged 0, skipped 0migrate okf moves each file’s frontmatter under x-ai-rulez.metadata, adds type (Decision for rules, Concept for
context, Playbook for skills, Reference otherwise) and title unless they exist, and writes the index.md files.
Bodies and skill resources (references/, scripts/, assets/) are not touched. A file with an unclosed frontmatter
block is skipped and reported. --check exits 2 while a tree still needs migration. The originals of rewritten files
are copied to .ai-rulez.bak-okf-<timestamp>/ at the project root; delete it once you have reviewed the result, and
do not commit it.
After the conversion:
type,titleandx-ai-rulezare reserved frontmatter keys.- A file you add by hand should have a
type(titleis optional). Frontmatter withouttypeis anAR9B1error; a file with no frontmatter still loads, with anAR9B1warning.generaterefreshes theindex.mdlistings. validatealso runs the OKF checks (AR9B*).
Go API
Section titled “Go API”Code that embeds ai-rulez should use github.com/Goldziher/ai-rulez/v5/pkg/airulez (experimental; see
Embedding). The module path is
github.com/Goldziher/ai-rulez/v5 and the binary is cmd/ai-rulez:
go install github.com/Goldziher/ai-rulez/v5/cmd/ai-rulez@latestIn pkg/airulez, the machine-local overlay is read only with Options.WithLocal; Options.WithoutLocal is removed.
See also
Section titled “See also”- Supported harnesses: what each preset writes.
- Hooks and settings and User-level configuration.
- Lock file and Trust model.
- Changelog: the complete list of changes in 5.0.0.
