Changelog¶
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog and this project adheres to Semantic Versioning.
[Unreleased]¶
[5.6.3] - 2026-10-24¶
Added¶
recursive_excludeandgenerate --recursive --exclude.generate --recursive(andvalidate --recursive) searched every directory, so a repository with deliberately invalid configurations in test fixtures failed the whole run.testdatadirectories are no longer searched, andrecursive_exclude = ["tests/fixtures"]in the configuration of the directory the run starts from (.ai-rulez/config.toml,.agents/ai-rulez.tomlor.config/ai-rulez/config.toml), or--exclude(repeatable), names more. Thegenerate_outputsMCP tool honours both (recursivewithexclude).
Fixed¶
- The live-reload watcher no longer scales its descriptors with the repository.
mcp --serve-skillswatched every directory of the project; on macOS and the BSDs (kqueue) each watched directory and each file in it costs an open descriptor, so one server held 50,000 to 110,000 in a large monorepo and a few servers overflowed the system file table. The watcher now has a budget of 2,048 descriptors, spends it breadth first (shallow convention directories such as.claudeandskillsfirst), and warns once when the budget is reached; the periodic poll covers what is left unwatched. Each daemon has its own budget, so many worktrees cost a few thousand descriptors each, not tens of thousands.no_watch = truestill disables the watcher (#480). - The shared daemon no longer pins the checkout.
mcp --daemon-serverchanges to its private cache directory after start, so a throwaway worktree can be removed, and exits when its repository directory is gone instead of waiting out the idle timeout. The new[mcp] daemon_idle_timeout(a Go duration such as"2m") sets that timeout, 15 minutes by default. The daemon's stderr (a watch budget reached, why it exited) now goes todaemon.logbeside its socket instead of the null device (#481). - A read-only skills server registers only the skill tools by default.
mcp --serve-skillswithread_onlyregistered 43 tools, about 92 KB of schema sent on every request. It now registersfind_skill,load_skill,search_skills,read_skill_fileandlist_skill_resources(a few KB) and no prompts;[mcp] authoring = truerestores the full read-only set and thereview-configprompt, andtools = [...]still names the list explicitly (#482). - A long home directory no longer silently disables the daemon. When the socket path under the cache is too long
(104 bytes on Linux, 100 on macOS and the BSDs, with a margin), the socket moves to
$XDG_RUNTIME_DIRor the temp directory underai-rulez-<uid>/. The daemon, its log and the proxy only use it when this user owns both that directory and the one inside it with mode 0700, and the log is never opened through a link, so another user on a shared/tmpcannot redirect them. If no path fits, the session logs a warning (it was debug level) and serves in-process (#483).
[5.6.2] - 2026-10-21¶
Added¶
[[lint.accept]]: accept one finding with a required reason. An entry names arule, apathglob and amatchsubstring of the finding's message (or aline, or both), plus areasonthat config validation requires. The matching findings stay in the report, marked accepted with the reason, and do not fail the run; a second finding of the same rule in the file stays reported. An entry that accepted nothing is reported as the newAR9I0 accept-unusedwarning, except when the run could not have produced the finding (rule off, analyzer not selected, or the file not linted by--path/--glob), so stale acceptances do not accumulate. A code the organization policy protects is never accepted (AR740).validate --update-baselinedoes not write accepted findings to the baseline. (#479)validate --tracked-onlyand[lint] tracked_only = true.validateandscanlint every file that is not git-ignored, tracked or not, so an untracked build artifact such as a__pycache__/*.pyc(AR051) made results differ between machines. With the option the run reports no findings in files outside the git index, except the files--changedor--sincenames; the files are still walked and their findings are dropped after the run. Outside a git work tree it logs a warning and keeps every finding. (#476)
Fixed¶
- The scanner flags no longer fail a run without
--external.validateandscanrejected--no-scan-cache,--write-baseline,--reason,--show-suppressedand--scanner-baselinewith "require --external", so a CI command had to branch on whether a scanner was configured. Without--externalthese flags now do nothing and one warning on stderr names the ones that were ignored;--reason,--reason-for,--min-severityand--rulestill need--write-baseline, and--write-baselinestill needs--reason. (#477) validatereports a malformed skill frontmatter once. A skill whose frontmatter is not valid YAML (for exampledescription: Args: a, b) printed aWARN Ignoring malformed YAML frontmatterline on stderr in addition to theAR306finding, which broke machine-readable output. The lint pass now leaves the warning out, sinceAR306carries it; commands that do not validate, such aslist, still warn. (#478)validate --update-baselineno longer records findings in untracked files. A teammate's or CI's checkout does not have those files, so their baseline entries were dead weight andlint-baseline.jsondiffered between machines. They are now skipped, and the run logs how many. (#476)- A scanner result without
locationsis no longer reported on.ai-rulez/config.toml:1. The finding looked as if it were about the configuration, and a baseline entry on that file stopped matching once the scanner added a location. A run that was handed exactly one skill (batch_size = 1) now attributes such a result to that skill'sSKILL.md; any other run reports it without a file, with(no location reported by scanner)at the end of the message. Cached scanner results from earlier versions are scanned again. (#474) - A finding without a file no longer takes the working directory as its path. Such a scanner result got the path
.(or the name of the directoryvalidateran in), so--tracked-onlydropped it, its fingerprint depended on where the command ran, and SARIF, GitHub and JUnit output named.as its location. It now has an empty path, a fingerprint that is the same from every directory, and no location in the output formats. [[lint.accept]]and the baseline no longer interfere.--update-baselinecopied config-accepted findings intolint-baseline.json(and refused security ones without--baseline-reason), and applying a baseline replaced their reason and counted them twice. The baseline now leaves them alone.validate --externalshows progress during a long scanner run. A scanner withbatch_size = 1over hundreds of skills ran for minutes without a line of output, so CI and agent shells could not tell it from a hang. Each scanner now printsname: N batches to runon stderr when it starts (every scanner,1 batch to runfor a single run) and, only when it runs two or more batches,name: batch 4/13as each finishes, also withparallel. The lines are information level:--quietdrops them and the report on stdout is unchanged. (#475)
[5.6.1] - 2026-10-20¶
Changed¶
- Built with liter-llm 2.3.0. The release binaries link the liter-llm 2.3.0 native library. The other Go modules (go-python-packaging 0.13.0, oras-go 2.6.4 and their dependencies), the docs site dependencies and the pnpm version the npm package is published with are updated as well.
Fixed¶
- The AR041 to AR058 checks no longer slow every scan by about half. In 5.6.0 each rule looked for its trigger
words one at a time, and the malware rules read the whole file once per word, so
validate,scanand the MCP skill scan took about 45% longer than in 5.5.3. All of a rule set's words are now found in one pass, which brings the cost of the new rules to about 12%; findings are unchanged. validate --pathabove the configuration's directory outside git. In a tree git does not track, the project root was the configuration's directory, so--config sub/.ai-rulez/config.toml --path docs/AGENTS.mdfailed with "outside the project root" although you named the file. A--pathabove it now widens the root to the nearest directory that holds the configuration and every named path, when that directory lies inside the working directory. Git work trees,--repo-root,--globand[lint] pathsare unchanged. (#470)
[5.6.0] - 2026-10-19¶
Added¶
message_propertiesappends SARIF result properties to a scanner finding's message. A[[lint.external]]entry lists property paths (evidence,snippet, a dotteda.b, orlocation.snippetfor the standard region snippet) whose sanitized values are appended as[evidence: ...; snippet: ...]withinmax_message; theskillspectorprofile setsevidenceandsnippet, so a CI annotation shows the matched text instead of only the rule title (#468).- Built-in name tables are generated from each harness's registry and checked every week.
tools/harness-builtins/generate.pyreads the commands, bundled skills and agents of 46 of the 52 harnesses from a public registry (a Rust, TypeScript, Go or Python source file, a file tree, or a structured docs page) at the tag or commit pinned inpins.tomland rewrites the names and aliases of the matching groups ininternal/harnessnames/data/<preset>.toml; source ids, quotes,user_collision,evidence,invocation_spacesandcompletenessstay as written.--checkexits 1 when a data file differs from its pinned source,--bumpshows the names a newer tag would change (--bump --applymoves the pin), and the six harnesses without a registry (aiassistant,amp,baz,bob,muse,trae) areextractor = "none"with the reason.check_sources.pynow verifies every quote of every table, fails on a recorded outcome that has no quote, and prints the verified and failed counts per harness.task harness:builtins:checkandtask harness:builtins:refreshrun them, and.github/workflows/harness-builtins.ymlruns the check every Monday and opens (or updates) one issue on drift. The data files shrank where one registry feeds many names: hermes, takt, codewhale and openclaw went from about 160 single-name groups to a handful. Running the extractors corrected the data: names that are not commands were removed (openin xum,exportandrememberin Letta,share,unshare,detailsand thescoutagent in OpenCode,feat015ctxand four uninstalled skills in Codewhale) and names that were missing were added (fastandultrafastin xum, 15 Kilo TUI commands,ultrareviewin Claude Code,forkin CodeBuddy,reset-allowed-toolsin Copilot CLI,planin dsh). 61 new verbatim quotes document collision outcomes that had none; every recorded outcome now has one. THIRD_PARTY_NOTICES.mdand a contributing section for rules and tests derived from NVIDIA/SkillSpector. The notices file carries the modification notice, the derived-file table and the header for derived files, andLICENSES/Apache-2.0.txtthe license text;scripts/transcode-skillspector-tests.pywrites candidateruleCasetables from SkillSpector pattern tests with that header (#466).validatedetects anti-refusal and context-exfiltration prose.AR041(a warning) reports text that removes the agent's refusals, caveats or safety rules, andAR042(a warning) reports text that sends the conversation, the system prompt or the user's data to an external URL, written as a link, a quoted or backticked URL or a bare host (a generic noun is not read as a destination); a phrase under a negation, in quotation marks or in a code span is not reported. A scope limit, a reviewer verb or quotation marks do not silenceAR041on an instruction that removes refusals (Never refuse, even if the request is harmful.is reported), curly apostrophes are matched, and ordinary product guidance is not reported. Both are derived in part from NVIDIA SkillSpector (Apache-2.0).AR043,AR044andAR045: persistence, unsafe deserialization and requests to internal endpoints.AR043(warning; info for an append to a shell startup file) reports a command that installs a crontab, launchd job, systemd unit, init or autostart entry, scheduled task or login item;AR044(warning) reportspickle,marshal,jsonpickle,yaml.loadwithout a safe loader,torch.loadwithoutweights_only=True, PHPunserialize, RubyMarshalandYAML.unsafe_load, JavaObjectInputStreamandnode-serializein scripts, fences and code spans;AR045(warning; info for a local administration port) reports a request to a cloud metadata endpoint or the ECS task-credentials address. A negation hides a finding of these three rules only in markdown, so a comment word in a script does not. Derived from the RA2, DS1-DS4 and SSRF1-SSRF3 signatures of NVIDIA SkillSpector.AR046toAR050: malware-shaped content in skills and bundled scripts.AR046 web-shell(request input passed to eval or a command runner in PHP, Python, JSP and ASPX),AR047 cryptominer(pool URLs, miner flags, coinjacking libraries; a bare miner name is info),AR048 reverse-shell(bash -i >& /dev/tcp/...,nc -e, mkfifo relays, socat, Python and PowerShell socket loops),AR049 offensive-tool(tool names at info, outside prose lines, credential-dump commands such assekurlsa::orprocdumpof lsass at warning) andAR050 phishing-kit-form(a password form posting to a locallogin.php, info; a form posting to an absolute URL is not reported). An ordinarycurlcommand does not matchAR047. All offline and RE2-only;AR048andAR049skip example regions, andAR046is not repeated on a lineAR005already reported.AR051 shipped-executable,AR052 blank-line-padding,AR053 obfuscated-injection-phrase: content that hides from a reader.AR051names Python bytecode (__pycache__,.pyc), Java classes, native libraries and executables a skill ships, by extension or by the leading bytes of ELF, Mach-O, PE and.pycfiles, so a renamed binary is found too; a file git ignores is not reported.AR052reports 20 or more blank lines followed by instruction-like text.AR053extendsAR004to phrases written with Cyrillic or Greek look-alikes, invisible characters or spaced-out letters (i g n o r e): a line is normalized once (NFKC, a table of look-alike letters, spaced letters joined) and only a line that holds anAR004phrase after that, and not before, is reported. A plain ASCII line without a spaced run costs one byte scan. All three are warnings. The rules are modelled on NVIDIA SkillSpector (Apache-2.0) checks SC8, SC9, P3 and AE4/AE6; aligned table padding is deliberately not counted.- Dependency-manifest and MCP-metadata rules:
AR054toAR058.AR054reports a bundledrequirements*.txt,package.jsonorpyproject.tomldependency without a pinned version,AR055a package index or registry redirect (--index-url,--extra-index-url,--trusted-host,registry=) in a manifest, config or install command, andAR056(info) a dependency name one edit from a popular PyPI or npm package, taken from lists of the most downloaded package names. Manifests at the skill root are scanned too, and a manifest with a BOM, in UTF-16, oversized or not valid UTF-8 is reported as not analyzable.AR057applies the hidden-character, HTML-comment and injection-phrase detections ofAR002toAR004to the description and tool metadata of MCP server definitions, with or without a configuration.AR058(info) reports commands a skill runs that itsallowed-toolsdo not cover.AR051reports once per file andAR058once per command, so a baseline entry for one file does not accept a second and--changedsees a new one. The manifest rules follow the supply-chain checks of NVIDIA SkillSpector (Apache-2.0). - Release archives, the Homebrew formula and the npm and PyPI packages ship
THIRD_PARTY_NOTICES.mdand the license texts inLICENSES/, and the transcoder refuses a--testsdirectory that is not a git checkout root or a commit that is not 40 hex digits (#466).
Changed¶
- New findings can fail an existing run.
AR014onenv | curl ...in an inline code span is an error (exit 2 by default), and the new warning rules fail--strict; baseline or[lint.severity]-override them where intended. A line such as>> ~/.zshrcis reported byAR006and, as info, byAR043. AR006leaves a write to a scheduler, init or autostart location toAR043.cat > ~/Library/LaunchAgents/x.plistis reported once, as persistence; an append to a shell startup file is still reported byAR006as before.
Fixed¶
- The
skillspectorprofile drops SkillSpector's "OSV.dev unreachable"SC4notice when egress is denied. SkillSpector has no offline switch, so withegress = falseit reported one lowSC4result perrequirements*.txtthat the lookup failed. Profiles can now listoffline_notices(a rule id and a message fragment, both must match); anegress = falseentry drops those results, and realSC4vulnerability findings are kept (#469). --write-baselinedrops scanner baseline entries that match no current finding. A rewrite kept every existing entry, so stale fingerprints piled up next to the new ones (93 stale plus 67 new gave 160 entries) until--strict-baselineforced a manual cleanup. The rewrite now reflects the current findings; entries of a scanner that did not run or failed, and of findings that--min-severityor--ruleleave out, are still kept (#467).AR014reportsenv | curl ...and a credential-file upload written inside an inline code span of a prose line, as it already did on a bare line and in a fenced block (#462).- A
[[skill_sources]]directory written with backslashes is a skill root forAR808.url = '.\vendor'(a Windows spelling) never matched, so a reserved name in it went unreported; a drive path such asC:\vendoris no longer read as a project directory. - Kimi Code's
/towercommand is a built-in name. The weekly registry check found it in the slash-command reference, so a customtowercommand or skill is now reported byAR9U0forkimi(#472).
[5.5.3] - 2026-10-18¶
Added¶
-
[[lint.ignore_rules]]: drop chosen rules in chosen files (rules = ["AR901", "AR9*"],paths = ["playground/**"], optionalreason). Codes, names and wildcard patterns over codes are accepted and validated when the configuration loads; the route is refused for policy-protected codes likeignore_paths, and ignored findings never reach the baseline or the ratchet. The table isignore_rulesbecause[lint] ignoreis already the list of codes dropped everywhere (#460). -
[lint] skill_rootsandvalidate --exclude <glob>.skill_rootslists the directories AR808 treats as harness skill roots, replacing the defaults;--excludeleaves matching files out of the filesvalidateandscanfind by convention,--pathand--glob, with or without a configuration.[lint] ignore_pathsis now described invalidate --helpand invalidate --explain AR808(#457). -
AR9U0 harness-builtin-name-collisionandAR9U1 harness-reserved-name: a skill, command or agent named like a built-in of any harness.internal/harnessnamesholds one table per preset (52 files, about 2,800 built-in commands, bundled skills and agents, each group with the page it was taken from and, where the harness documents the outcome, a verbatim quote) and what the harness does with a user item of the same name: it replaces the built-in (Claude Code, OpenCode, Qwen Code, Kilo), the built-in stays and the item is renamed or unreachable (Gemini CLI, Cline, Goose, Augment), or both are listed (Codex).validatecompares every output with each harness that loads it: the presets that write it and every harness that reads the directory it lands in, such as.agents/skills, whether or not its preset is enabled. Names are compared NFKC-normalized and case-folded, aliases included. A built-in with an undocumented outcome is information, listed once per item; an error needs a quoted outcome in a table set toerrorand a preset the project writes for (a harness that only reads a shared directory is a warning). Plugin-only content is exempt. Acknowledge a collision with[lint.builtin_names] allow = ["gemini:help"](orlint.allow_overrides), declare names the tables cannot know with[lint.builtin_names.extra], add a table in.ai-rulez/harness-names/<preset>.toml, and turn the rules off withenabled = false; AR9U1 (Claude Codesynced,anthropic-skills,anthropic-skills:*) cannot be allowed. The tables are in the AR9C0 age check andtask harness:verifyre-fetches their quotes. A project with a skill namedhelp,review,planorinitmay need anallowentry.tools/harness-builtinsconverts the compiled data set and checks sources TheAR9Ublock was reserved for a UI; that claim moves toAR9V(#447). -
--min-severity,--ruleand--reason-forfor--write-baseline.validate --external --write-baselineandscan --external --write-baselinerecorded every finding with one--reason.--min-severity <level>and repeated--rule <glob>(scanner rule id,scanner:ruleorARcode) now narrow what is written; findings left out stay reported and keep an existing entry.--reason-for RULE=textsets the reason per rule, and{rule},{severity}and{scanner}expand in--reasonand--reason-for. A new entry stores only the first line of the message (160 characters at most), which keeps a large baseline small; the format is unchanged, so existing baselines and--strict-baselinework as before (#458).
Changed¶
- AR808 reads its Claude Code names from the new table and reserves aliases. A skill replaces a built-in command but not its aliases, so a skill named
new(an alias of/clear),settings,share,undoorrcis reported as a warning (the skill loads; only the alias keeps running the built-in), not an error; the bundled skills synced from a claude.ai account (pdf,xlsx) are not reported. AR9U0 skips the Claude Code commands and bundled skills, so no name is reported twice. Authored content that the plan does not write where Claude Code reads it (a plugin-only skill, invoked as/plugin:skill) is no longer reported.
Fixed¶
-
validate --external --changedlists less. (Not a fix for the scanner's own run time, which is its process start and analysis, #461.) A scanner handed{skill_dirs}already ran only the skills with a changed file, but the run first listed the supporting files of every skill (a directory walk, agitcall and a read per skill) and then discarded them. The files of a skill that holds no changed path are now left out of that listing, and the changed skills are listed as before, so the findings are the same. In a 400-skill, 10,000-file repository with two edited skills,validate --strict --external --changedtook 14.4 to 16.7 s and now takes 1.5 to 2.3 s (1.4 s without--external) (#461). -
AR401 no longer reports a documented path that git ignores. A missing path matched by
.gitignore,.git/info/excludeor the global excludes file (.claude/settings.local.json,.claude/personal-preferences.md) is a local file, not stale guidance.[lint] allow_paths,[[lint.ignore_rules]]and an inlineai-rulez-lint-ignorecomment cover placeholder paths, and are now listed invalidate --explain AR401. git is asked once per run for all missing paths (#459). -
The scanner cache log counts only cached skills.
validate --external --no-scan-cache --changedloggedScanner results served from the cachefor the skills--changedleft out although the scanner ran for the others (#461). -
AR808 only checks skills and commands where the harness loads them from. A hand-written
SKILL.mdis compared with the Claude Code built-ins only inside.claude/skills(or.claude/commands), a plugin'sskills/directory, a local[[skill_sources]]directory or askill_rootsglob; an application's ownx/src/public/skills/code-review/SKILL.mdis still linted for format but no longer reportsshadows the built-in /code-review. Authored.ai-rulez/content is always checked (#457). - AR402 no longer reads the code span in a link's text as a resource reference.
[breferences/s.md](../../b/references/s.md)was reported as a missingreferences/s.mdalthough the link target exists. A code span inside the text of an inline link is its label; the link target is checked instead (an unresolved target is reported as a broken link, not as AR402) (#456).
[5.5.2] - 2026-10-18¶
Added¶
[mcp] serve_skillsmakes a bareai-rulez mcpserve skills. The key (defaultfalse) behaves as--serve-skills, so a client entry can beai-rulez mcpalone; the flag wins, and--serve-skills=falseturns the key off. It is in the schema and accepted byvalidate(#454).skillspectorscanner profile for NVIDIA SkillSpector.profile = "skillspector"runsskillspector scan --no-llm --format sarifone skill per run, checks the>=2, <3version range, and keeps the scanner offline:--no-llmis required and--transitive,--mcp-registryand--mcp-registry-compareare deny-listed. Profiles can now setbatch_size,versionandrequired_flags, which entries inherit and override. Opt-in, not part ofbaselineorstrict; contract-tested against SkillSpector 2.12.0, with a recipe indocs/strict-validation.md(#448).
Fixed¶
- The
lint.allow_overridesscope matching has one implementation.generateandvalidate(AR703) shared the matching only by copy; both now callpresets.CollapseAllowed, with no change in behaviour. scanners doctor --externalreports a timed-out version probe. For a scanner without aversionrange the timeout was discarded and the row readunknown (the version command printed nothing usable); it now saysversion probe timed out after 10s.- An explicit
AR9E6severity is no longer overridden when every scanner result is dropped. The escalation to an error applies only while the rule has its default severity;[lint.severity] AR9E6 = "info"(or"warning") is kept, andoffstill disables it (#448). - Cached scanner results written by earlier builds are not served. The path normalisation and the out-of-scope batch fields changed how a report maps to findings, so the scan cache mapping version is bumped and older entries miss once (#448).
- A server started without
--serve-skillswarns about skills-only[mcp]keys.discover_skills,allow,deny,exclude,no_watchanddaemonwere accepted and silently ignored, leaving a client with the authoring tools and nofind_skill.mcpnow logs one warning at startup naming the keys that are set and ignored, and says to pass--serve-skillsor set[mcp] serve_skills(#455). {project}names the project root in a scanner command, and the stage working directory is documented. A relativecommand[0]was resolved against the project but a relative script argument (["sh", "tools/t.sh"]) against the stage, so it failed with exit status 127.{project}/tools/t.shnow works, the script it names is still hashed into the cache key, and the difference is documented next tocommand(#450).- A skill-relative scanner result resolves against the skill of its own batch. With
batch_size = 1and{skill_dirs},SKILL.mdandreferences/x.mdwere matched against every staged skill, found ambiguous and dropped asAR9E6. Each batch now resolves against the skills it was handed; a path that is still ambiguous in a multi-skill batch is dropped as before, andAR9E6is an error when every result of a run was dropped (#448). - The scanner version probe has a
version_timeoutand a timeout is reported as one. The probe forversionwas fixed at 10 s and a slow one read asversion output ""(AR9E2), so the scanner silently did not run.version_timeout = "30s"on[[lint.external]]raises it (default unchanged), and a timed-out probe saysversion probe timed out after 10s(#449). generatehonourslint.allow_overridesin the duplicate warning. A local skill that shadows a builtin and is listed inlint.allow_overrides(asnameordomain/name) no longer logsDuplicate skill collapsedon every run, matchingvalidateandAR703(#444).- A local skill may shadow a builtin command, and a local command a builtin skill. The two kinds share one output id, so a project item with the id of a builtin of the other kind failed
validateandgeneratewithskill and command ids collide. The project item now wins, the builtin is dropped, and the collapse is reported asAR703and in thegenerateduplicate warning, silenced bylint.allow_overrides. Two project items, or two builtins, of different kinds with one id stay an error (#445). - A local domain named like a builtin pack warns instead of silently skipping the pack. The pack's items were dropped with only a debug line.
generateandvalidatenow logBuiltin pack skippedonce per pack, naming the items that are lost (those a same-named project item does not shadow) (#446).
[5.5.1] - 2026-10-17¶
Added¶
[mcp]takes themcpserver settings, and--toolslimits the tools.read_only,daemon,no_watch,allowanddenyunder[mcp](also in rootai-rulez.tomland the layered and local configs) set what the flags of the same name set, so.mcp.jsoncan beai-rulez mcpalone; a flag given on the command line wins.tools = [...]and the new--toolsflag register only the named tools, onmcpandmcp --serve-skills; an unknown name stops the server and lists the valid ones. The organization policy gains[mcp] read_only = true, which neither a key nor a flag can loosen (read_only = falsein the repository is reported asAR740). See Settings in the configuration (#440).mcp --exclude <glob>and[mcp] excludekeep paths out of repository discovery. Globs relative to the project root (**crosses directories, a pattern without a slash matches at any depth, a trailing slash names a directory) leave skill directories and the context index alike unread, before any file is classified, and the watcher ignores them. The flag adds to the configured list. See Excluding paths (#438).AR808 reserved-name: a skill or command named like a Claude Code built-in is an error. A project skill replaces the built-in of the same name, so.claude/skills/initsilently disabled/init.validatenow compares skill and command names and theirname:frontmatter, for hand-written files and without a config too, against the built-in commands and bundled skills of the Claude Code commands reference (aliases are not reserved), case-insensitively with_and spaces read as-. An intentional override is listed inlint.allow_overridesasinitorclaude:init. Projects with a skill namedreview,plan,run,debugand the like now failvalidateuntil they rename it or acknowledge it. See Strict validation (#442).
Fixed¶
mcp --read-onlyno longer advertises authoring. The server instructions are built from the registered tools, so a read-only server names only the tools it has and no longer points to the CRUD tools,generate_outputsor--read-only; an authoring prompt is listed only when every tool it calls is registered (#434).mcp --serve-skills --discover-skillsworks without a configuration. In a repository with no.ai-rulez/orai-rulez.toml,ai-rulez://catalogis built from the discovered skills and context (withscopesandskill_discovery),ai-rulez://configand the item templates are not listed, and only the tools that work without a configuration are registered (#435).- A renamed discovered skill is reported. When two repository skills share a name and one is served under its path, the server logs a start-up warning with both paths and the served name, and the catalog lists it under
skill_discovery.renamed(#436). presets = []no longer empties the context index. Rules, agents and commands are indexed from the conventions present in the repository whatever the presets; the presets still choose the skill directories (#437).- Scan findings in repository context name the file. An instruction file, rule or agent is reported as
context://./CLAUDE.md:2instead ofskill://CLAUDE.md/CLAUDE.md:2, and the message says the file is withheld, which an error finding does at every trust level (#439). find_contextwithpathhonours rule globs. A Claude Code rule'spaths:and a Cursor rule'sglobs:(relative to the directory holding the harness folder) now narrow the rules returned for a file; rules without them apply to their whole scope, andscopequeries are unchanged. The index cache format moved to version 2 and is rebuilt once (#441).- A repository skill wins over a builtin of the same name.
mcp --discover-skillsserved the builtin and dropped.claude/skills/<name>; the repository skill is now served and a start-up line names the builtin it replaces.validatereports a hand-written skill that collides with a configured skill asAR703, as it already did for.ai-rulez/skills, silenced bylint.allow_overrides(#443). - The catalog reports no served or shadowed repository skills as
[], notnull. - On Windows, editing a file in an ordinary directory no longer rebuilds the repository index: a directory event now counts only when the directory can hold an instruction file, skill, rule or agent.
- Pi prompts keep working links to the skills. A command's relative links are written against
commands/, so../skills/x/SKILL.mdreaches the rootskills/tree, but the Pi prompt generated from it sits two levels down in.pi/prompts/and its skills may live in a[plugin.pi] skills_dirof their own. The prompt copied the link unchanged, so withskills_dir = "skills"it resolved to.pi/skills/x/SKILL.md, which does not exist. Relative links that climb out of the command are now resolved fromcommands/and rewritten from.pi/prompts/to where the target is (../../skills/x/SKILL.md); with the default.pi/skillsthey are unchanged (#452).
[5.5.0] - 2026-10-17¶
Added¶
mcp --serve-skills --discover-skills[=repo|all]and[mcp] discover_skillsserve the skills already in the repository.repoindexesskills/,.claude/skillsat any depth and the configured presets' skill directories through the sameskill://resources,find_skillandload_skill, honouring.gitignore, skipping generated copies, and under the same scan, budget and--allow/--denyrules;alladds installed and included skills whatever their delivery. Name collisions follow the skill precedence (ai-rulez-defined first); shadowed skills are a start-up warning and appear in theai-rulez://catalogresource. See Discovering skills already in the repository (#432).installed_skillssourceaccepts~,owner/repo,github:owner/repoandfile://.~paths follow the same trust rule as any local path; shorthand expands to an https URL, leaving lock keys of existing entries unchanged.- Installed skills are discovered recursively and in harness directories. Skills are found to six levels deep, and an entry with no
pathand nothing atskills/<name>searchesskills/,.claude/skills/,.agents/skills/and the other harness skill directories. ai-rulez skill installand the MCPinstall_skilltool take everyinstalled_skillsfield:--include,--exclude,--name-prefix,--trust,--resources,--version,--tag-prefix,--include-prerelease,--min-release-age,--domain,--profileand--local-override(the tool arguments have the field names). See MCP server.- Import plugins and marketplaces from other harnesses.
[[installed_marketplaces]]and[[installed_plugins]]read Claude Code, Codex and Cursor marketplace indexes (a repository, GitHubowner/repo, a local directory or an httpsmarketplace.json) and Claude Code, Codex, Cursor, Gemini extension and Agent Plugins plugin layouts, and turn their skills, agents, commands, rules, hooks and MCP servers into ai-rulez content for every preset. Entries are pinned inai-rulez.lock(newmarketplaceandpluginkinds, honored by--locked,--frozenand--offline, moved byai-rulez update --kind plugin|marketplace), scanned before they are pinned and atgenerate, and covered by approvals, deny digests and the organization policy. Hooks are imported only withenable_hooks = true; a hook or MCP server that uses${CLAUDE_PLUGIN_ROOT}is kept only for a plugin inside the project. New commands:ai-rulez marketplace add|remove|list|updateandai-rulez plugin install|remove|list|update. See Installed Plugins. resourceson aninstalled_skillsentry sets what is bundled. It replaces the default with name, path or glob patterns, a leading!removing what an earlier pattern added, for example["*", "!docs"]or["rules"](#419).ai-rulez generate --refresh-remote(and[lock] refresh_on_generate = true) re-resolves remote includes, installed skills and skill sources to their newest allowed version and rewritesai-rulez.lockbefore outputs are written. The pre-pin security scan runs first; a refused tree aborts the run with nothing written, andgenerate --accept-findingspins it anyway after review, aslock --accept-findings. Without the flag nothing changes.skip_outputs(also accepted inconfig.local.toml) lists output kinds ai-rulez never writes, overwrites or deletes:instructions,agents_md,claude_md,rules,skills,agentsandcommands. A project can hand-writeCLAUDE.md,AGENTS.mdor skills and let ai-rulez emit only installed skills, plugins, MCP servers and hooks. A file an earlier run generated and that is now skipped stays in place with a one-time warning. See Managing only remote skills and plugins.- A configuration without a
.ai-rulez/folder:ai-rulez.tomlat the project root (overlayai-rulez.local.toml) declares installed skills, skill sources, includes, MCP servers, hooks, permissions and lint settings, withai-rulez.lockbeside it.generatewrites no instruction files and leaves hand-writtenCLAUDE.mdandAGENTS.mdalone; the manifests are.ai-rulez-manifest.jsonand.ai-rulez-manifest.local.jsonin the root. A config directory still wins over the file.ai-rulez init --minimalcreates it. See Config without a .ai-rulez folder. [session_start]andai-rulez session-startset a remote session up with no hand-written hook.generateadds aSessionStarthook to.claude/settings.jsonthat runsai-rulez session-start. In a remote (cloud) session (CLAUDE_CODE_REMOTE=true) it regenerates the outputs of the fresh clone (--lockedwhenai-rulez.lockexists, offline first) and returnsadditionalContextthat says the generated files are managed by ai-rulez and, when enabled, how to use its MCP server and served skills (find_skill, thenload_skill). Locally it does nothing unlesswhen = "always". The hook tries an installedai-rulez, thennpx -y ai-rulez@latest, thenuvx --from ai-rulez@latest ai-rulez(the latest published release, run from an empty private directory so a project.npmrc,node_modules/.binor uv configuration cannot substitute the package;versionpins a release instead), and never fails a session: the command always exits 0, is bounded bytimeout(default 120 s, process tree killed), runs generate with a scrubbed environment under a lock file, and reports a problem as one stderr line.ai-rulez doctorlists the launcher order and warns when the committed settings lack the hook, git ignores them, or the npm registry is denied or unreachable in a remote session. Keys:generate,when,mode,timeout,version,command,prompt,prompt_text; see Run generate when a remote session starts.- A slow scanner can be batched, run in parallel and given more than 15 minutes.
timeoutwas capped at 15 minutes for one run that received every skill.batch_sizeon[[lint.external]]splits{skill_dirs}into runs of at most that many skills, each with its owntimeoutand cached on its own;parallel(1 to 16, default 1) runs batches concurrently.[lint.scanner_policy] max_timeoutraises the ceiling of a run'stimeoutfrom 15 minutes up to a hard 6 hours; an entry cannot raise it (#427). See Strict validation. - Scanner results are cached per skill, and
--changednarrows the run. A scanner handed{skill_dirs}was cached as one blob and always received every skill, so one edited skill re-scanned all of them. Its result is now keyed per skill (tree hash of the skill, the command without the directories, scanner identity, environment), only the skills without a cached result are handed to the scanner, and under--changedor--sinceonly skills with a changed file are.--no-scan-cachestill bypasses the cache and--dry-runshowshitormissfor each skill (#426). See Strict validation. - External scanners see every file of a skill directory. Staging copied only
SKILL.md,scripts/,references/andassets/, so a file at the skill root or in another folder was never scanned. Every regular file now reaches the scanner, under the limits of an installed skill (no symlinks, no credential-like names, nonode_modules/.git/virtualenvs, no file over 8 MiB).stage_globsandstage_excludeon[[lint.external]]narrow the set, and--dry-runlists the same files (#428). See Strict validation. - The scanner result cache follows the script a scanner runs. A scanner written as
python3 scan.pywas keyed by thepython3binary, so editingscan.pykept serving the old findings. The first existing file argument of a known interpreter (sh,bash,python,node,deno,bun,ruby,perl,uv run,pipx runand others) is now hashed into the key, and the newcache_fileslist on[[lint.external]]names other files a scanner loads (globs relative to the project). Existing entries are not reused (#424). See Strict validation. - Scanner messages are no longer cut at 500 characters without a way around it.
max_messagein[lint.scanner_policy]and on a[[lint.external]]entry (the entry wins) sets how many characters of a finding message text output keeps. A cut message ends in...,--debugcounts the cut ones per scanner, and the JSON report (full_message) and SARIF output (fullMessageproperty) keep the whole text (#425). See Strict validation. validate --strict --externalcan tolerate scanner warnings.--strictmeant--fail-on warningfor every finding, and--strict --fail-on erroris rejected, so any scanner warning failed the run.fail_onin[lint.scanner_policy], and now on a[[lint.external]]entry, sets the failing threshold of scanner findings (AR011) independently of--strictand--fail-on:fail_on = "error"reports scanner warnings without failing on them. A scanner threshold replaces the run's for that scanner's findings, so a policyfail_on = "error"no longer lets--strictfail on a scanner warning (#429). See Strict validation.- A scanner
versionpin is checked against the scanner, not its launcher, andscanners doctorapplies the same check. The range was matched againstcommand[0] --version, which ispython3ornodefor a script scanner. The newversion_commandon[[lint.external]]names the argv that prints the scanner's version, andscanners doctor --externalnow reportsnot healthyfor a scanner outside its range instead ofok, exactly wherevalidate --externalrefuses to run it (#431). See Strict validation. find_skilltakespathandscopewhen the server discovers the repository.pathkeeps the skills that apply to a file (its scope and the ancestor scopes, the nearest scope winning a skill name);scopelists one scope, with the scopes below it whenrecursiveis true. Each discovered result names itsscope. See Monorepos: scopes and nested context.- The
ai-rulez://catalogresource gains ascopessection with each repository scope's per-kind counts and sources, narrowed by?path=,?scope=and&recursive=true;skill_discovery.indexreports the index size. See Monorepos: scopes and nested context. find_context,load_contextandcontext://<scope>/<name>resources serve nested instruction files, rules and agents (AGENTS.md,CLAUDE.md,GEMINI.md, Copilot instructions, rules and agents directories of the enabled presets) under the same scan, session budget and--allow/--denyrules as skills; the body is read from the repository on each call, never from the index. Registered only with--discover-skillsor[mcp] discover_skills. See Monorepos: scopes and nested context.- The server instructions name the first eight repository scopes by depth (then "and N more") when discovery is on, and the repository index holds at most 20000 items, warning at start-up when it keeps only the shallowest scopes.
- The repository index is cached between server starts. Summaries of the indexed files (by path, size, modification time and git tree) and the scan verdicts of content with no finding (by content hash, scan level and scanner) are kept in
<user cache>/ai-rulez/monorepo-index/<repo-id>/, never a file body, with mode 0600 through write-and-rename under a lock file. The index file carries an HMAC with a per-user secret, so an edited, planted, corrupt or version-mismatched file is discarded and rebuilt; a cache entry never bypasses--allow/--deny, andload_contextscans the body it reads on every call. On the benchmark monorepo a warm start takes about a tenth of a cold one. See The index cache. [mcp] index_cache = "user" | "repo" | "off"places the index cache.user(the default) falls back to a gitignored.ai-rulez/.cache/(.ai-rulez-cache/in the root layout, excluded through.git/info/exclude) when the user cache is not writable;repoalways uses it;offkeeps none. See Configuration.ai-rulez cache statusandai-rulez cache clear [--all]show and drop the index cache, with--format json(schema/cache-status.schema.json,schema/cache-clear.schema.json). They work in a directory with no configuration. See CLI reference.ai-rulez doctorreports the index cache (newindex-cachecheck): which location is in use, its size and counts, and a warning when it is not writable or its index file is corrupt, of another version or not authentic.- The repository index refreshes while the server runs. A debounced file watcher (fsnotify) rebuilds the catalog when a nested
AGENTS.md,CLAUDE.md,SKILL.md, a.claude,.agents,.codex,.cursor,.githuborskillsentry, or a directory appears, changes or goes, and the server announcesresources/list_changedthrough the newai-rulez://indexresource.--no-watchturns it off. See Live refresh of the index. create_skillandupdate_skillwrite a skill into a nested scope. Withscope(an existing project directory),allow_write: trueand an optionalharness, the skill goes to<scope>/<harness skills dir>/<name>/SKILL.mdafter symlink, enabled-preset, generated-copy and strict security-scan checks; it is the harness's own file, not ai-rulez content, and withai-rulez.lockpresent it is reported as drift untilai-rulez lock.update_skillis now annotated destructive and not idempotent. See Writing a skill into a scope.
Changed¶
- A configuration without presets is valid: a lint-only configuration.
presetsmay be omitted or[](it no longer fails withat least one preset is required, nor does the schema requireminItems: 1).generatethen prints one line, writes nothing and exits 0;generate --checkanddoctorreport no drift;validate,scanand[[lint.external]]work. See Lint-only configuration (#430). - A project that generates for Claude Code now gets a
SessionStarthook in.claude/settings.jsonby default. The generated file changes on the nextgenerate(a new file, or a new entry merged beside your own hooks), the hook appears in the "new hook commands" notice. The hook returns at once outside a remote session. Itsnpxanduvxfallbacks run the latest publishedai-rulezinstead of the version of the binary that generated the file, so the generated line no longer changes with each release; set[session_start] versionto pin one. Set[session_start] generate = falseto opt out and remove it. Other harnesses are not affected. - Skill discovery finds
skills/and the enabled presets' skill directories at any depth, as it already did for.claude/skills, and each discovered skill inskill_discovery.servedcarries itsscope. Askills/directory inside a hidden directory such as.agents/is left to that convention.
Fixed¶
- Installed-plugin notices appear once, and
marketplace addandplugin installconfirm what they wrote. "Installed plugin hooks are not imported" and the name-conflict warnings were logged once per load, so twice or more per command. Both commands now print a one-line confirmation on stderr (hidden by--quietand--format json). See Installed Plugins. - Installed skills log fewer false alarms. A symlink inside a whole-bundle skill is refused with a warning (it was logged as an error while the run succeeded), and "Unrecognized skill subdirectory" is no longer printed for a directory the entry's
resourcespatterns name, included or excluded with!, or leave out with["none"]. generate --refresh-remote --accept-findingsgenerates from the tree it accepted. The lock was rewritten to the accepted commit andgeneratethen failed the import scan, leaving the lock and the outputs out of step. The accepted tree now passes the scan of that run; the next plaingenerateneedstrust = "warn"on the entry, which the error hint names. The served-skill scan now uses the installed skill entry'strustinstead of the default. See Managing only remote skills and plugins.- CRUD commands and the MCP tools find the project from a subdirectory.
list,add,skill list,domain,profileandincludefailed with ".ai-rulez directory not found" below the project root, in both layouts; they now walk up likegenerate(stopping at the git root, never above$HOME), as do the MCP tools. MCPinit_projectstill initializes the given directory, but refuses when a directory above it in the same repository holds a configuration in any layout. mcp --serve-skills --discover-skillsworks in a repository with no configuration, as the docs say, instead of exiting with "load configuration: .ai-rulez directory not found". Without discovery the error remains.- MCP
init_projectnow refuses in a directory whose configuration isai-rulez.toml, instead of scaffolding a.ai-rulez/that would shadow it. generate --watchnow also watches local installed-skill sources,skill_sources, and installed marketplaces and plugins, not only includes andlocal_override.skill installandskill listreportgitforowner/repo,github:andssh://sources, which they logged and listed aslocal.ai-rulez local showprintsskip_outputsinstead of<redacted>: a list of output kinds holds no secret.- Schema and docs match the binary: the
refresh_on_generatetext names all ten flags that skip it, and[delivery.stub] description = ""is documented as the same as leaving the key out. name_prefixoninstalled_skillsandinstalled_pluginsprefixes the skill's output directory, not only itsname. Two entries that resolve skills with the same directory name and different prefixes used to fail withduplicate output ids, andname:disagreed with the directory. A prefixed skill is now written to<prefix><dir>/, for example.claude/skills/vendor-pdf/. Output paths of existing prefixed skills change (the old directory is removed as stale); lock keys do not.- Commands are written for
codex,antigravity,devinandwarpunder the defaultagents_md = true. Their command skills belong to the shared.agents/skillstree, which was rendered from skills only, so no command appeared and nothing warned. The tree now carries them (withagents/openai.yamlfor Codex); on Devin the command skill in the shared tree now hasdisable-model-invocation: trueinstead oftriggers: [user](withagents_md = falseDevin's own.devin/skills/<command>/SKILL.mdkeepstriggers: [user]). See agents.md sharing. - Commands find the configuration from a subdirectory.
generate,clean,lock,validateand the other project commands walk up from the working directory to the nearest directory holding a config (.agents/ai-rulez.toml,.ai-rulez/config.toml,.config/ai-rulez/config.tomlorai-rulez.toml), stopping at the repository root and never above the home directory, as Configuration always said; outputs stay relative to the config root.-C/--configand--config-dirstill win. The no-config error names the directories searched. - A local
installed_skillssource must stay inside the project. A committed entry could name any path, unlike includes and[[skill_sources]]; it now fails with the same error, with the same exceptions (config.local.toml, the user config). - A local installed skill is pinned in
ai-rulez.lockas alocal-skillitem, sogenerate --lockedfails when its files change. Runai-rulez lockonce to record the pins. - A local installed marketplace or plugin is pinned in
ai-rulez.lockas alocal-marketplaceorlocal-pluginitem holding the tree digest of its directory, so editing ahooks.jsonor hook script makeslock --checkandgenerate --lockedfail. Runai-rulez lockonce to record the pins. See Lock file. ai-rulez lockandmcp --serve-skillswork on a lint-only configuration (nopresets, orpresets = []). They failed withno preset configured that can render skills; such a project serves no skills, so the lock pins none.skip_outputs = ["commands"]and["skills"]classify a command written as a skill by its source. A command rendered as.claude/skills/<command>or.agents/skills/<command>was counted by its path, socommandsstill wrote it andskillsdropped it. It now followscommands; a command whose name a skill already has is not written, so that path stays a skill.- A stale empty directory is pruned in a configuration without a
.ai-rulez/folder..claude/skills/<old>stayed after aname_prefixchange because the project root counted as the configuration directory. - The
generate --refresh-remoterefusal names the flag that exists. The docs saidgeneratehas no--accept-findings;generate --refresh-remote --accept-findingspins the reviewed trees, and the refusal now says so. ai-rulez initrefuses a project configured byai-rulez.toml. It created.ai-rulez/beside the file, which then shadowed it.init --forcekeeps the file asai-rulez.toml.bak-<timestamp>and scaffolds;init --minimalalready refused.generate --userloads the user config in user scope, so its includes and installed skills may point outside the home project.- An installed skill is bundled whole, not only
references/,scripts/andassets/. Skills that keep documents besideSKILL.md(cli.md) or in a folder of their own (rules/,templates/) arrived without those files, so the links inSKILL.mdwere broken and generate warned that the folder was dropped. Every file in the skill directory is now bundled and listed in the generatedResourcessection, exceptSKILL.md,evals/,agents/and hidden files; a project's own skills keep the canonical layout (#419). - A whole-bundled installed skill is bounded. Credential-like files (
id_rsa*,*.pem,*.key,credentials.json,*.tfstateand the like) are never bundled, with a warning naming each; a single file over 8 MiB is skipped with a warning instead of failing the skill; a skill over 2000 files or 16 MiB fails with an error naming the limit and pointing atresources, and the skills of one entry together stay under 20000 files and 64 MiB, past which the entry fails with an error pointing atincludeandresources. See Installed skills. skip_outputs = ["instructions"]no longer skips files that merely share a root file's name. A skill resource, rule or docs page namedCLAUDE.md,AGENTS.mdorGEMINI.mdis still written; only root files at the project root and directly in a configured scope are skipped.- Telemetry in a root-layout project reads only
ai-rulez.tomland its local overlay, not a strayconfig.tomlorconfig.jsonat the project root. generatein a fresh clone no longer strips hooks from a committed.claude/settings.json. A clone has no machine-local record of the earlier run, so the hooks (and the self server of.mcp.json) already in the committed file looked like the user's; the upgrade fallback then took them back as "no longer rendered" and left{}. A run takes back nothing it still renders. Known limitation: a clone cannot retract a hook the configuration has since dropped, because the committed manifest is not trusted as proof of what ai-rulez wrote; the stale group stays in the committed.claude/settings.jsonandgenerate --checkreports no drift for it until it is removed by hand (see Settings). The Claude Code settings document, when it carries ai-rulez-owned hook groups, is no longer pinned as an output ofai-rulez.lock(a clone cannot tell it is wholly ai-rulez's, solock --checkandvalidatereportedAR982there).
[5.4.1] - 2026-10-16¶
Added¶
- The official GitHub Action accepts 16 optional
llm-*inputs matching the CLI's environment overrides. Settings persist to later job steps; omitted inputs preserve inherited configuration. Credentials remain in the caller's environment, and setup makes no model requests. - A GitHub Actions guide documents all inputs, environment precedence, provider credentials, Bedrock, outputs, and the distinction between the action ref and installed binary version. CI smoke-tests the composite action on Linux, macOS and Windows.
Fixed¶
- Successful stable v5 publications create or advance the floating
v5action tag after GitHub, npm, PyPI and Homebrew publication. Serialized updates reject prereleases, incomplete publications, divergent histories and rollback from older release reruns; full release tags remain unchanged. - Reject malformed action binary versions before writing runner outputs, and remove the unsupported
AI_RULEZ_LLM_BACKENDvariable from the LLM documentation. - The setup action honors the requested release even when another CLI is on
PATH. Download progress stays off the returned binary path, and Windows installs retain the fallback native DLL beside the executable.
[5.4.0] - 2026-10-16¶
Added¶
ai-rulez judgereviews agent configuration with verified evidence. Three reviewers (securityJR-S01-JR-S15, governanceJR-G01-JR-G07, qualityJR-Q01-JR-Q13) read every unit (a skill with its scripts and references, an agent, command, rule,CLAUDE.md,AGENTS.md, MCP or settings file; nested.claude/directories at any depth) together with the leads ofvalidateandscan. Reviewer candidates carry a verbatim quote and a line range that Go checks against the text that was sent (a failed one is corrected once, then deleted and counted); failed rubric criteria without a verifiable quote are preserved as unit-level findings; an adjudicator keeps, downgrades, merges or drops what survives, asymmetrically (a security finding is dropped only with a concrete benign context and a verbatim counter-quote); scores, statuses and the decision are computed in code. Text in a reviewed file is inert data behind nonce-fenced trust tiers. Findings carry a stable id, a chain of quoted steps and the strongest benign reading;--explain-dropslists everything dropped. Exit codes: 0 pass, 1 could not complete, 2 findings at or abovefail_on.judgegovernance reads your repository's own standards. Path-scoped rules of every ancestor.claude/rules/**/*.mdand the nestedCLAUDE.md,CLAUDE.local.mdandAGENTS.mdfiles with their@importsare attached to the unit; a governance finding must quote the file and the convention it breaks.judgeruns rubric skills. A skill with## C1 - titlesections is a rubric:--skill NAME|path:DIR,--skill-for CATEGORY=NAMEand[judge.skills]apply it, every criterion is reported aspass,failornot_applicable, and a failure is aJR-G06finding.judge --changed[=REV],--since REVand--impact. Only the units changed sinceREV(HEADfor a bare--changed) are judged, and with--impact(the default) so are the units a changed rule orCLAUDE.mdapplies to.- A
[judge]config table setscategories,fail_on,min_confidence,attach_rules,attach_claude_md, rubric skills with pins, prompt extensions and overrides (an override from a repository file needs--allow-prompt-override), a traps file and quote-pinned, expiring[[judge.suppress]]entries. Models, spend caps, rate limits, consent and the API key are not the repository's to set: such keys are ignored and reported. judge --format sarif, a v2 JSON report and ajudgeMCP tool. SARIF maps findings toai-rulez/JR-xxxrules withsecurity-severity,codeFlowsand tool notifications for coverage gaps;schema/judge-report.schema.jsondescribes the JSON. The MCP tool takes no model, price or cap and needsallow_llmin the call and in the user's[judge]table;estimatealways works.judgecoverage, caps and cache. A unit over the per-call budget is split into overlapping chunks;--max-cost(default 2.00 USD),--max-tokensand--max-callsadmit calls in priority order and report what did not fit;--concurrencyand--rpmbound the calls; answers are cached for 30 days (--cache-readonlyserves hits only). A unit not fully reviewed ispartialand the runincomplete.bench/judge/, the evaluation of judge. A synthetic corpus of 183 labelled units (malicious samples for every security code, benign lookalikes, false-positive traps with a malicious twin each, quality defects, a governance fixture repository, clean units), offline tests of the verifier and convention attachment, andtask bench:judge, which runs the pipeline on Bedrock and prints precision, recall and F1 against labels.judgeworks through Amazon Bedrock. The egress host is derived fromAWS_REGION,AWS_DEFAULT_REGIONor theAWS_PROFILEprofile (bedrock-runtime.<region>.amazonaws.com), so noAI_RULEZ_LLM_BASE_URLis needed; inference-profile ids are priced from the catalog and--price-input/--price-outputprice any other model;AI_RULEZ_LLM_TEMPERATURE=omitleavestemperatureout for every model (#414).validatedetects three more agent-targeted attacks.AR036(credential-store instruction in prose) andAR037(a bundled script that reads the environment or a credential file and sends it over HTTP) are warnings,AR038(an instruction to run commands without asking) is a note;AR003andAR004also flag an HTML comment that addresses the agent or tells it to hide a step (#415).- New builtin guidance for testing, review, configuration and decision records.
ai-governancegains thegenerated-filesrule and theconfig-placementandskill-authoringskills;documentationgains theadr-immutabilityrule and theadr-workflowskill;securitygains thecustomer-data-boundaryrule. Existing builtins tighten:testing-conventionsandtdd-workflow(never edit code under test to pass, verify a test can fail),code-quality-standards(scoped suppressions,TODO(PROJ-123)markers),agent-workflow,verification-before-completion,secrets-handling(no default for a missing secret) and thecode-reviewerandtest-writeragents.
Changed¶
- Dependencies and release tooling are current. Upgrade Go modules, documentation dependencies, CI actions and pinned tools, including liter-llm 2.2.4, Registry 3.1.2, OpenTelemetry Collector 0.162.0 and Vault 2.2.0. Windows CLI tests run in isolated, exhaustive process shards alongside the remaining packages; native Go cache paths are used, and failed Windows runs preserve valid compiled objects.
-
The README documents the full capability set. A feature table links authoring, generation, validation, judge reviews, evaluation, governance, publishing, MCP and the Go API to the detailed guides. Installation, contributor and release instructions are corrected.
-
validatealso lints the hand-written harness files of a configured project. A.claude/skills/x/SKILL.md, a nestedCLAUDE.mdor a.claude/rulesfile that the configuration does not list used to be skipped, sovalidateandscanreported a project clean that had findings. They are linted now; the filesgeneraterecorded and files with the generated header stay outputs.catalogandpublishkeep to the configured content. --changedtakes an optional base revision onvalidate,scanandjudge. A bare--changedis unchanged (it compares withHEAD: uncommitted and untracked work);--changed=REVcompares withREV, so--changed=origin/mainreplaces--since origin/main. Write the value with=: a separate word stays a path argument.--since REVkeeps working as the same thing.--changedwas a boolean flag before, so--changed=falseis no longer accepted; leave the flag off instead.validateis an order of magnitude faster on a large repository. On a monorepo of about 90,000 tracked files, 230 skills and deeply nested.claudedirectories (macOS, 18 cores, heavily loaded machine), config-freevalidate --strictwent from 195 s to 14.6 s,--pathwith three files from 11.1 s to 2.1 s and--changedfrom 60 s to 5.9 s, with identical findings. The time was spent in git: eachSKILL.mdstarted two git processes that read the whole index, andgit ls-files --otherswalked the work tree on one core. A run now lists the files once and shares the listing between target resolution, skills and the changed-files narrowing; the ignore state of all entry files is one batchedgit check-ignore; untracked files of a repository with a large index come from a parallel directory walk; and the Python files of thepytest -mmarker scan are read concurrently. Small repositories keep the singlegit ls-filescall.- Every command that calls a model resolves and refreshes the AWS credential chain for a
bedrock/model.judge,eval,verifiers,review,improveandsearchshare one client: the default chain (environment,AWS_PROFILE, SSO, assume-role, web identity, container and instance roles) is resolved through the AWS SDK's cache, temporary credentials are renewed before they expire, and a call refused as unauthenticated renews them and is tried once more.llm doctornames the Bedrock host. - Claude 5.x models get no
temperatureup front. The parameter is left out instead of being sent, rejected with HTTP 400 and retried; the retry remains for other models. - The response cache can expire entries and be read-only (
llm.Options.CacheMaxAge,CacheReadOnly). A reply that a caller validated is cached even when a provider stamped a content-filter stop on a whole, valid reply.
Fixed¶
- Private npm packages include the Windows fallback DLL. The package file list retains
bin/liter_llm_ffi.dllbeside a prebundled executable, so offline packages keep the native library needed by a DLL-linked build. - Virtual Go API workspaces produce relative output plans on Windows. Memory and snapshot workspaces use a fully qualified synthetic root, preventing duplicated output paths, incorrect stale-output removals and inconsistent plan digests. Disk-write and path-confinement restrictions are unchanged.
-
Strict validation distinguishes generated OKF listings and ignored unmanaged files. Bundle index and log files are recognized as generated listings; ignored discovery-only fixtures no longer consume authored load budgets, while configured sources remain budgeted.
-
Judge prompt inputs stay within their intended boundaries. Repository rubric and trap paths cannot escape the project or traverse symlinks; every outgoing model request redacts credential patterns and configured secret values. Scoped rules also attach other rules with overlapping globs.
- Large-repository discovery includes directories that replace tracked files. The optimized untracked walk now agrees with Git when a tracked file becomes a directory containing new instruction files.
- More
validatechecks and fewer misleading messages. NewAR039(a committed.claude/settings.jsonthat allowsBash(*)or every tool, or setsdefaultMode: bypassPermissions) andAR040(a settings key one or two edits from a known key, a probable typo);AR963reports a marketplace entry whose localsourcedirectory is missing;AR101reports an unclosed[or{in apathsglob as malformed instead of "matches no tracked file"; a--pathdirectory names the files it leaves out; the summary line saysvalidation:rather thanstrict validation:; config-free runs no longer print "Unrecognized skill subdirectory" warnings for content that is only linted;AR101is not raised when the repository index lists no file. - CLI.
sbom --helpand the docs use--type spdx-json(--formatis the report format);init --yesnever prompts to replace an existing configuration directory;init --format jsonprints only the result document; the.ai-rulez.bak-*directoryinit --forceleaves is excluded from git;convert --dry-run --format jsonwith nothing to convert prints an empty report on stdout;ai-rulez mcp --read-onlyno longer registers thejudgetool. judge. A unit is withheld only for credentials theAR001scan recognises, and the message names file:line and pattern; SARIF output validates against the 2.1.0 schema (no prose-onlyfixes); ignored[judge]/[llm]repository settings are reported as notices (text, JSON, SARIF);--estimatecovers every unit, counts calls once and exits 1 when over--max-cost; a failed rubric criterion always becomes aJR-G06finding; a named path through a symlink is refused.judgenever follows a symlink, andmcp --read-onlyno longer registersjudge. A named path that was a symlink to an in-repo file or directory, or lay below one, was resolved and judged although the docs say symlinks are never followed; it is now refused. Thejudgetool sends content to a model provider and spends money, so the read-only MCP tool set excludes it.judgeno longer loses a failed rubric criterion that has no verifiable quote. A criterion reported asfailincriteria_resultswith noJR-G06finding (an absence such as a missing## Verifyheading) or with a quote that is not in the file was dropped by grounding. It now surfaces as a unit-levelJR-G06finding (absence: true,origin: criteria, quote unverified when the reviewer gave one that did not verify), lowers the governance score, and the dropped candidate is listed with itsrubric_idunder--explain-drops.judge --estimateis consistent and honest about the cap. It estimates every unit instead of failing at the 200-unit limit (a notice says a real run would be refused), counts one definition of calls (estimate.callsplanned,estimate.calls_fitunder the caps, in the note too), includes the adjudicator schema in its prompt estimate, and warns and exits 1 when the estimate exceeds--max-cost. A test holds the prompt estimate within 15% of a tokenizer count of the prompts actually built.judgesays what it ignores in the repository config. A repository[llm]provider, model or base URL, andmodel/provider/base_urlin a repository[judge]table (whichvalidaterejects as unknown keys whilejudgeused to proceed silently), now produce a notice naming the key and where to set it; any other unknown[judge]key is an error as invalidate. All judge notices are also emitted as SARIFtoolExecutionNotifications.judge --format sarifis valid SARIF 2.1.0 again. A suggested fix was emitted asfixes:[{description}], which lacks the requiredartifactChanges; the suggestion now goes into the result message andproperties.suggestedFix. A test validates the output against the SARIF 2.1.0 schema.judgewithholds a unit only for a credential the security scan (AR001) recognises. The secret gate matched anytoken/secret/api_keyassignment, so code such asaws_session_token=credentials["SessionToken"]orsecret = vault.read_secret_value(name)was withheld althoughvalidatecalled it clean. Both now shareinternal/secretpat; the withhold reason namesfile:lineand the pattern, never the secret.- Fewer false positives in
validate --strictreference and credential checks (22 errors on a large monorepo went to 2, both genuinely dangling). AR402 also resolves a skill'sscripts/,references/andassets/paths from the area root (the directory that holds the skill's.claude/directory), and from the skill a sentence names ("see skillx,references/y.md"); the files of a skill no local file defines are not checked, and placeholder paths (references/services/X.md,..., globs) are skipped. AR301 ignores plugin-qualified names (pr-review-toolkit:code-reviewer), the skills the Claude Code harness and its bundled plugins provide (simplify,loop,security-review,dataviz, ...;lint.known_namescovers project-specific externals), a plain word before "rule" (a CSSdisplayrule; a hyphenated name or "rule file" still counts), and names a sentence denies or puts in the past ("the formerxskill", "NOT a registered slash command", "Don't add areferences/patterns/tier"); an unknown`/name` skillis information instead of an error, since a user-level skill is not in the repository. AR001 no longer reports a snake_case identifier such assaq_tn3270_scan_passwordas a credential. - AR301 no longer reports prose about a linter rule. A singular
`x` ruleafter a possessive or a tool name (eslint's,ruff,lint) is prose, and rule references are not reported in a tree with no rule items;lint.known_namessilences a name (#416). - Config-free
validatelints.claude/{rules,commands,agents}at any depth. Only the root.claudedirectory was discovered, so a nestedpkg/.claude/rules/x.mdwas neither linted nor indexed forAR301(#411). - AR101 resolves
paths:/globsof a nested.claude/rules/*.mdrelative to the directory holding.claude. Such globs were matched from the project root and reported as matching no file (#412).
Removed¶
- The v1 per-file score report of
judge, its--rubricand--min-scoreflags and the built-inbuiltin:instruction-qualityrubric.
[5.3.0] - 2026-10-14¶
Added¶
ai-rulez judge [paths...]grades instruction files with a model, with no configuration file. With no paths it judgesAGENTS.mdat any depth and the.agents/and.ai-rulez/Markdown; named files and directories work anywhere in the project, symlinks are never followed and generated files are skipped. The model comes from--model provider/modelandAI_RULEZ_LLM_*variables and the key from the provider's own variable; no repository or user config is read. A real run needs--allow-llm, prints the egress host and model first, refuses a non-https endpoint (loopback excepted), a model with no known price under--max-cost(default 0.25 USD) and a worst case above the cap, and honours an organization policy that forbids model use. A file with secret-looking text is not sent and counts as a finding unless--redactmasks it; long files keep head and tail.--estimate(--dry-run) sends nothing. The built-in rubricbuiltin:instruction-quality(or--rubric <file>) scores clarity, actionability, non-contradiction, scope, secrets and stale commands. Exit 0 all pass, 1 could not run, 2 below--min-scoreor skipped for secrets.--format jsonfollowsschema/judge-report.schema.json; see Judge.- The agents source reads an
AGENTS.mdin a subdirectory as context scoped to that directory.svc/api/AGENTS.mdbecomes the itemagents-svc-apiwith the path scopesvc/api/**, rendered like any path-scoped context, and tools that readAGENTS.mdthemselves get no copy. The walk skips symlinks, hidden, dependency and build directories and anything.gitignoreignores, stops at a subdirectory with its own ai-rulez configuration, is bounded in depth and size, and never reads a file with the generated banner. agents_md_split = truereadsAGENTS.mdas one context item per##heading. With the agents source the items are namedagents-<heading slug>(agentsfor the text before the first heading,-2,-3for a repeated heading) andlist context,remove contextand the MCP list and delete tools address them; removing one deletes that section fromAGENTS.mdand leaves the rest of the file byte for byte. Without the settingAGENTS.mdstays one item namedagents, now shown bylist context, andremoverefuses to touch it.validaterejects the setting without the agents source.validate --outputsand[lint] lint_outputs = truelint the generated files. The security, references and descriptions analyzers run over the bytesgeneratewrites (or would write, whether or not it has run), including files a past run recorded that the plan no longer writes. A finding carries"source": "output"in JSON,properties.sourcein SARIF and[generated output]in text; a generated copy of an authored item is skipped and a finding an authored file already has is dropped, so a problem is reported and counted once, at the authored path. Findings on outputs carry no fix. With no configuration, the files that carry the generated header are the outputs.schema/validate-report.schema.jsondocumentssource.validate,scananddoctorwork in a repository with no configuration file. When nothing is found in the working directory or above they printno ai-rulez config found; linting detected fileson stderr and use an implicit configuration (no presets, includes or hooks, default[lint], the git toplevel or the working directory as the root).AGENTS.md,CLAUDE.md, everySKILL.md,.claude/{skills,agents,commands,rules},.agents/**,.cursor/rules, an.ai-rulez/tree withoutconfig.toml, plugin manifests, hook files and.mcp.jsonare found by convention and treated as hand-written (a file with the generated header is left out).--pathand--globreplace the detection, nothing is ever written, the baseline needs--baseline <file>unless.ai-rulez/exists, and the exit codes are unchanged.generate,clean,lockand the other commands that need a configuration now fail withrun ai-rulez init.validateandscanlint files outside the content tree with--path,--globand[lint] paths. Each takes files or directories (globs are relative to the project root), repeatable; every one must resolve inside the repository root once symlinks are followed, and untracked files that the repository does not ignore are included. A plugin manifest, a marketplace, a hook file or an MCP file is recognized by its content, so one kept in an unusual directory gets the same checks (AR963,AR507,AR6xx) as the one in.claude-plugin/,hooks/or.mcp.json;SKILL.md,AGENTS.md,CLAUDE.mdand files underagents/,commands/,rules/andchecks/are classified by name and place. A file with the generated header is skipped,--update-baselinerefuses--pathand--glob, and--fixstill never edits a generated or out-of-root file.- A new Architecture page describes the load, merge, validate, render and write pipeline, the three source layouts, recursive runs, the plugin pipeline, the lint pipeline and the package map. The shipped
ai-rulezskill, the READMEs anddocs/monorepo.mdnow describe the agents source, config-free lint,judgeand the ordering of recursive output as the code behaves.
Fixed¶
validate --recursiveno longer drops a--pathor--globfile that lies outside every root. Each root linted only the picks below its own directory, so a pick elsewhere in the repository (a root-levelREADME.md, adocs/folder) was never linted and the run could pass. Such files are now linted once at the repository root with the implicit configuration, named in the notices, and counted in the exit code.--path <directory>honours.gitignoreand never walks.git. A directory pick (--path .included) walked the whole directory, so an ignorednode_modulesfailed the run on the 20,000-file limit or had its contents linted, and.gitwas read. In a git repository the directory now takes the tracked files and the untracked files git does not ignore; outside git it is walked skipping the usual dependency, build, cache and hidden directories below it.- A hand-written file can no longer hide from config-free
validateby quoting the generated marker.AGENTS.md,CLAUDE.mdand the other lint targets, and the harness traps, took any file withGenerated by ai-rulezorAI-RULEZ :: GENERATED FILEanywhere in its first 2 KB for generated output and skipped it silently, so asub/AGENTS.mdwith the marker under a heading and a hostile body was never scanned. One detector now servesgenerate, the lint targets and the traps: the marker must be the file's own header (the first comment after a byte order mark and frontmatter, or aContent-Hashcomment in a skill's or agent's frontmatter), and a storedContent-Hashmust match the body. A conventional file left out as generated is named in thenoticesinstead of being dropped silently; a header alone is still text anyone can write, so read that list. - The harness traps recognize the real
AI-RULEZ :: GENERATED FILEheader. Only the oldGenerated by ai-rulezbanner counted, so a filegeneratewrote was judged as hand-written (a warning, with an autofix thatgeneratewould overwrite) and a gitignored output with the real header was never scanned. Such a file is now an error that names a preset bug, with no fix, and gitignored outputs are scanned. - The docs state that a named non-markdown file gets only the credential scan. A text file named with
--pathor--globthat is no content kind is checked forAR001andsecret_patternsonly; the injection, hidden-character and link rules apply to markdown and the recognized JSON kinds. --recursivereports a not-linted file once. A file picked with--pathor--globthat two roots both skipped appeared twice in thenoticesof the combined report and on stderr; duplicates are now dropped, in first-seen order.judgereports every file it skips, and a skipped named file no longer aborts a list. A file over 2 MiB, a binary file and a generated file named on the command line or found below a directory areskippedentries with a reason (a named file over 2 MiB used to fail the whole run); a run with no paths still leaves generated files out silently. The summary'scalls,tokensandcost_usdwere 0 with--max-cost 0and no--max-tokens; they now count the answered calls. Cap amounts in messages keep four decimals, so a$0.0004cap is no longer shown as$0.00. The docs now say thatjudgehas no--max-calls, grades files one after another, and passes a key only fromAI_RULEZ_LLM_API_KEY_ENVto a custom endpoint.judgerefuses a real run when it cannot tell which host receives the file content. With a provider ai-rulez does not know (anything but openai, anthropic, gemini, mistral, groq, deepseek, xai, openrouter and cohere, whose hosts come from liter-llm's provider registry) or a bare model, and noAI_RULEZ_LLM_BASE_URL, the egress line used to say "the provider's default endpoint" and the run went ahead. It now stops and namesAI_RULEZ_LLM_BASE_URL;--estimatestill works.judgestrips terminal escapes from everything it prints and defuses fence markers of any length. A file name, a provider error or a rubric path with an escape sequence or line break reached the terminal and the JSON report; control characters now become spaces in every string of the text and JSON output. A run of five or eight</>in a judged file survived the old three-bracket replacement and could still form a fence; a space now breaks every run before it reaches three.judgeno longer sends a secret in a rubric, a path or a file's copy of the API key. A--rubricfile with secret-looking text, or the value of the API key variable, is refused (--redactdoes not apply to a rubric). The value ofAI_RULEZ_LLM_API_KEY_ENV's variable and of the provider's own key variable is treated like anAR001secret in judged files: the file is skipped, or masked with--redact, and the value is never printed. A file path is masked the same way in the prompt and the report.judgerefuses the home directory and/outside a project, and bounds its directory walk. Run from$HOMEor/with no git repository, it listed unrelated projects'AGENTS.mdfiles and could walk for minutes; it now stops with the "not a project" refusalvalidategives, also when paths are named. Outside git the walk stops with an error past 100,000 entries, and default discovery skips directories deeper than 8 levels.judgelists a named file it skips instead of reporting "nothing to judge", and refuses default discovery in a folder that is not a project. Naming only a file over 2 MiB or a generated file ended withnothing to judgeand no report, and a binary file alone exited 0 although nothing was judged. The skipped file is now an entry with its reason andbytes, and the run exits 1 withnothing judged(a file skipped for secrets still exits 2). In a directory with no repository and noAGENTS.md,.agents/or ai-rulez config at its root, a run with no paths now refuses and asks for explicit paths, instead of judging theAGENTS.mdof every unrelated project below it.scan --path .envno longer reports "no findings" for a file it never read. A file named with--pathor--globthat was not a recognized kind (.env,.yaml, any other extension), was over 2 MiB, was not a regular file or left the project through a symlink was dropped without a word. Now every such file is reported as<file>: not linted (<reason>)on stderr and in a newnoticesarray of the JSON report, and a text file of any extension that you name is scanned for credentials (AR001) as plain text (at most 2 MiB, never through a symlink). A file named with--paththat cannot be read, is not a regular file or is over 2 MiB fails the run with exit 1; the same file matched by a--globis a notice.schema/validate-report.schema.jsondocumentsnotices, anddocs/cli-reference.mdnow says that a--paththat does not exist or leaves the project root exits 1.--path agents.mdisAGENTS.mdon a case-insensitive file system. On macOS and Windows the lower-case spelling was not classified as an instruction file, and naming both spellings linted the file twice. A path now takes the spelling the file system stores.--outputsno longer hides a different secret of the same type. A finding on a generated file was dropped when an authored file had one with the same rule and message, and AR001 masks the secret, so a stale generated copy holding a rotated key looked like the authored one. The flagged line is now part of the comparison.- A project without configuration no longer lints the files
generatewrote as if they were hand-written. Only the oldGenerated by ai-rulezbanner was recognized, so anAGENTS.mdorCLAUDE.mdwith the realAI-RULEZ :: GENERATED FILEheader was linted as a source, untagged, and its findings were reported twice next to the authored ones. It is now skipped, and with--outputslinted and marked"source": "output". validate --recursivein a repository with no configuration no longer passes unchecked. It printedNo configuration files foundand exited 0 even with a secret ina/AGENTS.md. It now lints the implicit root exactly as the run without--recursivedoes (--pathand--globhonoured), so the findings and exit codes match;--config-onlystill reports that no configuration was found.schema/validate-report.schema.jsondeclares the finding keyfile, notpath.validate --format jsonandscan --format jsonhave always emittedfile; the schema described a key that never appeared. A test now compares the keys findings emit with the schema, and checks that SARIF carriessourceinresults[].properties.- The refusal to lint a home or filesystem-root directory no longer suggests
--pathor--glob. Those flags do not lift the refusal; the hint now says to run the command from inside a project directory. - A nested
AGENTS.mdin a directory named with glob metacharacters is scoped to that directory only.app[1]/AGENTS.mdproduced the scopeapp[1]/**, which matchesapp1; the directory name is now escaped (app\[1\]/**). - Nested
AGENTS.mditem names no longer depend on the walk or on.gitignore. Directories whose slugs collide (a/b,a-b,a b;app[1],app1) or sanitize to nothing got-mdor numeric suffixes that changed when an unrelated directory was added or ignored, which churned the outputs and the lock. They now carry a short hash of their path, decided over all directories found before.gitignoreis applied. - Removing an
AGENTS.mdsection is atomic and safer.remove contexton a split item now writes a temporary file and renames it overAGENTS.md, keeps the file mode, refuses a symlinkedAGENTS.md, and aborts when the file changed in between instead of overwriting the edit. A leading BOM is kept, CRLF files work, and a file with lone CR line endings gets a clear error instead of "could not locate the sections". remove contexton a splitAGENTS.mditem namesAGENTS.md, not a file that never existed. It printed.agents/context/agents-<name>.md; it now printsAGENTS.mdand says which section was removed (--format jsoncarries the same path).- Nested
AGENTS.mddiscovery says when.gitignoreis not applied and ignores symlinked configuration. Outside a git repository nothing is ignored; a root.gitignorethere now produces a one-line warning, and the behaviour is documented. A symlinkedconfig.tomlorai-rulez.tomlno longer marks a subdirectory as a project of its own. read_skill_fileandlist_skill_resourcesreject out-of-rangeoffsetandlengthinstead of converting them. A JSON number such as1e30orNaNwas cast toint, which Go leaves platform-dependent. One shared check now accepts whole numbers from 0 to 2147483647 and answers anything else with an error naming the argument; a negativelist_skill_resourcesoffset used to be clamped to 0 and is now an error too.find_skill,search_skillsandload_skillreject out-of-rangelimitandbudget_bytesinstead of converting them. The same1e30orNaNcast tointthatoffsetandlengthhad; the arguments still treat zero or a negative number as the default, but a non-finite, fractional or larger-than-2147483647 value is now an error naming the argument.read_contextandupdate_context(andshow/edit context) handleAGENTS.mditems. For a splitAGENTS.mditem the read returned the whole file (or failed with "outside the configuration directory") and the update failed with "file not found". The read now returns just that section, and the update rewrites just that section atomically (mode, line endings and BOM kept, refused when the content would add, drop or rename a##heading or the file changed meanwhile). The unsplit itemagentsreads as the whole file andupdate/removerefuse it with a message saying why.generate --watchanddoctorcover nestedAGENTS.mdfiles with the agents source. Watch mode watched only the rootAGENTS.mdand.agents, so editing a nested file regenerated nothing; it now watches the nested files the loader reads (a file created while it runs is picked up after the next run).doctornow reports a generated nestedAGENTS.mdleft in the source, as it did for the root file. The docs also state that outside a git repository the whole tree is read and that a## Agentsheading becomesagents-agents.- Updating the last section of a split
AGENTS.mdworks for CRLF files and keeps the file's ending.edit contextandupdate_contexton the last section of a CRLF file always failed with a bare-CR error, because a\rwas added to the final line even when no\nfollows it, and an LF file lost its final newline. A line now gets its\ronly when a newline follows, and the file keeps or lacks its trailing newline as it had. - The docs state two limits of the split and nested
AGENTS.mdsources. Every item of a splitAGENTS.mdpins the whole file inai-rulez.lock, so editing one section reports "source changed" for every item of that file inlock --checkandgenerate --check. Adding a nested directory whose slug collides with an existing one (a bnext toa-b) adds the path hash to both, so the existingagents-a-bis renamed too. - OKF metadata is projected into the native plugin bundles. A skill, command or agent written in Open Knowledge Format (
type:plus anx-ai-rulez:block) was copied into theclaude,agent-plugins,opencodeandpibundles with that frontmatter, which the hosts do not read. The bundledSKILL.mdand command and agent files now carry native frontmatter (name,description,license, ... at the top level,type,titleandx-ai-rulezremoved), body and source file unchanged. - A combined Pi and OpenCode bundle refreshes the OpenCode
package.jsonmetadata, and OpenCode uses the Pi package name. Withpackage_json = "merge"an authored rootpackage.jsonkept a staletype,main,exportsand@opencode/pluginrange, and the two runtimes could name the package differently; the OpenCode entry point and dependency are now written over the authored ones,[plugin.pi] package_namenames the combined package, andverify --pluginchecks those keys. - A non-object
dependenciesvalue no longer panics the npm manifest merge. A Pi or OpenCodepackage.jsonwhosedependencieswas not an object made the combined-bundle merge panic; it is now left to the disagreement check. AR9T0recognizes the rootplugin.jsonandmcp.jsonwritten foragent-pluginsandcopilot. Only the Codexrootlayout counted as writing them, so a bundle withruntimes = ["codex", "agent-plugins"](Codex on the defaultlegacylayout) was reported for files it ships on purpose. The two runtimes now count like the Codexrootlayout, which changes theAR9T0findings for such a bundle.- A combined Pi and OpenCode
package.jsononly takes the OpenCode-owned keys from the OpenCode manifest. Withpackage_json = "merge"the OpenCode manifest overwrote every Pi key exceptfilesandkeywords(name,version,description,scripts, ...) and skipped the disagreement check. It now refreshes onlytype,main,exports,dependencies,homepage,licenseandpackageManager; any other key that differs fails withruntime npm manifests disagree. - A credential in an MCP server URL path is no longer published.
publish --emit mcp-server-cardand the ARD manifest skip, with a note that never prints the URL, a remote server whose path has a segment that looks like a key (/mcp/sk-abc123, aghp_token, an AWS key id, or an opaque token of 32 or more letters and digits). The path is otherwise emitted as written, which the docs now state.
[5.2.1] - 2026-10-14¶
Fixed¶
- A live reload no longer restores a served catalog that is missing a skill saved a moment earlier. The catalog cache was keyed after the configuration load, so a save finishing in between cached the half-saved render under the finished file's key. The cache is now written only when the files did not change during the build.
generate --checknames the toplevel.gitignorerelative to the real working directory. A directory reached through a symlink or a Windows short name made the path climb out and back in.- On Windows,
plugins/ormarketplaces/being a file is reported instead of read as no definitions. Windows lists a file as path-not-found.
[5.2.0] - 2026-10-14¶
Added¶
- Served skills that bundle scripts can now be used over MCP.
list_skill_resources,load_skill,get_skilland theskills/list,skills/getandresources/listmetadata report each file'smode,executableand aninterpreterhint;load_skilladdsfiles_accesssaying the files are not on disk and how to fetch and run them; andread_skill_filetakesencoding: base64,offsetandlength, so binary assets and large files no longer fail with "is binary" and are read in chunks of at most 256 KiB under the session budget. Amaterialize_skilltool is not added; the docs give the client-side recipe (#405). publish --emit mcp-server-cardwrites one MCP Server Card per remote MCP server, and the ARD manifest embeds the same card. The card follows SEP-2127 (Final) as defined by the experimental server-card extension, vendored at a pinned commit and checked by new conformance tests. Header values and environment values are never written (a header becomes a required input, marked secret when its name or value is a credential), and stdio servers have no card. The ARDmcp-server-card+jsonentries now carry this card instead of the earlier ad hoctransportobject and no longer list stdio servers. The standards table lists the card as partial (experimental extension); the "agent bundle" row says no upstream spec exists (#392, #393).gitignore_excludekeeps committed outputs out of the managed.gitignoreblock, andgeneratewarns about tracked files and negation conflicts. Entries use gitignore notation (AGENTS.md,**/CLAUDE.md,.claude/skills/) and never apply to machine-local or secret outputs;gitignorestays a boolean.generatewarns once when the block matches a file git tracks (the whole recursive block is checked, nested paths named, unanchoredAGENTS.md/CLAUDE.mdlines called out) and when a!patternof yours un-ignores a managed line, naming itsfile:line(#398).default_layers = [...]inconfig.tomlactivates committedconfig.<name>.tomllayers without an environment variable. They merge beforeAI_RULEZ_LAYERS; a listed layer with no file is an error, andai-rulez config layersshows each layer's origin (default,env) in text and JSON (#401).[[marketplace.plugins]]merges bynameacross layers and the local overlay. A layer can add, override or drop (remove = true) one plugin without restating the list; it used to replace the whole array. The newplugins_authoringandmarketplaces_authoringlists merge the same way (#401).- Several plugins and several marketplaces from one root.
[[plugins_authoring]]entries (or.ai-rulez/plugins/<name>.toml) each bundle only the skills, commands, agents, domains, MCP servers and hooks they select, with their own runtimes and author;[[marketplaces_authoring]]entries (or.ai-rulez/marketplaces/<name>.toml) each write their own index, provenance sidecar andplugins/<name>/bundles to theiroutput_dir.[[marketplace.plugins]]accepts the same new fields.generate --pluginandverify --pluginprocess them all in one run;--plugin-nameand--marketplace-namelimit a run, andverify --plugin --format jsonaddspluginsandmarketplacesstatuses and names the plugin and marketplace on eachdriftentry. The single[plugin],[marketplace]members and nested-root layouts are unchanged (#401). AR9T1 plugin-selector-unmatchedwarns about a plugin selector that matches no content, andvalidaterejects duplicate plugin or marketplace names, a marketplace that lists an unknown plugin or none, two marketplaces with oneoutput_dir,[plugin]with[[plugins_authoring]],[marketplace]with[[marketplaces_authoring]], and two member plugins sharing a name (#401)..agentsand the rootAGENTS.mdcan be the source of truth.source = "agents"(or a configuration in.agents/ai-rulez.toml, preferred when present, with the manifest, lock andlocal/in.agents/.ai-rulez/) readsAGENTS.md, Agent Skills in.agents/skills,.agents/rules|agents|checksand the ai-rulez extensions.agents/commands|context|domains/<name>, and translates them to every other tool without ever writingAGENTS.md, a scope'sAGENTS.mdor anything under.agents/. Every other tool gets its full generated files (agents_mdis forced off) and tools that readAGENTS.mdthemselves get no copy of it. A generated header marks a file as output so it is never read back as source,cleannever removes the source, and the lock, watch, guard, doctor,add/remove/list, the MCP tools and recursive discovery learn the layout.init --source agents(and theinit_projectargument of the same name) starts one,convert --into .agentsandinit --source agents --from ...migrate other tools' files into it. See .agents as a source.- A root-level
action.ymllets any workflowuses: Goldziher/ai-rulez@<ref>directly. It installs a sha256-verified release binary, resolvinglatestor a pinned version, caches the download and addsai-ruleztoPATH;scripts/pre-commit/run-ai-rulez.shgainedAI_RULEZ_RESOLVE_ONLY=1, which prints the resolved binary's path instead of running it.
Changed¶
- Behaviour change: the
mcp-server-card+jsonentries of the ARD manifest changed shape. Each entry'sdatais now a full MCP Server Card (SEP-2127) instead of the earlier ad hoc object: thetransportobject is replaced by the card'sremoteslist, and stdio servers, which a card cannot describe, are no longer listed at all. A consumer that readtransportor looked for a stdio server in the manifest must readremotesinstead, and a manifest published before 5.2.0 differs from one regenerated now.
Fixed¶
- The
default_layersdescription says where layers sit in an.agentsproject (.agents/.ai-rulez/), in the schemas, the type docs and the MCP server docs. - A UTF-8 BOM before a generated banner no longer hides it. The generated-file check strips a leading BOM, so such files are still recognized as generated by
generateandconvert. - The compact generated header keeps its wording for every default project. The
STRUCTURE:line was reworded for all projects, which changed every compact-headerContent-Hash; only an.agentssource project uses the prefixed directory names now. - Interrupting a recursive
validateorgenerate --checkstops it from starting more roots, and loggers made with attributes or groups share one write lock. The workers kept taking roots after Ctrl-C, and the run now exits 1 naming the interruption instead of reporting the roots it did read as the whole result. Child loggers (With,WithGroup) each had their own lock on the shared stderr stream, so concurrent records could interleave. - A recursive
validate --strictloads the git trees of different repositories in parallel. The tree cache held its lock for the whole load, so roots of separate repositories queued behind each other; now only roots of one repository share a load. - The shared git index listing of a recursive run no longer answers where
git ls-files -- :(literal)would differ. A path below a submodule now goes to git (as before the listing), which fails closed there. validate --recursive --strict --externalchecks one root at a time. The scanners' dry-run plan went to stdout and--write-baselinewrote one file from several roots at once; the output could interleave and the baseline lose entries.- Warnings from the roots of
validate --recursiveandgenerate --check --recursivecome out in root order. Refused symlinked content,--gitignorenotices, policy warnings and configuration-load log lines were written from the worker goroutines, so their order changed from run to run; each root now collects them and the run prints them with that root's result. init --source agentswarns about a configuration it shadows or is shadowed by. Writing.agents/ai-rulez.tomlbeside an existing.ai-rulez/config.toml(which is then ignored), or aconfig.tomlbeside an existing.agents/ai-rulez.toml, said nothing untildoctorran.- A hand-written file that quotes the generated header is no longer taken for output. The check looked for
AI-RULEZ ::anywhere in the first 2 KB, so anAGENTS.mddocumenting ai-rulez was ignored as source (andconvertdropped such a file); the banner must now be the file's header, and# Content-Hash:lines count only inside the frontmatter. CRLF files are still recognised. - Layers of an
.agentsproject are documented and their messages name the right directory.config.<name>.toml,config.local.tomland the plugin definition files are read from.agents/.ai-rulez/(not beside.agents/ai-rulez.toml);config layersand thedefault_layersmissing-file error now name that path. The.agentspage also documents that the first#heading ofAGENTS.mdis dropped when rendered, thatlist,removeand the MCP tools cannot address theagentscontext, and that a symlinkedAGENTS.mdis never followed. - With
source = "agents", the generated header and the new-commands warning name the real source. The header saidSource: .agents/.ai-rulez/ai-rulez.tomland "modify .agents/.ai-rulez/ content" (or.ai-rulez/), and the warning named aconfig.tomlthat does not exist; they now name.agents/ai-rulez.toml(or.ai-rulez/config.toml) and.agents/. source = "agents"projects no longer get the legacy-layout notice, andmigrate okfrefuses them. The notice named the unread.ai-ruleztree, andmigrate okfwould have rewritten it (and madeindex.mdfiles there) instead of the.agentscontent.- With
source = "agents", an output below a symlinked directory no longer writes into the source..claude/rules -> ../.agents/rulesor.claude/skills/foo -> ../../.agents/skills/foobypassed the filter that keepsgenerateoff.agents; paths are now resolved through symlinks first and such outputs are dropped with a warning. AR981names the drifted file in an.agentsproject. The path was joined to the state directory (.agents/.ai-rulez/.agents/rules/x.md) instead of the project root.- With
source = "agents",.github/copilot-instructions.mdno longer repeats the text ofAGENTS.md. The exclusion of tools that readAGENTS.mdthemselves was dropped for a root file several presets share, so Copilot still got a copy; the file is now negated directly when every configured preset writing it readsAGENTS.md. Gemini CLI is no longer treated as a reader, sinceagents_mdis off and its settings load onlyGEMINI.md. generate --pluginno longer writes into the.agentssource. Withsource = "agents"the Codex index.agents/plugins/marketplace.json(and any bundle placed under.agents) silently replaced a hand-written file; plugin outputs onAGENTS.mdor under.agents/are now skipped with a warning.validateno longer reports OKF findings twice when the[okf]bundle is the configuration directory under another spelling. A relative path, a trailing separator or a symlink to it defeated the comparison.- A plugin sidecar generated on Windows records no file modes. It recorded
0644for every file (Windows has no executable bit), soverify --pluginon Linux reportedmodedrift for each script (#403). generate --checkjudges the managed.gitignoreblock as a set and follows a symlinked.gitignore. A block with a linegeneratewould remove was reported in sync; it is now drift, as is a missing entry. With a symlinked.gitignore(git does not read it)--checkreported nothing; it now compares theai-rulezblock in.git/info/exclude. Outside a git repository--checkalso counts machine-local outputs thatgenerateputs in.gitignore.validaterejects a marketplaceoutput_dirthat sits inside another's. Only identical directories were refused, sooutandout/inner(or an emptyoutput_dirbeside any other) wrote one marketplace's index and bundles into the other's tree.AR9T1points at the plugin's own definition. It anchored on the firstname = "<plugin>"line ofconfig.toml, which could be a marketplace or another table, and ignored.ai-rulez/plugins/<name>.toml; it now reports the plugin's table or its definition file.- A
plugins/ormarketplaces/directory that cannot be listed fails the load, and unknown keys in a definition file are errors. AnyReadDirfailure (a permission error, a file where the directory should be) was read as "no definitions" and the plugins vanished silently. Unknown keys in.ai-rulez/plugins/*.tomland.ai-rulez/marketplaces/*.tomlalready fail the load with the file and key named; they are not ignored like unknown keys inconfig.tomllayers. - A leading
/in agitignore_excludeentry anchors it at the root./AGENTS.mdalso excludedsvc/AGENTS.md; it now names only the root file, as in a.gitignore. - The server card namespace derived from
github.com/owner/nameisio.github.owner, and other hosts add the owner. It wascom.github, shared by every repository on GitHub, andhost/owner/namedropped the owner. - A remote MCP server URL with a query, fragment or
${...}placeholder is no longer written into the server card and ARD manifest.?api_key=...or?token=${TOKEN}was published verbatim; the server is now skipped with a warning that does not repeat the URL. - A directory symlink in a skill no longer bundles the credentials behind it. Files and subdirectories reached through a followed link (
references/ref -> docs/, or a link to the project root) skipped the credential check a linked file gets, sodocs/.env, a nestedid_rsaor.git/was copied into the output ofgenerateandgenerate --plugin; they are now skipped with a warning. AR001now detects the AWS secret access key. Only the access key id (AKIA/ASIA) was reported; a 40-character value assigned to a name containingaws_secret_access_key(any case,=or:, optionally quoted) is now a finding with the value masked, and is redacted before text goes to a model or a publish step (#409).- The AR021 documentation no longer claims
references/is skipped. A skill'sreferences/files are scanned likeSKILL.md(exempt them with[lint] example_paths = ["**/references/**"]);examples/andtemplates/are not scanned at all (#410). - The shared
.agents/skillstree honoursdelivery. Withagents_mdon, codex, cursor, gemini, copilot, opencode, zed and the other presets reading it wroteservedskills with theirscripts/andassets/and nodynamic-skillsstub, sovalidate --strictreportedAR991on its own output. The tree now leaves served skills out and carries the stub, as the claude tree does; if a preset reading it cannot call MCP, the tree keeps the served skills as files and adds the stub (#402). AR501andAR502resolve${CLAUDE_SKILL_DIR}in skill frontmatter hooks. A hook command reaching a bundled script through${CLAUDE_SKILL_DIR}/...or$CLAUDE_SKILL_DIR/...is checked against the skill's own directory for existence and, when run directly, the executable bit; a missing script passedvalidate --strictbefore (#406).builtinsaccepts a positivedomain/nameto load a single item of a pack, andbuiltin:domain/nameworks in profiles. A positivedomain/namewas read as a pack calleddomain/nameand silently ignored; it now loads only that item (still applied after!domain, so["security/secrets-handling", "!security"]keeps just that item;domainwins overdomain/name). A root!domain/nameis now honored for packs loaded throughbuiltin:<pack>. Behavior change: an unknown positive entry (builtins = ["nope"], a typo in an item) now failsvalidatewith the valid choices instead of being ignored; unknown exclusions remain warnings.builtins showlabels its item names asbuiltinsidentifiers (#399).validateruns every analyzer in an OKF-layout project, with the OKF findings next to theirs. A configuration directory with a rootindex.md(whatinitscaffolds) used to stop at theAR9B*checks: the references (AR101, AR201), security (AR001, AR004, AR005), resource (AR402) and hook analyzers never ran,--analyzer referenceswas ignored, and with--strictany OKF warning ended the run. The OKF findings now go through the lint run, so[lint]severities,--fail-on, the baseline and--analyzer(including--analyzer okf) apply to them, and--recursivereports them too. Exit codes change: a project that passed only because the other analyzers were skipped now exits 2 on their findings, and an OKF error no longer hides them. A strict run that exits 2 no longer prints "Configuration is valid" (#396, #408).generate --pluginquotes the path of ascripthook withoutargs. It wrote${CLAUDE_PLUGIN_ROOT}/hooks/<name>bare, whichvalidate --strictflags as AR963; the Claude, Cursor and Gemini hook files now carry"<root>/hooks/<name>". A hook withargsis exec form and is unchanged (#404).generate --pluginfollows an in-project symlink inside a skill directory. It dropped such a link without a word while plaingeneratecopied the target, so the bundle shipped without the file andverify --pluginpassed. Both modes now apply one policy: a link to a file or directory inside the project is followed, any other is skipped with a warning naming it (#407).verify --plugindetects a lost executable bit..ai-rulez-generated.jsonnow records each output's mode and a bundled hook or skill script that is no longer executable is reported as drift of kindmode(also in the--format jsondriftarray andschema/verify-plugin.schema.json). A sidecar from an earlier release, which has no modes, still verifies until the nextgenerate --plugin(#403).generateno longer inserts a blank line under# END ai-rulez, and a!dir/negation is honored like a!fileone. The lines after the managed block are kept byte for byte, and!.claude/skills/now drops.claude/skills/from the block as!CLAUDE.mddropsCLAUDE.md;doctorno longer reports an output you re-included on purpose, nor suggests ageneratethat would change nothing (#398).generate --checkverifies the managed.gitignoreblock. A missing block, or one lacking an entrygeneratewould add, is reported asmissing:/stale: .gitignore (managed block)(note: "managed block"in--format json) and exits 2; a recursive check judges the shared toplevel block once after the last root (#400).- The machine-local manifest is gitignored whenever it exists, with
gitignore = falsetoo.generateignored.ai-rulez/.generated-manifest.local.jsononly for machine-local outputs orgitignore = true, sogit add -Astaged a digests-only manifest and every later run warned that git tracks it; the entry no longer pulls in.ai-rulez/local/or the overlay patterns (#397). - Errors for a committed config layer name the layer file. They said "local config" and "the local overlay"; they now read
unknown key(s) in layer config.lint.toml: ...and point at the layer files (#401). validate --recursiveandgenerate --check --recursiveare faster on large monorepos. The roots are now checked concurrently (at mostGOMAXPROCS) and reported in path order, so output and exit codes are unchanged; the roots of a repository share one listing of the git index instead of reading it once per root to ask whether the config is tracked; each root prints a[n/N] path (time)progress line on stderr (not under--quietor--format json);doctor --debuglogs the duration of each check. This improves the time but the issue stays open for the reporter's profile (#376).
[5.1.1] - 2026-10-13¶
Fixed¶
generate --dry-runno longer listscreate-direntries. A directory marker creates nothing since #379, so the plan listed directories a real run never made (#394).- A baz rule whose glob points inside a nested ai-rulez root stays in the root
AGENTS.md.generatewrotesvc/web/AGENTS.mdfor it while the nested root's own run reported it as an orphan, sogenerate --check --recursivefailed on every run; the rule is now inlined into the root file in every mode andgeneratewarns naming it (#395).
[5.1.0] - 2026-10-12¶
Added¶
[plugin.pi] package_nameandskills_entry = "directory"for the Pi runtime.package_namesets the npm name instead of@<owner>/pi-<plugin-name>, and underpackage_json = "merge"an authorednameis now kept when it is unset;skills_entry = "directory"writes"./skills"topi.skillsand"skills/"tofilesinstead of one entry per skill.fileslistsREADME.mdandassets/only when they exist in the bundle (#383).verify --pluginnames the outputs that drifted. Text output lists each path with its kind (changed,missing,stale-source,obsolete) and--format jsoncarries adriftarray of{path, kind}; the existing fields are unchanged (#381).[plugin] skills_dir_owned = truedeclares the bundle's skills directory fully owned.verify --pluginthen reports a skill directory or file it did not generate (a ghost left by a rename, a stray file directly underskills/) as drift of kindextra, andgenerate --pluginremoves it; the default stays off, so an unrecorded skill directory is never touched (#387).
Fixed¶
generate --pluginremoves the directories it leaves empty when it deletes unrecorded files.skills/<skill>/old/deep/is removed bottom-up; a directory that still holds anything stays (#387).generateno longer leaves empty.claude/agents,.claude/skillsand.claude/rulesdirectories in roots without such content. Output directories are now created only when a file is written into them, and empty ones an earlier run created are removed; a directory holding any file is never touched;generate --planno longer lists those directories asmkdirentries (#379).AR990no longer flags a single code spanload_skill <name>. That is the form its own message recommends; a bare name in backticks, a source path or another call form on the same line is still reported (#377).AR9T0looks for component paths only under the plugin's own root. A.mcp.jsonin a parent directory of a nested plugin was reported as if the plugin shipped it (#378).AR9T0also covers a rootplugin.jsonthat ai-rulez does not write. Codex prefers it as the manifest, so a stray one under the defaultlegacylayout is reported; a rootCLAUDE.mdis not on the list because Claude Code does not load one from a plugin root (#384).verify --pluginreports unrecorded files and symlinks in a bundle's skills tree, andgenerate --pluginremoves the files. A file inside a generated skill directory that.ai-rulez-generated.jsondoes not list is drift of kindextra, and a recorded output that is a symlink is kindsymlink;generate --plugindeletes the unrecorded files (--dry-runshowsdelete-stale:) and never touches a skill directory it does not generate or your sources (#380).generate --pluginno longer writes through a symlinked output, andverify --pluginreports a symlinked directory. A generated file that is a symlink, or has a symlinked parent directory, now fails generation (and--dry-run) with an error naming the link, and the link target is left unchanged;verify --pluginreports the link as drift of kindsymlink(#386).generate --plugin,verify --pluginandvalidateno longer warn that a skill'sagents/directory is unrecognized.agents/openai.yaml(Codex display metadata) is bundled by the plugin passthrough, so the warning said the opposite of what happens, and it repeated for every skill (#382).
[5.0.5] - 2026-10-12¶
Added¶
[rules] baz_rest = "skip"keeps rules baz does not select out of the rootAGENTS.md. Underbaz_default = "none"the rules that do not opt in, and rules excluded by!baz, were still inlined into the root file (127 KB in one project);skipdrops those path-scoped items whenbazis the only preset writing that file. The default"root"is unchanged (#371).-
[mcp] self_server_launcher = "binary-then-npx"starts an installedai-rulezand falls back to npx. The generated.mcp.jsonentry is ash -cscript with--serve-skillspassed as an argument, so a project needs no hand-written shell wrapper;self_server_versionpins the npx fallback and it excludesself_server_command(#372). -
Layered configuration:
config.<name>.tomlfiles merged overconfig.toml.AI_RULEZ_LAYERS=ai,custom(orAI_RULEZ_PROFILE, orconfig.WithLayers) activates layers in order, a later layer winning; tables merge per key, named lists such asmcp_serversandinstalled_skillsmerge by name, andconfig.local.tomlstill applies last.ai-rulez config layerslists the layers and which are active; a layered view is never written back asconfig.toml(#370).
Fixed¶
- Loading a project with many domains runs one
git ls-filesfor the configuration directory, not one per domain's skills and commands directory. Each listing took about a second in a repository with an 89,000-file index, so a 28-domain project spent half a minute in the content scan; the per-directory listing remains the fallback when the configuration directory is not covered (a nested repository, no git) (#376). generate --checkno longer opens every file of the work tree to look for orphaned outputs. The walk for the generated files of a removed nested root read a header from each file below the project root, which took over a minute on an 89,000-file monorepo with no output; it now reads only files a preset can write (a root file name such asCLAUDE.md, or a file below an output directory such as.claude/), and--debuglogs how many files it visited and how long it took (#376).validateandgeneratewarn when[profiles]defines nodefault. A root config with[profiles]but nodefaultprofile ordefault =setting generates the built-in default, which sees only root content, global builtins and included domains, so every other domain's root outputs were removed with no message. The warning names the domains and suggestsdefault = ["*"](#374).generatesays where a baz rule went when its glob points into.ai-rulez/or.git/. No nestedAGENTS.mdis written there (it would be read back as a source file), so the rule was inlined into the root file or, underbaz_unscoped = "skip", dropped, without a word; both cases now warn and name the rule (#375).generatewarns about an MCP server entry left behind after its[[mcp_servers]]block is removed. An entry is taken out of.mcp.jsononly on the machine that recorded writing it (the gitignored local manifest), so a fresh clone kept it silently. The committed manifest now lists, as a hint only, the server entries written from the shared configuration, andgeneratewarns once per leftover entry and names the file; it never removes an entry on that evidence (#373).--serve-skillsis no longer appended to aself_server_commandthat is ash -cscript. The flag landed in$0of the script; such a command is now left as written, anddocs/configuration.mddocuments the"$@"plus trailing"sh"pattern (#372).
[5.0.4] - 2026-10-12¶
Added¶
AR990flags path-style and bare pointers to served skills. A static file that names a served skill by source path (.ai-rulez/domains/<d>/skills/<name>/SKILL.md,.../skills/<name>/references/x.md) or by a hyphenated bare name in prose (see skill dev-cycle) is now reported, and the diagnostic suggestsload_skill <name>; a non-hyphenated word or another tree'sskills/directory stays clean forAR301(#363).[delivery.stub]customises the generateddynamic-skillsstub.descriptionreplaces the description every session lists, andbodyorbody_file(relative to the config directory) replaces the text, which may use{{server}}and must still namefind_skillandload_skill;validaterejects a blank description, both body forms, unknown variables and a missing or escaping file (#362).- Instruction files tell agents how to load served skills. While skills are served,
AGENTS.mdand each MCP-capable harness's root file get a short "Unlisted skills" section: callfind_skill, thenload_skill, on the serving MCP server, with the.ai-rulez/skills/<name>/SKILL.mdfallback.[mcp] skills_instructions = falseleaves it out andskills_instructions_textreplaces it ({{server}},{{skills_dir}}) (#359). [header] nestedshortens the header of a root inside another root. The defaultcompactwrites one line,Generated by ai-rulez from <path>; do not edit, in place of the outer root's 10 to 50 line banner,omitwrites none andinheritkeeps the full header; theContent-Hashline is kept, sogenerate --checkstays valid. A custom[header] textis kept unlessnestedis set explicitly (#364).[header] mcp_line = falsedrops the MCP server line from generated headers. Without it the line is derived from[mcp](#360).targetsaccepts negations, and thebazpreset has a per-rule opt-in.targets: ["!baz"]excludes a rule from one preset (a negation always wins; a rule with only negations still goes everywhere else),[rules.baz] include = [...]andbaz: truefrontmatter opt rules in underbaz_default = "none", and the directory-target workaround is documented (#366).[rules] baz_unscoped = "skip"keeps directory-less path-scoped rules out of the rootAGENTS.md. A rule scoped by**/*.goor**/BUILDcannot be nested for Baz, so it was inlined into the root file that Claude also imports;skipdrops it whenbazis the only preset writing that file (a file shared withcodexand the others keeps it). The default"root"is unchanged, and thebaz_budgetwarning now names the inlined rules (#365).[rules] baz_dedupe_max = Kcollapses a rule copied into more than K directories. The rule is written once to the directory covering all its globs, up to the rootAGENTS.mdwhen they share only the repository root (never withbaz_unscoped = "skip"), instead of one copy per directory (#367).
Fixed¶
- A non-recursive
generateno longer deletes the outputs of a nested root. The orphan walk of a top-level output directory (for examplepackages/from a scope) descended into a nested root below it and removed that root's committedCLAUDE.md/.claude/rulesfiles; it now stops at any directory holding its own config, as the stray-tree walk already did (#368). - The header's MCP line names the server the project launches. It was a hardcoded
npx -y ai-rulez@latest mcp; it now followsself_server_command,self_server_versionand--serve-skills, and is left out when[mcp] self_server = false(#360). - A nested root's generated header names its own config. The
Source:line and the directory hints of a root below the repository root readproducts/em/webapps/.ai-rulez/config.toml, its path from the git work tree root, instead of the repository-root.ai-rulez/config.toml;generate,generate --recursiveandgenerate --checkagree on it (#361). docs/baz.mdno longer claims Claude Code loads a nestedAGENTS.mdwhenever its directory has noCLAUDE.md. Claude Code readsAGENTS.mdnatively only when noCLAUDE.mdexists in the working directory or above it (v2.1.277 or later), so with theclaudepreset's rootCLAUDE.mdshim a baz-only nested file is not loaded; the page now states the three cases and what the documentation does not settle (#369).
[5.0.3] - 2026-10-12¶
Added¶
- One MCP server serves the skills and the authoring tools.
ai-rulez mcp --serve-skillsnow registers the config tools alongsidefind_skill/load_skilland theskill://resources, in one process under one name (ai-rulez), so a project no longer runsai-rulezandai-rulez-skillsside by side;--read-onlyregisters only the read-only tools, the generated.mcp.jsoncarries a singleai-rulezentry runningmcp --serve-skills, andAR993counts that entry (#358). ai-rulez mcp --serve-skills --daemonshares one long-lived server per repository. The session becomes a stdio proxy: it connects to a per-repo daemon (a0600socket in a0700directory under the user cache, keyed by repository toplevel, ai-rulez version and the serving flags), spawns it on first use, and serves in-process on any failure. The daemon exits after 15 minutes with no client, removes its socket, and reloads the catalog on change; the mode is not offered on Windows (#321).[plugin] shared_resourcescopies shared directories into every bundled skill. Each listed directory (e.g.references/) is written under every skill for every runtime and recorded in the provenance sidecar, a relative../../references/x.mdlink fromSKILL.mdis rewritten to the copy, and a name clash with a skill's own file is an error rather than a silent overwrite (#353).[plugin] provenance_headers = falsewrites bundled files without the generated header. Markdown/JS/TS/PY outputs are byte-identical to their sources while.ai-rulez-generated.jsonstill records every output, soverify --plugin, obsolete pruning andpublish --emit agent-pluginsare unchanged and a header-less file ai-rulez did not record stays hand-written (#354).[plugin] version_fromreads the plugin version from an existing manifest.version_from = "package.json"(alsopyproject.tomlandCargo.toml) resolvesproject.version/package.versionfor the manifest, the lock andverify --plugin, so a bump by release-please or changesets shows as drift; it is mutually exclusive withversion, andvalidatereports a missing file, a missing key or both keys set (#355).- The Pi runtime can reuse the shared skills tree and keep its own
package.json.[plugin.pi] skills_dirwrites each skill once into that tree and pointspi.skillsat it instead of a second copy under.pi/skills/, andpackage_json = "merge"updates only the keys ai-rulez owns, keepingscripts,devDependenciesand the rest (#356). AR9T0 root-component-shippedwarns when a single-plugin root holds paths hosts load by default. A plugin whose bundle root is the repository root is reported for each default component it ships there (.mcp.json,hooks/,agents/,commands/,settings.json, ...), naming the host that loads it, withcontent_rootor a monorepo member as the fix (#357).- A plugin can set its package manager and its OpenCode dependency.
[plugin] package_manageremitspackageManagerin the generated OpenCode/Pi rootpackage.json, and[plugin] opencode_dependencyoverrides the@opencode/pluginconstraint (default bumped to^2.0.26). The JSON serializer no longer escapes&as\u0026(#350).
Fixed¶
- The manifest-free orphan walk leaves a committed plugin bundle alone.
generate --pluginwrites its bundle into the project tree with the same generated header the generator writes, sogenerate --checkreported every bundle file as an orphan andgeneratedeleted them; the walk now honours the bundle's.ai-rulez-generated.jsonprovenance sidecar (#350). - A hosted MCP server's
oauthblock reaches.mcp.jsonfor thecopilotandcursorpresets. Only themcpsidecar wrote it, sogeneratefailed with "presets write different content to the same path" whenevercopilotorcursorwas active (#334). - A top-level key written after a
[[table]]array header is a load error. The swallowed-key check only descended[table]maps, so a key after[[mcp_servers]]was silently read as a key of the last element and ignored (#344). generate --pluginrewrites an existing generated header instead of stacking another. A plugin whosecontent_rootoverlaps its output tree (content_root = ".") reads its own decorated output as the source, so each run added a header block,verify --pluginnever converged andpublish --emit agent-pluginsfailed at theAR9N0preflight (#351).AR301no longer reads prose as a skill pointer. A backticked term before the noun "skill" ("abothskill") and askills/path segment that is not this project's tree (src/mastra/public/skills/shared/<name>/SKILL.md) were reported as unknown references; the matcher now requires a pointer form, so the real ones (reference: skill x,skills/x,.claude/skills/x/SKILL.md) still fire (#352).
[5.0.2] - 2026-10-11¶
Added¶
- Profiles select domains with
*and!name. A profile entry may be a glob (*,backend-*) or a negation (!legacy) as well as an exact domain name; a profile that is only negations is an error, andprofile addaccepts the same spellings (#324). [claude] claude_md = "none"writesAGENTS.mdwithout theCLAUDE.mdimport shim, for projects that want Claude Code to readAGENTS.mdalone. The default stays"shim"(#317).AR9Q0 claude-md-shadows-agents-mdreports a hand-writtenCLAUDE.mdthat shadows the generatedAGENTS.md; opt in with[lint] agents_md_only(allow list:agents_md_only_allow) (#318).AR9R0 served-skill-bundled-filesandAR9R1 both-dmi-pointerwarn when aservedskill shipsscripts/that are never written to disk, and when static content points at adelivery: bothskill whose frontmatter disables model invocation (the Skill tool refuses it). The served-skill docs recommenddelivery: bothwhen a skill runs bundled scripts (#336, #337).doctor, andvalidate --strict --mcp-user-scope, report MCP servers shadowed in a harness user scope with a different endpoint, naming both scopes and the removal command, and generated servers that differ only by name while pointing at the same url (#330).[[mcp_servers]]expresses an explicittypeand a pre-registered OAuth client. A hosted server takesoauth = { client_id, callback_port, scopes }, rendered in.mcp.jsonfor Claude and VS Code and as a TOMLoauthtable for Codex, and skipped with a warning by a harness that cannot express it.AR602warns when a hosted server names noclient_id, or when one name resolves to two endpoints across sources (#334).tokens --format jsoncarries a stablealways_loadedkey, and its diagnostics (including the over-budget line) go to stderr so stdout stays one parseable document (#340).- The
bazpreset's nested surface is controllable.[rules] baz_default = "none"nests only items whosetargetsselectbaz,baz_dedupe = truewrites one shared parentAGENTS.mdwhen several globs share a parent, andbaz_budgetwarns above a byte budget. Every default is unchanged (#341). [plugin] includebundles the files a plugin's MCP command runs. Globs relative to the content root are copied verbatim into the Agent Plugins package and recorded in.ai-rulez-generated.jsonwith content hashes, soverify --pluginandpublish --emit agent-pluginscount them andAR9O2stops firing once a./command resolves to an included file (#349).
Performance¶
- The rendered serve catalog is cached on disk.
ai-rulez mcp --serve-skillsre-rendered every served skill on each start; the rendered catalog is now cached (HMAC-authenticated, re-admitted on restore, and guarded against symlinks and oversized files), so a warm start is about 0.2 s (#320). - The per-file security scan runs once across a worker pool.
validateand thegeneratesecret gate scanned every file serially and re-read the same file once per caller; the scan now runs once per file acrossGOMAXPROCSworkers, each file is read once per run, and content reached by more than one item is scanned once. Findings, their order and every exit code are unchanged. On the lint benchmark the full run drops from 110 ms to 45 ms and the security analyzer from 71 ms to 15 ms (#312). generateno longer runs the whole security family before writing. Its secret gate read only AR001 but ran every security rule (shell, injection, hidden characters, hosts, blobs) over every file; it now runs the credential patterns alone, with the same AR001 findings. On a 590-file project,generateCPU (user) fell from 1.4 s to 0.65 s.validatebuilds the split text of each file once per run instead of up to three times and skips body-scan patterns on lines that cannot match (user CPU 1.9 s to 1.6 s). Output is unchanged.
Fixed¶
generate --checkreports an orphaned generated file without the manifest. A generated file whose source was removed was not reported when the gitignored.generated-manifest.jsonwas absent (CI, a fresh checkout), because the orphan pass relied on it.--checknow also reports any file carrying a Content-Hash header that no output emits (#313).- Progress frames are no longer written to a non-terminal stream. With stdout/stderr redirected,
generate,validateand--checkwrote spinner and bar frames with ANSI codes and carriage returns into the output; the animated UI now draws only on a terminal, andNO_COLORalso suppresses it (#314). .mcp.jsonentries have one shape. A configured server was written with"disabled": false, a key Claude Code does not document for an active server, while the self-server entry had"type": "stdio";disabledis now written only for a server that is turned off, and every stdio entry carries the sametype: stdio(#315).- A skill, rule or agent file that cannot be loaded is reported. A
SKILL.mdover the 8 MiB content limit (or any other read failure) was dropped from the output with no message fromgenerateorvalidate; the scan now logsfailed to load skill file|content file|agent filewith the path, as it already did for commands. - AR002 accepts the emoji presentation selector after U+2139, U+203C, U+2049, U+3030 and U+303D (
ℹ️,‼️,⁉️,〰️,〽️). They are letter or punctuation bases, which the rule rejected as a possible hidden payload. migrate v5pins[mcp] self_server = false, so a migrated project no longer gains the ai-rulez MCP server in.mcp.json(once per nested root in a monorepo) until it opts in.- One managed
.gitignoreblock for a recursive run. A recursivegeneratewrote the block into every nested root; it is now written once at the git toplevel, and an explicitgitignore = falseis warned about rather than overridden (#316). - A plugin-only root no longer writes a header-only instruction file. A nested root that declares only
[plugin]/[[plugin.mcp]]but keeps a preset wrote aCLAUDE.md/AGENTS.mdholding nothing but the generated header; the root file and the sharedAGENTS.mdare now skipped with a warning naming the file and the preset (#327). installed_skillsandskill_sourceshonorinclude/exclude. An entry that selects skills by glob still resolved its root as a single skill directory, soinstalled_skillserrored withexpected SKILL.md at skills/<name>andskill_sourcesserved nothing; wheninclude/excludeis set and no path is given the entry is now a directory of skill directories, and a glob that matches nothing is reported (#329, #331).AR301andAR990share one reference matcher.AR301treated a backticked term followed by the plural noun "skills" as a pointer, so prose failed validation;AR990only saw backticked names, so the documented non-backticked pointer forms (reference: skill x,skills/x,.claude/skills/x/SKILL.md,subagent_type: x) went unreported. One matcher now backs both (#335, #339).AR990,AR992andAR402no longer misfire.AR992reported a preset that writes no skills tree (claude+baz) on every generate;AR990saw a bare backticked served-skill name but not aload_skillpointer in the same file; andAR402rejected areferences/path that names a sibling skill's file. Each is now consistent with its own rule (#342, #343, #346).AR990fires only for always-loaded content. A served skill naming a served sibling is not reported (#319).AR201/AR401resolve names from a scope's emitted directory, so a container image tag in a scope is no longer a false positive (#325).validate --recursivereports findings in its summary and counts the same roots asgenerate --recursive. It printed "All N config(s) are valid" while exiting 2, and counted a different root set (#338).validatenames the nested roots a non-recursive run skips (#326).- Plugin
${PLUGIN_ROOT}is rewritten in a plugin'sargsandenv, not only itscommand(#322). generateoverwrites a committed output in a fresh worktree without--force. With no.generated-manifest.json, a bundled skill asset ai-rulez had written was refused as a file "ai-rulez did not write"; a raw output below a directory this run renders a skill file into is now treated as ai-rulez's own copy, while a hand-written file and a symlink stay refused (#332).- Stale generated files are pruned and reported without a manifest. A removed agent's
.claude/agents/<name>.mdstayed on disk and--checkexited 0; the retired-file set now includes every header-carrying orphan the render no longer emits, shared bygenerateand--check(#333). - Bundled skill assets are tracked in the manifest and reported as orphans. A skill's
scripts/copy carries no header, so a committed manifest now prunes one whose source is gone and--checkreports it; with no manifest a header-less asset is left alone (#328). tokenscounts theAGENTS.mda canonicalCLAUDE.mdimports. Withagents_md = truethe always-loaded total counted the import shim but not theAGENTS.mdit loads, understating a budget gate; a runtime whose preset importsAGENTS.mdnow counts that file once (#347).- A top-level key written after a
[table]header is a load error, not a silent no-op. TOML readagents_md,builtinsorgitignoreafter[rules]as keys of that table and ignored them, so the settings stopped applying with only a warning; loading now names the key, the table and the fix (#344). - A generated file left by a removed nested root is reported and pruned.
generate --check --recursivesaid up to date when a whole nested root was deleted and itsCLAUDE.md/.claude/rules/*remained; the manifest-free orphan pass now walks outside the current output roots, so--checkreports it andgenerateremoves it (#345). - A key unknown to a nested table is no longer reported as a misplaced top-level key.
descriptionin a[[plugin.mcp]]entry was told to "move it above the first [table]", which would change its meaning; the hint now applies only where TOML really can have swallowed a top-level key (#348).
Documentation¶
- Corrected the Claude Code rules-folder nesting claim (#323).
[5.0.1] - 2026-10-11¶
Added¶
- Compose roles.
--roletakes a comma-separated list, as doroles show,roles resolve,tokens --roleandmcp --serve-skills --serve-role. A composition is the union of its members: the domains and items they keep are combined, and where two members setskill_modeordeliveryfor the same skill the more specific key wins and, on a tie, the role listed last. A composition is pinnable —ai-rulez lock --role a,brecords the union under the canonical namea,b. See Roles. AI_RULEZ_PROFILEandAI_RULEZ_ROLEselect the active profile and role when the matching flag is not given; an explicit flag always wins (#309).- A
domainfield on[[mcp_servers]],[[installed_skills]]and[[skill_sources]]ties a remote or fetched resource to a domain, so a profile or a role selects it by domain exactly as it selects local content.profileskeeps working and is an additional filter (#308). installed_skillsgains theskill_sourcesconveniences:includeandexcludeselect several skills from one repository with a single entry,name_prefixnamespaces their names, andtrustsets the repository-scoped scan level (#310).
Fixed¶
- A kept obsolete plugin file is no longer forgotten after regeneration. An obsolete generated file you edited by hand is preserved with a warning, and
verify --pluginnow keeps failing while it exists, as documented, instead of passing once the provenance sidecar is rewritten (#307).
Documentation¶
- Answer how profiles and roles differ, how installed skills and skill sources differ, and how to compose roles.
[5.0.0] - 2026-10-10¶
Run ai-rulez migrate v5 (--dry-run to preview, --check for CI); upgrade steps are in Migrating to v5.
Positioning¶
v5 repositions ai-rulez as a standards-compliant lifecycle tool for agent knowledge and capabilities: author in one source of truth, generate for every harness, bundle and publish to the open formats, lint and validate against each standard's own schema, and govern through signing, approvals and policy. The README, the documentation site (organized as Author, Generate, Bundle and publish, Validate, Govern, Operate) and the package metadata follow that story. "Planned" means the code does not yet generate and validate the standard. Supported today: OKF (Open Knowledge Format v0.2), Agent Plugins, ARD, Agent Skills, AGENTS.md, llms.txt, MCP server config, CycloneDX/SPDX SBOM, in-toto/DSSE/Sigstore and OpenTelemetry (OTLP); the MCP server card and "agent bundle" are planned. Pinned spec versions and conformance status are in Standards.
Breaking¶
- Config format is v5. A
version = "5.0"project is required; a 4.x config is rejected withrun ai-rulez migrate v5and a 2.x/3.x config tells you to install ai-rulez 4.x first. YAML/JSON configs (config.yaml,config.json, local variants) and separatemcp.toml/mcp.yaml/mcp.jsonfiles are no longer read —migrate v5converts them toconfig.tomland[[mcp_servers]].migrate v4andinit --format yaml|jsonare removed, andmigratenow has real subcommands (migrate v5,migrate okf; themigrate 5/migrate v5.0spellings are gone). See Migrating to v5. - Go module is
github.com/Goldziher/ai-rulez/v5: install withgo install github.com/Goldziher/ai-rulez/v5/cmd/ai-rulez@latest. npm, PyPI and Homebrew are unaffected. - One flag taxonomy. The only shorthands are
-C -D -T -q -y -n -o;-nis--dry-runeverywhere and--config-dirhas no shorthand (declared once, globally).--outis--output(a file) or--output-dir(a directory);generate --strictis--strict-config,lock --strictis--refuse-findings,sign --policyis--org-policy;--json/-jis--format json;generate --no-fetchis--offline; the confirmation skip ofcleanand theremovecommands is--yes/-y. No command takes a config path argument any more: useai-rulez -C <path> validate. A removed spelling fails with a hint naming its replacement. - Removed aliases and flags:
g,clear,v,check, the hiddenmcp --transport/--address/--port,--verbose/-V, and the deadgenerate --update-gitignore,--no-configure-cli-mcp/-Mand--skip-cli-mcp/-S. UnprefixedDEBUG,QUIETandVERBOSEno longer change the CLI; useAI_RULEZ_DEBUG/AI_RULEZ_QUIET.guardis now listed in--help. - Command results go to stdout, diagnostics to stderr.
list,domain|profile|include|skill list,clean --dry-runandlock --checkprint their result on stdout;-qhides only progress and success lines. The content commands (add,remove,domain,profile,include,skill) print the created/removed/rewritten path on stdout, take--format json, honor the global--config-dir/-C, and gain the newshowandeditverbs;add checktakes--severity,--toolsand--targets.ai-rulez --versionandversionprint to stdout, and a group command with no subcommand prints help and exits 0 (was 1). - One error rendering and one exit path. Every failure prints
Error: ...(then validation errors and aHint:) on stderr and, under--format json, a{"status":"error", ...}document on stdout. Exit codes:0ok,1could not run,2findings/drift/failed gate,3(lockonly) served skills left unpinned. A declined or non-interactive confirmation exits 1; prompts go to stderr. - Report commands share one flag vocabulary and one JSON contract.
sbom --format cyclonedx|spdx-jsonissbom --type;--formatistext|json;telemetry hook --format json|tomlis--syntax;eval runprintstextby default (--format markdownkeeps the old output). Every--format jsondocument carriesschema_version, and lists return{"schema_version":1,"items":[...]}instead of a bare array. Each report has a published schema underschema/(table indocs/cli.md#json-contracts). verifychecks attestations and provenance only. Drift isgenerate --check, which re-renders and also catches hand edits and deleted files; a bareai-rulez verifyexits 1 and names it.validateruns the content checks by default.--config-onlyrestores the old config-only run;--strictnow means warnings fail (--fail-on warning), sovalidatecan exit 2 where it exited 0. Malformed YAML frontmatter is a finding,AR306 frontmatter-malformed(generatestill exits 1), and a configuration that loosens the organization policy (AR740) exits 2 fromgenerate,validate,lock, recursive runs anddoctorinstead of 1.generate --lockedand--frozenfail closed without anai-rulez.lock(exit 2, hint to runai-rulez lock).cleankeeps generated files you edited by hand unless--include-editedis passed;--yesonly skips the confirmation prompt.usage ...andreport usage|evalsfold intotelemetry ...with no aliases (telemetry hook|record|feedback|report [evals]);[lint.budget]/[lint.tolerate]are renamed[lint.ratchet](JSONratchet_exceeded), while[lint.budgets.<kind>]size limits keep their name.- New defaults.
agents_md = true:AGENTS.mdis canonical andCLAUDE.mdimports it, withagents_md = falsefor per-harness files. Generated headers carry only the per-fileContent-Hash([header] hashes = "full"restoresSource-Hash). The managed.gitignoreblock is opt-in (gitignore = trueorgenerate --gitignore). liter-llmis the only model backend and builds need cgo. Every model call goes through liter-llm 2.2.0, linked statically, so ai-rulez is built withCGO_ENABLED=1and a C toolchain; release binaries grow about 35 MB. The[llm] backendkey andAI_RULEZ_LLM_BACKENDare gone (an OpenAI-compatible gateway is abase_url). Build from source withtask setup:llmonce.- Stricter names and values for content, the CLI and the MCP tools. A name must not end in
.md, contain whitespace or start with.or-;--targetsrejects unknown words;removechecks the item exists;domain removerefuses while a profile lists the domain;add skillwithout a description scaffolds one that passesvalidate. - Locks.
[lock] enforcedefaults to true wheneverai-rulez.lockexists; every pin uses one hashing scheme (sha256, algorithm-prefixed Merkle tree) and scripts are hashed byte for byte; a lock without content pins fails--check; a lock that pins role outputs is writtenversion = 2. Locks written by earlier v5 builds read as stale once — runai-rulez lock. - Remote sources. Plain
http://andgit://are rejected (usehttps://,ssh:///git@host:pathor a local path); the git token (AI_RULEZ_GIT_TOKEN,--token) is sent only togithub.comor the hosts inAI_RULEZ_GIT_TOKEN_HOSTS, as a host-scoped header rather than inside the URL. - A committed config cannot reach outside the project: a local include, skill-source path, custom preset
path, provider spec path,okf.dirormarketplace.output_dirthat escapes it (or names.git/.ai-rulez) is refused, and afile://include or skill source must name a repository inside the project (override withconfig.local.toml, the user config orAI_RULEZ_ALLOW_FILE_URLS=1). Content symlinks in the project's own.ai-rulez/are followed only when the target is inside the project;generaterefuses symlinks that leave it or point into.gitor credential files and stops onAR001secrets. - Security scanning.
[lint.security] scan_importsis on by default (generatescans imported content and stops at error-level findings;AR012is an error under[lock] enforce);ai-rulez scanreports only thesecurityanalyzer;lockandupdatescan what they pin and refuse error findings unless--accept-findings; staged[[lint.external]]scanners run confined by default (isolation = "auto", no network unlessegress = true). init --fromruns throughconvert --writewith the same scan, validation and lossiness report; an existing config directory is moved aside and restored if the import fails.- Preset changes.
windsurfis renameddevin(the preset, its.windsurf/output directory and thewindsurf_modelfrontmatter key; there is no alias — rename inconfig.tomland agent files and delete the old outputs), and thecontinue-devpreset is removed with no replacement (doctorreports both).[[plugins]]no longer writes.claude/plugins.jsonor.codex/plugins.json(no tool reads them;generatewarns). - Claude Code MCP servers are written only to
.mcp.json, so resolved MCP env values do not land in a committed settings file; an entry an earlier version wrote into.claude/settings.jsonis removed on the nextgenerate/clean. - User-scope and signed data.
[telemetry] service_nameis user scope only (a repository value is ignored and reported asAR9K1). Eval results are signed per user (eval-results.key): an unsigned or edited record isunverified, re-run and ignored, and CI must useeval run --force. The usage log is version 3 (digest,digest_scheme,event_id, saltedsession), andschema/catalog.schema.jsondescribes catalog version 2. - MCP tools are stricter. Arguments are validated against typed schemas (
additionalProperties: false), so an unknown argument, wrong type, out-of-enum or missing required argument is an error result instead of being coerced;validate_configand other failures returnisError: trueinstead of a success document;working_directory,config_fileandconfig_dirare confined to the server's start directory (mcp --root <dir>to change it,mcp --allow-any-dirto lift it); the authoring server no longer advertisestools.listChanged;roles resolve --format jsonspells the keyhonored. [mcp] self_serverdefaults totrue:generateadds the ai-rulez MCP server to.mcp.json(and each harness's own MCP file); opt out with[mcp] self_server = falseorgenerate --no-self-mcp.improve prconfines what it runs (scrubbed git environment, no hooks, only the push keeps transport credentials;--isolationcuts the network for the worktreegenerate/lockand--allow-networkleaves it on), and the scheduled repair workflow template pins its actions to a commit SHA.
Added¶
- OKF (Open Knowledge Format, spec v0.2).
export okf,import okfandokf validateread and write deterministic bundles,[[includes]] format = "okf"uses one as a source, the opt-inokfpreset keeps a bundle in sync withgenerate, and the.ai-rulez/tree itself can be an OKF bundle (migrate okf). Seedocs/okf.md. - llms.txt via the
llms-txtpreset (llms.txtand optionalllms-full.txt), linted byvalidate --strict. Seedocs/llms-txt.md. - Agent Plugins (agent-plugins.org) packages.
generate --pluginwrites a portableplugin.json,skills/andmcp.jsonconformant to the published 1.0.0 specification (or the 1.1.0 draft via[plugin] spec), and thecopilotruntime and the Codex root manifest write the same package.validate --strictloads it as a conformant client would (AR9O0-AR9O5),verify --plugincompares it against its sources,publish --emit agent-pluginsemits a clean directory andconvert --from agent-pluginsimports one. Seedocs/agent-plugins.md. - Agentic Resource Discovery (ARD) manifest (
[ard],publish emit ard) and Pi packages ([plugin] runtimes = ["pi"]). Seedocs/ard.md,docs/plugins.md. - New MCP tools for what the CLI already did (
scan_content,token_report,cost_report,sbom,okf_validate,approvals_status,policy_show,list_verifiers,list_builtins, and agent/command CRUD);generate_outputstakesprofile/role/check/offline; the authoring server gains prompts (author-skill,add-rule,review-config,trim-context), resources (ai-rulez://config,ai-rulez://catalog, item URIs) and cancellation. Approving, signing, locking, evaluating and publishing stay CLI-only. - Shell completion of enum values and project names (rules, context, skills, agents, commands, checks, domains, profiles, roles, includes), and a generated command reference (
docs/cli-reference.md) with examples for every command. - Dynamic skills and roles.
delivery: static|served|bothletsmcp --serve-skillsserve skills on demand withfind_skill,load_skillandlist_skill_resources;[[roles]]maps a job to a slice of the content withgenerate --role,roles list|show|resolveand a generatedroles.json; skills can be served from a git repository or directory via[[skill_sources]], pinned in the lock. Seedocs/mcp-server.md,docs/roles.md. - Content lock.
ai-rulez.lockpins the version, every authored item, the generated outputs, remote sources and served skills, withlock --check,lock --diff,lock --content-only,lock --subject,--locked/--frozenand version-constraint pins moved byai-rulez update. Seedocs/lockfile.md. checkscontent kind: code-review guidelines in.ai-rulez/checks/rendered for the review tools that read a repository file, with a marker block that keeps your own text. Seedocs/checks.md.generate --watchlive regeneration, andgenerate --emit-plan FILEfor a deterministic plan of every file.ai-rulez doctor: read-only diagnostics (removed presets, drift, unresolved MCP placeholders, lock drift, missing tools), exit 2 on errors.- Hooks and permissions across harnesses. Top-level
[[hooks]]and[permissions]are translated into each harness's native format and merged into your settings files key by key, so hand-written entries survive; matchers are translated by tool name; plugin-based harnesses (OpenCode, Kilo, MiMo Code, Pi, Amp) get a generated hook module. Seedocs/settings.md,docs/permissions.md. - User-level output.
generate --userrenders a user config into each preset's per-user locations andclean --userremoves exactly that. Seedocs/user-scope.md. validate --strict: deep content validation with stableARcodes, report formats (text|json|sarif|github|junit|markdown), baselines, budgets/ratchet,--fix,--since/--changed,--analyzer, a risk score and--explain. Seedocs/strict-validation.md.- Security checks
AR001-AR011in strict validation and the dedicatedai-rulez scancommand (secrets, hidden characters, prompt injection, credential access, unrestricted tools, an outbound host allow-list and merged[[lint.external]]results), plus harness trap lint. Seedocs/harness-traps.md. - Reports and analysis:
tokens(prompt-token cost by load bucket, including the item listing),cost(context-cost offenders),catalog(--format jsonand a self-contained--htmlsite),sbom(CycloneDX 1.6 and SPDX 2.3) andsearch(lexical plus an optional hybrid embeddings index). Seedocs/sbom.md,docs/search.md. - Governance.
approverecords reviewer approvals in the lock bound to the content digest, with[governance]policy (approvers,min_approvers,CODEOWNERS, expiry, sign,approve --from-github-review).signwrites Sigstore bundles (key, KMS or keyless) for the lock, bundles, skills, SBOM and policy, andverify --attestationchecks them against[signing];verify --selfverifies the running binary against its release provenance. An organization policy from outside the repository (--policy, the managed path,--discover-org) bounds sources, lint, locking, governance, signing, scanners, MCP, hooks and network use, andvalidate --show-policyprints the effective policy.publishpreflights, builds a reproducible archive and releases to GitHub, npm or OCI with--execute --yes. Seedocs/signing.md,docs/policy.md,docs/publish.md. reviewscores items against a rubric (optional LLM judge with--semantic, calibration and a gate), andimprove(experimental) runs an external optimizer behind a held-out eval gate. Seedocs/review.md,docs/improve.md.eval runexecutes harness-neutral skill eval cases (pass rate, trigger precision/recall, ablation, token cost) with adapters and a result cache. Seedocs/evals.md.- Telemetry (opt-in, identifier-only):
telemetry hook|record|flush|doctorand an OTLP/HTTP export through a bounded offline outbox, plusreport usagesections for rules, agents and context. Seedocs/telemetry.md. convertreads existing tool files into an equivalent.ai-rulez/tree with a lossiness report (native, rulesync, skills-lock, APM, Tessl and OKF importers), andinit --fromuses it.pkg/airulez(experimental): load, validate, plan and apply a project from Go with no working directory, environment, clock or subprocess. Seedocs/embedding.md.- Preset and plugin outputs: the
bazpreset; native MCP, command and user-scope outputs for many existing presets; per-domain plugins and marketplaces ([marketplace]);[placement]to keep skills and commands plugin-only;.codex/config.tomlmerged as a document. Seedocs/plugins.md.
Changed¶
publish --to npmpreserves the runtime metadata (Pi resources, OpenCode entry points, dependencies, keywords, license) of the package it publishes while owning the name, version,fileslist andpublishConfig.validate_configandscan_contentshow project-relative paths and carry the whole lint document.generatevalidatesconfig.tomlagainst the schema and warns about unknown keys (--strict-configorAI_RULEZ_STRICT=1fails), and prints a summary of new or changed commands (hook commands, MCP servers, managed env, plugin enablement, permissions) even with--quiet(--yesorAI_RULEZ_ACK_COMMANDS=1silences it).cleankeeps a generated file whose body was edited by hand and warns;generate --dry-runmarks current filesunchanged:and edited filesedited:.- The lock and
generate --checkreturn exit codes per run, so3works with--recursive; a served skill the security scan refuses is reported and left unpinned (lockexits 3) unlesslock --refuse-findings. tokenscounts the item listing for harnesses that list skills, commands or agents, so--budgetgates the number the prompt really carries.liter-llmis pinned at v2.2.3, and the LLM response cache is version 3.- Several presets write their native MCP, command and agent shapes more accurately (for example
opencodeusesmcp.<name>withenabled,codexwrites skills to.agents/skills, the root.mcp.jsonfollows themcppreset shape, andantigravitywrites commands as skills).
Fixed¶
migrate v5drops the rejected 4.x[[skills]]table, keeps CRLF inconfig.tomland preserves comments;migrate okfwrites atomically with a backup and refuses to destroy a rule namedindexorlog.validateon an OKF bundle reports the OKF verdict before "Configuration is valid" and writes the OKF document under--format json;-qno longer prints the OKF summary or strict totals.generate --checkno longer reports a filegeneratejust wrote asedited.- Content is never read, written or deleted through a symlink inside the configuration directory;
add,edit,show,removeand the MCP CRUD tools refuse symlinked domains, directories and files. editand the MCP update tools keep an item's priority, targets, description and other frontmatter when only the content changes.- No log line or error prints a credentialed include or skill source;
include addandskill installwarn and redact. export okf --output-dircan no longer delete your configuration, andimport okfstrips group/world-write bits and redacts credentials; OKF export, import, export is byte-identical.- Locking fails closed: an enforced lock fails when an include cannot be resolved, file modes in digests are OS-independent, and
validate --strictreportsAR981instead of skipping an unverifiable lock. - Running inside a git hook no longer corrupts the repository (every git subprocess drops
GIT_DIRand the other inherited git variables). [llm]trust and safety:allow_network,base_url,api_key_envand price overrides are honoured only from user scope, a pasted key is never echoed, the budget cannot be overshot by concurrent calls, andtimeout_seconds/max_cost_usdreject unusable values.- The
mcp --serve-skillsserver bounds served-skill metadata, applies the session budget toget_skill/read_skill_file/resources/read, handles skill-source name collisions, and fetches a locked commit by SHA even after a tag moves. - Hook merges read commented JSONC/TOML/YAML documents, and presets that share one document union their owned keys instead of conflicting.
Removed¶
- The hidden
mcp --transport,--addressand--portflags (the server speaks stdio only). ai-rulez migrate,init --format, legacymcp.yaml/mcp.toml/mcp.jsonloading and the V2/V3 config readers.- The
compressionoption (a no-op since v3.13) and the legacyinit --fromimporter engine. - The process-wide preset registry and other legacy Go APIs;
pkg/airulezis the supported embedding API.
Security¶
- MCP
--rootconfinement runs on the symlink-resolved path it judged, so a link swapped between the check and the tool's use cannot redirect a call outside the root. - The
run_verifiersMCP tool never starts a program (command predicates are refused even withAI_RULEZ_VERIFIERS_ALLOW_EXECset). - The committed generated manifest no longer licenses anything: a forged claim cannot strip permissions, env or allow keys from your settings, and the local manifest stores array elements as digests rather than values.
ai-rulez.lockno longer pins resolved secrets or the checkout path, and lock writes never follow a committed or linked symlink.- Output paths cannot escape the project: a custom preset, provider spec,
okf.dirormarketplace.output_dircan no longer makegeneratewrite outside the project or into.git, and symlinked output directories are not followed. - The git token never leaves allowlisted hosts and never appears in process arguments or a cached clone's
.git/config; include and skill caches are0700and keyed by name plus a URL hash. - Imported content is scanned: symlinks in includes, installed skills and OKF bundles are skipped, files are size-capped, a local
[[skill_sources]]path must resolve inside the project, and credentials in a source URL are refused (AR035). - Machine-local files are never written or read through a symlink, and the LLM cache secret must be a regular
0600file. - Hook
scriptpaths are validated and shell-quoted, permission translations do not widen or bypass rules, and hook plugin matchers that Go and JavaScript read differently are skipped with a warning.
[4.24.2] - 2026-10-04¶
Fixed¶
- Internal/test-only fixes; no user-visible change.
[4.24.1] - 2026-10-04¶
Fixed¶
- Internal/test-only fixes; no user-visible change.
[4.24.0] - 2026-10-04¶
Added¶
pipreset for the pi coding agent: a sharedAGENTS.md, skills in.agents/skills, agents in.pi/agents, and MCP servers in.pi/mcp.json.
Changed¶
AGENTS.mdis now shared bypialongsidecodex,opencode,xumandamp.
[4.23.1] - 2026-10-03¶
Added¶
- Xum stdio MCP
envis written as a shell assignment prefix. generatewarns once about a v1-shaped OpenCode plugin (OpenCode v2 does not run it).
Changed¶
- MCP servers in shared settings documents are owned one by one, so a server you wrote by hand survives
generateandclean. - A plugin
namemust match^[a-z0-9][a-z0-9._-]*$and contain no..for every runtime.
Fixed¶
- Local content reaches each tool through a file it loads (
GEMINI.local.md,AGENTS.local.md,AGENTS.override.md,.junie/rules/ai-rulez.local.md, ...); the old non-working names are removed on upgrade. cleanleaves no ai-rulez keys behind in hand-authored settings documents.
[4.23.0] - 2026-10-03¶
Added¶
config.local.*overlay andai-rulez localfor machine-local configuration, plus local skills, agents, commands and domains under.ai-rulez/local/.agents_md: renders one sharedAGENTS.mdand.agents/skillstree for many presets.- A shared baseline and drift guard: a teammate-view run never deletes your local outputs, and drift on a tracked shared file stops generation unless
--allow-local-drift;--no-localrenders the teammate view.
Changed¶
.gitignoremanagement adds only what git does not already ignore.
Fixed¶
- Gemini agents are written to
.gemini/agents/, the location Gemini CLI loads. - Xum disabled stdio MCP servers are written as disabled.
Security¶
- Generated files that contain resolved MCP secrets are written
0600. - Include and skill sources are logged with URL credentials redacted.
[4.22.2] - 2026-10-03¶
Fixed¶
- Generator schema v8 forces a one-time rewrite of existing generated files.
- Cursor agents are written to
.cursor/agents/. - OpenCode agent model, variant,
hidden,temperatureandtop_pare written in the shapes OpenCode accepts (an unqualified model is omitted with a warning).
[4.22.1] - 2026-10-03¶
Fixed¶
- Cursor
globsis written as a bare comma list. - Negated globs (
!x) are dropped from every dialect with a warning. AGENTS.mdis identical acrosscodex,opencode,xumandamp.- Rules no longer vanish when a rule file name is taken (written as
<id>.ai-rulez.md), and names that map to the same file id no longer abort generation (later ones get a stable suffix).
[4.22.0] - 2026-10-02¶
Added¶
- Rule
activationfrontmatter (always,glob,auto,manual), plus[rules] modeandmode_by_preset. - Copilot path-specific instructions (
.github/instructions/*.instructions.md). - Local rules written as native rule files.
Changed¶
- BREAKING: rules are split into native rules folders by default (
[rules] mode = "split"): Claude writes.claude/rules, Copilot.github/instructions, Junie.junie/rulesand Antigravity.agents/rules. Set[rules] mode = "inline"(ormode_by_preset) for the old layout. - Frontmatter
targetsnow restrict rule outputs, and scoped rule files go to the root rules folder with the scope path as a prefix.
Fixed¶
- Include and skill-source URLs no longer leak credentials into logs or errors.
- Cline, Continue and Windsurf rules honour
paths/globs; Cursor context rules are applied automatically. - Generated rule files are gitignored per file, and a hand-written rule file is never overwritten.
[4.21.0] - 2026-10-02¶
Added¶
headerson[[mcp_servers]]: remote servers can send HTTP headers (Authorization = "Bearer ${TOKEN}").[header] hashes(full,content,none) chooses the freshness lines;contentstops one edit from rewriting every generated file.
Fixed¶
- The secret guard now also covers
opencode.json.
[4.20.1] - 2026-10-02¶
Fixed¶
- CRUD commands and the MCP CRUD tools work in a project using the
.config/ai-rulez/layout.
[4.20.0] - 2026-10-02¶
Added¶
[mcp] self_server:generatecan add ai-rulez's own MCP server to the project.mcp.json.validate --recursive(-r).
Fixed¶
generate --recursiveexits non-zero when any root fails.cleanno longer removes a merged settings document that holds content ai-rulez did not write.validate --quietis honored.
[4.19.0] - 2026-10-02¶
Added¶
- Configuration discovery also accepts
.config/ai-rulez/(the.configproposal) as a fallback;.ai-rulez/still wins when both exist.
Changed¶
- Generated headers name the real config directory.
[4.18.0] - 2026-10-01¶
Added¶
[header] textreplaces the generated banner with your own multi-line text while keeping the freshness lines.
Fixed¶
- Rules and context render in priority order again (critical to minimal).
- Regeneration is forced once after the ordering change.
- A generated
opencode.jsonstays a managed artifact (it owns$schema).
[4.17.0] - 2026-09-30¶
Added¶
- Path-scoped rules via
globs/pathsfrontmatter (Claude.claude/rules/<id>.md, Cursor.mdcwithalwaysApply: false). - Cursor rule frontmatter (
alwaysApply/globs/description). - Profile-scoped
[[mcp_servers]]and[[installed_skills]](profileslist). defaults.omit_agent_fields.
Fixed¶
- Generated agents are spawnable as subagents (OpenCode
mode: all, Xumsubagent.runnable). - Scoped outputs no longer repeat the root content.
[4.16.0] - 2026-09-30¶
Added¶
${PROJECT_ROOT}can be used in an MCP server'scommandorargs, resolved at generation time.
Fixed¶
validatechecks the raw config against the JSON schema (unknown keys and bad enums are reported).include add --merge-strategyaccepts the values the resolver uses.- The
popularpseudo-preset emits the curated provider set, and documentation was corrected.
[4.15.0] - 2026-09-30¶
Added¶
- Profile-scoped builtin domains (
builtin:<name>, e.g.builtin:dockerloads a pack for one profile only). - OpenCode v2 preset output (
opencode.jsonwithmcp.servers) and an OpenCode v2 plugin adapter.
Changed¶
- Merged JSON documents can own a nested key path (for example
mcp.servers).
[4.14.1] - 2026-09-29¶
Fixed¶
- Internal/test-only fixes; no runtime behavior changed.
[4.14.0] - 2026-09-29¶
Added¶
xumpreset for the Xum coding agent (AGENTS.md,.xum/skills,.xum/agents,.xum/mcp.jsonc).- Provider-backed custom presets: a
[[presets]]entry may reference a declarative provider spec instead of a template.
Fixed¶
- CRUD commands no longer write a spurious
config.yamlinto a TOML project (a mutation loses the comments of a hand-commentedconfig.toml).
[4.13.0] - 2026-09-27¶
Added¶
ai-rulez tokensreports the prompt-token cost of the configuration by load bucket (always,conditional,on demand,unmodeled), with--budget,--compare-profilesand a--tokenizer.- Composed profiles:
--profile base,backend(ordefault = "base,backend") resolves to the de-duplicated union of their domains.
Changed¶
- The
Generated:header line is now off by default (output is byte-reproducible);[header] timestamp = trueopts in. - Builtin rules are roughly halved, with narrow guidance (code-quality, testing, token-efficiency, docker, observability) moved into skills that load on demand.
Fixed¶
- Skills and commands now honour source precedence (root > domain > include > builtin); the previous inversion silently dropped root and domain items.
generateremoves the directories its own stale-file pass emptied.
[4.12.1] - 2026-09-25¶
Fixed¶
generateno longer deletes H1-like lines from inside fenced code blocks.- A merged settings document keeps its indentation and CRLF line endings.
Changed¶
- Removed the unused
internal/scannerpackage.
[4.12.0] - 2026-09-25¶
Added¶
- Bundled hook scripts for plugins (
scriptfield) plus the hook actionargs,timeout,ifandstatus_message. - Directory form for commands (
commands/<name>/COMMAND.mdwithreferences/).
Changed¶
- Settings documents are merged, not overwritten (
.claude/settings.json,.mcp.json,.gemini/settings.json,.agents/settings.json,.amp/settings.json): ai-rulez owns specific keys and preserves the rest. .gitignorelists the subdirectories ai-rulez writes (.claude/skills/) rather than whole assistant directories.init --setup-hookspreserves comments, key order and indentation.
Fixed¶
- Non-canonical skill/command subdirectories warn;
validatereports skill/command output-id collisions; a plugin passthrough source outside the project (including through a symlink) is refused.
[4.11.5] - 2026-09-19¶
Fixed¶
- Skill includes pinned to a full commit SHA resolve deterministically and fail closed.
- Schema validation compiles with a fresh compiler per call.
Changed¶
- Go toolchain to 1.27 and dependency upgrades.
[4.11.4] - 2026-09-18¶
Fixed¶
- The managed
.gitignoreblock is spliced back in place instead of being reordered. validatefails on malformed content frontmatter, and a skill with malformed frontmatter is no longer dropped from output.- Includes pinned to a full commit SHA fail closed instead of falling back to stale cache.
[4.11.3] - 2026-08-24¶
Added¶
[header] timestamp = falseomits theGenerated:line.
Fixed¶
generateis reproducible across checkout paths.
[4.11.2] - 2026-08-08¶
Fixed¶
ai-rulez mcpno longer wedges when a host sendsinitializetwice on one stdio session.
[4.11.1] - 2026-07-31¶
Fixed¶
generateno longer inlines the full rules block into every generated skill and agent file.- A skill whose source frontmatter fails to parse no longer emits a second raw frontmatter block.
[4.11.0] - 2026-07-22¶
Added¶
ai-rulez cleanremoves the files produced bygenerate(the inverse ofgenerate), with--dry-run,--force,--keep-gitignoreand--keep-manifest, exposed as theclean_outputsMCP tool.
Fixed¶
- MCP
update_rule/update_context/update_skillno longer fail withfile already exists.
[4.10.0] - 2026-07-22¶
Added¶
- Local-override content under
.ai-rulez/local/(rules and context), emitted only to per-preset.localfiles and gitignored, with--localonadd rule/add context. - Bare/flattened include layout: included repositories no longer need an
.ai-rulez/wrapper.
Changed¶
- Builtin language, binding, OWASP and dependency conventions now emit as on-demand skills instead of always-inlined rules, shrinking the generated root files.
- The default header style is now
minimal.
[4.9.4] - 2026-07-12¶
Changed¶
- Builtin language and binding rules are tool-agnostic (opinionated tools are framed as examples).
Fixed¶
- The Python builtin no longer references a TypeScript type, and vite+ typos are fixed.
[4.9.3] - 2026-07-12¶
Fixed¶
- Windows drive-relative plugin paths (
C:outside) are rejected on every platform.
Changed¶
- Poly catalog npx/uvx execution paths are pinned for reproducible hooks.
[4.9.2] - 2026-07-12¶
Fixed¶
- Plugin path validation is consistent across operating systems.
[4.9.1] - 2026-07-12¶
Fixed¶
- Poly hook installation paths use the
versionsubcommand.
[4.9.0] - 2026-07-12¶
Added¶
- Reusable
poly-hooks.tomlcatalog with selectable hooks and guarded execution paths. - Recursive plugin generation and verification (
--if-configured). - Plugin generation and verification hooks for Poly and pre-commit.
[4.8.0] - 2026-07-12¶
Added¶
- Native Hermes Agent generation (
hermespreset,.hermes.md, project plugins and PyPI entry points). plugin.content_root,plugin.hermes.sourceandplugin.hermes.requires_python.ai-rulez verify --pluginfor plugin freshness and provenance.
Fixed¶
- Authored Markdown whitespace is preserved around generated headers.
[4.7.0] - 2026-07-11¶
Added¶
- Complete Codex plugin bundles (root MCP config, marketplace metadata, recursive skill resources).
- OpenCode plugin generation from
.ai-rulez/opencode/index.js. - Deterministic plugin provenance (generated-file headers and
.ai-rulez-generated.jsonsidecars).
[4.6.0] - 2026-07-08¶
Added¶
- Plugin and marketplace authoring via
ai-rulez generate --plugin(Claude, Cursor, Codex, Gemini, Kimi, OpenCode, Factory), with a[plugin]block, hooks,${PLUGIN_ROOT}, and single-plugin or monorepo marketplaces. - Agent
extendsdirective: an agent inherits a lower-precedence agent's body and frontmatter and appends its own.
Changed¶
- Agent precedence is deterministic (same-named agents collapse to the highest-precedence definition).
[4.5.0] - 2026-07-02¶
Added¶
- Rule and context deduplication by name with source precedence (root > domain > include > builtin).
compactoption to omit per-rule priority annotations, and per-rule builtin exclusion (!domain/rule).- MCP tool annotations and server title/instructions.
Changed¶
- Remote MCP servers are emitted in each tool's own format (no empty
command/transport); stdio is unchanged. - Preset rendering moved to a declarative provider DSL, and lint/format moved to poly.
[4.4.1] - 2026-06-07¶
Fixed¶
.ai-rulez/.generated-manifest.jsonis gitignored when gitignore management is enabled.
[4.4.0] - 2026-06-07¶
Added¶
- Per-preset model overrides for agents (
<preset>_model, e.g.claude_model) withdefaults.model_by_presetas the fallback; the legacymodel:still works.
Fixed¶
- The TOML loader no longer drops the
[defaults]table.
[4.3.0] - 2026-05-30¶
Added¶
generate --gitignore(with-i); the old--update-gitignoreremains a deprecated alias.- Collision-safe short flags across commands.
- MCP environment placeholder resolution from
--env, the process environment,.envand--env-file.
Fixed¶
- Secret-bearing MCP outputs fail generation when their path is not gitignored, and resolved secrets are redacted before source-hash calculation.
[4.2.2] - 2026-05-26¶
Added¶
polyglot-bindingsbuiltin (Rust-core, native ABI, FFI ownership, cross-language errors).
Changed¶
- The TypeScript builtin recommends
oxfmtwithoxlint.
[4.2.1] - 2026-05-21¶
Fixed¶
- Cursor/Codex skill descriptions are quoted, so descriptions containing colons load.
[4.2.0] - 2026-05-21¶
Added¶
- Manifest-scoped generation cleanup:
generatedeletes only files recorded in.generated-manifest.json. - Real dry-run output (
create-dir,write-file,delete-stale) without touching the filesystem. - Custom config directory (
--config-dir), exact config-path loading, and[[scopes]]subfolder outputs.
Fixed¶
- Hand-written files in assistant output directories are preserved during regeneration.
[4.1.6] - 2026-05-03¶
Changed¶
- Git fetches use sparse checkout (
--depth 1 --filter=blob:none --sparse), fixing "response body too large" and cutting network/disk use; requires git ≥ 2.25. - BLAKE3-based cache invalidation replaces the time-based TTL.
[4.1.5] - 2026-05-02¶
Changed¶
- Skill resources are no longer concatenated into
SKILL.md:references/,scripts/andassets/are emitted as separate files, andSKILL.mdcarries a## Resourcesindex (progressive disclosure).
Security¶
- A malicious installed skill cannot follow a symlink out of its directory.
[4.1.4] - 2026-05-01¶
Fixed¶
- Internal test-client rewrite; no user-visible change.
[4.1.3] - 2026-04-30¶
Fixed¶
generate --recursiveon a polyglot monorepo no longer takes minutes or aborts on a broken symlink: it prunes build and cache directories, skips shared rule libraries, processes configs in parallel and memoizes tree scans (minutes to about a second, steady state).
[4.1.2] - 2026-04-30¶
Added¶
- Per-subagent reasoning effort for Codex (
model_reasoning_effort) and OpenCode (reasoningEffort).
[4.1.1] - 2026-04-30¶
Added¶
- Reasoning effort extended to Codex, Amp and Windsurf, with
defaults.effort_by_presetand MCPdefault_effort_by_preset.
[4.1.0] - 2026-04-29¶
Added¶
- Reasoning effort on Claude Code subagents (
effortfrontmatter: low/medium/high/xhigh/max/inherit). defaults.effortproject default and MCPdefault_effort.
[4.0.8] - 2026-04-27¶
Added¶
Source-Hashheader line and hash injection for YAML-frontmatter files (hashes as YAML comments).
Changed¶
- Output is sorted alphabetically by name and normalized to a single trailing newline (one round of reordered output).
Fixed¶
- Generation is deterministic (domain iteration sorted); the
toolslist is no longer corrupted into a Go slice string; themcppreset validates; skip-on-content-hash fires for frontmatter+banner files.
[4.0.7] - 2026-04-27¶
Fixed¶
--debugand--update-gitignorenow take effect..gitignoreno longer duplicates a pattern already present outside the managed fence.
Changed¶
- Intentional-behavior warnings and verbose generation logs demoted to debug.
[4.0.6] - 2026-04-25¶
Fixed¶
- The MCP schema accepts V4 configs, and generated headers reflect the actual config filename.
[4.0.5] - 2026-04-25¶
Fixed¶
- Config discovery finds
config.toml/config.json, and recursive generation no longer skips them.
[4.0.4] - 2026-04-25¶
Fixed¶
- Pre-commit hooks fire on
.ai-rulez/changes, stale versions in scripts/wrappers are updated, and init templates default to config version 4.0.
[4.0.3] - 2026-04-24¶
Fixed¶
- Gitignore uses subdirectory patterns for shared directories (for example
.github/agents/).
[4.0.2] - 2026-04-24¶
Added¶
- Builtin agents (code-reviewer, test-writer, security-auditor, docs-writer, devops-engineer, release-engineer) and new rules (verification-before-completion, systematic-debugging, testing-anti-patterns).
Changed¶
- README redesigned around builtin capabilities and the development workflow.
[4.0.1] - 2026-04-24¶
Added¶
- New builtins
cicd,docker,observability; new rulesno-ai-signatures,agent-workflow,communication-style,tdd-workflow,anti-patterns; more domains auto-included.
Fixed¶
migrate v4preserves MCP servers.
[4.0.0] - 2026-04-23¶
Changed¶
- BREAKING: TOML is the default config format (
initwritesconfig.toml); existing YAML configs still work. - BREAKING: MCP servers are inline under
[[mcp_servers]](legacymcp.yamlstill loads with a deprecation warning). - BREAKING: V2 compatibility is removed (config types, migration, validator, generators), along with the deprecated
CompressionConfig,--skip-mcp,--auto-migrateandmigrate v3.
Added¶
- Agent generation for all presets, context rendering for per-file presets, Claude MCP/plugins output, Cursor/Copilot MCP, Codex plugins/commands, Copilot commands, TOML config support,
[[plugins]]/[[marketplaces]]andmigrate v4.
Removed¶
- V2 types, loader, migration, validators and generators; V1/V2 schemas; the V2 template engine; MkDocs config and the generated
site/.
[3.14.2] - 2026-04-20¶
Fixed¶
- Critical:
generateno longer deletes non-generated files in.github/(workflows, CODEOWNERS, issue templates).
[3.14.1] - 2026-04-20¶
Fixed¶
- Added the CI/Taskfile tasks the workflows referenced, and fixed Windows path-separator tests.
[3.14.0] - 2026-04-20¶
Added¶
antigravitypreset (GEMINI.md,.agents/settings.json, skills/agents under.agents/).- Skills and agents for Gemini, Cursor, Codex, Amp, Windsurf, Copilot, Cline, Junie and OpenCode; Cursor skills moved to
.agents/skills. vite-plusbuiltin; MCP read tools;working_directory; enum constraints;read_config/update_config;dry_run/recursive; tool annotations;builtins show.- Content hash in generated headers (files skipped when unchanged) and an include cache TTL with
--no-fetch. - Fenced, idempotent
.gitignoreupdates.
[3.13.1] - 2026-04-19¶
Fixed¶
- Frontmatter parsing falls back to raw map parsing; a missing skill description is a warning; cache locations are unified under
~/.cache/ai-rulez/.
[3.13.0] - 2026-04-19¶
Added¶
agent-delegationbuiltin (an## Agentsroster in generated output, disable with!agent-delegation).- New binding builtins (
jni-rs,extendr,cgo) and three token-efficiency rules.
Changed¶
compressionis a no-op and deprecated (its stopword removal made output ungrammatical); the compression package was removed.
[3.12.0] - 2026-04-17¶
Added¶
- Installed skills:
ai-rulez skill install/remove/list, fetched at generate time (installed_skillsconfig, with MCP tools). - A distributable
ai-rulezskill anddocs/llms.txt.
Fixed¶
- YAML config saving preserves ordering, comments and formatting, and include/skill caches are cleared before each fetch.
[3.11.5] - 2026-04-15¶
Fixed¶
- The Claude preset emits no header comments in skill, agent or command files.
[3.11.4] - 2026-04-15¶
Added¶
local_overridefor includes (use a local directory instead of a git fetch), and minimal headers for skills/agents.
Fixed¶
- Claude frontmatter is emitted as the first line; skills carry
user_invocableanddescription; included domains are always available; output is deterministic.
[3.11.3] - 2026-03-29¶
Fixed¶
- Includes fail fast when the resolver is not registered.
[3.11.2] - 2026-03-25¶
Fixed¶
.github/is no longer ignored as a directory, and files inside fully-managed directories are not listed individually.
[3.11.1] - 2026-03-25¶
Added¶
- R language builtin (tidyverse, testthat, CRAN, extendr).
[3.11.0] - 2026-03-25¶
Fixed¶
- Include domains are no longer duplicated, Claude output shrinks from ~13 MB to ~440 KB, and stale files are cleaned before writing.
Changed¶
- Local rules in
CLAUDE.mduse@pathlazy-loading references.
[3.10.0] - 2026-03-18¶
Fixed¶
- Included skills expose their frontmatter, and included domains are discovered and usable in profiles.
[3.9.0] - 2026-03-11¶
Added¶
- Root command docs (
build,fix,lint,review,test) and a repository-managed pre-commit config.
Changed¶
- Replaced lefthook with the prek/pre-commit toolchain.
Fixed¶
- Codex skills always carry a
description; V3 validation rejects a missing description; E2E test binary setup is stable.
[3.8.3] - 2026-03-08¶
Fixed¶
- Domain content from includes is no longer duplicated into root slices.
[3.8.2] - 2026-03-08¶
Fixed¶
- The Claude preset collects domain skills, agents and commands, and the
default-commandsbuiltin generates correctly.
Changed¶
- Language builtins expanded with linting, SAST, coverage and benchmark tooling.
[3.8.1] - 2026-03-07¶
Changed¶
- Replaced simple compression with a token-reduction engine (levels
off/light/moderate/aggressive/maximum, markdown-aware).
Fixed¶
- Flat ai-rulez structures are detected at sub-paths.
[3.8.0] - 2026-03-07¶
Added¶
- Built-in domains: 23 embedded content domains shipped with the binary.
builtinsconfig field (true/false/list/!name) andai-rulez builtins list./iterateand/parallelizeslash commands.
[3.7.3] - 2026-02-19¶
Fixed¶
- The publish workflow resolves Go from
go.mod.
[3.7.2] - 2026-02-16¶
Fixed¶
- Claude skill rendering respects frontmatter
targets, and the npm installer supports offline/private-registry bundled binaries.
[3.7.1] - 2026-02-16¶
Fixed¶
- Windsurf trigger frontmatter safely quotes
descriptionandglob.
[3.7.0] - 2026-02-16¶
Fixed¶
- Includes now carry agents, and Codex skills always carry a
description.
[3.6.1] - 2026-01-07¶
Fixed¶
- Windows releases use
.zipinstead of.tar.gz.
[3.6.0] - 2026-01-05¶
Changed¶
- Skills and commands are written to dedicated directories instead of inlined into root files across every preset (Claude's
CLAUDE.mddropped ~77%, from 14K to 3.2K lines), reducing prompt tokens.
[3.5.0] - 2026-01-04¶
Added¶
- V3-native command system: file-based slash commands in
.ai-rulez/commands/with YAML frontmatter, profile-aware and rendered per preset (migrate v3for V2 commands). - Configurable prompt compression and context-file optimization (a required
summary; context rendered as summaries with@links, ~34% smaller).
Changed¶
- Remote include cache moved to the system cache directory.
Fixed¶
- Includes merge commands from remote sources, and the default profile includes commands.
3.4.1 - 2026-01-03¶
Added¶
- SSH git clone support for private repositories, self-hosted GitLab, and repositories whose root is the ai-rulez structure.
3.4.0 - 2026-01-03¶
Added¶
- Configurable header styles (
detailed/compact/minimal),CLAUDE.mdgeneration, and markdown formatting with markdownlint compliance.
3.3.2 - 2026-01-02¶
Fixed¶
- SSH git URL conversion in includes, with multiple SSH URL formats accepted.
3.3.1 - 2025-12-31¶
Fixed¶
- SSH git URL detection (
git@host:pathwas treated as a local path).
3.3.0 - 2025-12-31¶
Fixed¶
- Complete agents support in the includes system.
3.2.2 - 2025-12-28¶
Added¶
initcreates root and domain agent directories, and generated MCP config includes the ai-rulez server.
3.2.1 - 2025-12-28¶
Fixed¶
- Gitignore uses relative paths and no longer ignores
.ai-rulez/.
3.2.0 - 2025-12-28¶
Added¶
- Full agent/subagent support in V3 (
.ai-rulez/agents/), agent metadata, and Claude Code subagent output. - Auto-migration of V2 configs (interactive in a terminal, silent in CI;
--auto-migrate).
3.1.0 - 2025-12-27¶
Added¶
migratecommand for V2 to V3 configuration migration.
3.0.0 - 2025-12-27¶
Added¶
- Directory-based configuration (
.ai-rulez/), CRUD CLI commands, 22 MCP tools, domains, profiles and includes.
Changed¶
- BREAKING: configuration changed from a single YAML file to a directory structure, and
initno longer uses AI agents for dynamic initialization.
Removed¶
- BREAKING: the
enforcecommand and V2 dynamic init are removed; single-file YAML support is dropped.
2.4.0 - 2025-10-22¶
Fixed¶
- CLI MCP interference with template-generated files, and output type values in AI-generated configs.
2.3.0 - 2025-10-05¶
Added¶
- AI-powered rule enforcement, automatic gitignore management, and the Junie preset.
2.2.0 - 2025-09-20¶
Added¶
- MCP file-based configuration for Claude and other tools.
2.1.0 - 2025-08-20¶
Added¶
- CLI MCP integration for Claude, and an improved init phase.
Fixed¶
- Cross-platform binary extensions, and the Homebrew formula structure.
2.0.0 - 2025-07-30¶
Changed¶
- BREAKING: schema v2 with a priority enum, unified
name(wastitle), and named target resolution.
1.6.0 - 2025-07-10¶
Added¶
- Target filtering and named targets.
1.5.0 - 2025-06-25¶
Added¶
- Initial release: core configuration generation, the rule definition system and a template-based generator.