v16.22.0
·
282 commits
to main
since this release
Immutable
release. Only release title and notes can be modified.
What's Changed
New Features
- Crush is a new tool target for rules, ignore files and skills.
--targets crushwrites the rootCRUSH.md(project) or~/.config/crush/CRUSH.md(global); Crush has no modular rules directory, so non-root rules fold into the root file the same way they do forzcodeandcodexcli. The ignore feature writes a project-only.crushignorewith gitignore-style patterns, and the skills feature discovers and generates standard Agent Skills under.crush/skills/(project) and~/.config/crush/skills/(global), includinguser-invocable,disable-model-invocationand thelicense/compatibility/metadatapackaging fields, which are normalized through the shared Agent Skills helpers. Every surface was verified against Crush's Go source (#2920, closes #2762). - CodeBuddy Code is a new rules target.
--targets codebuddywrites the rootCODEBUDDY.mdmemory file (also read from.codebuddy/CODEBUDDY.md, and~/.codebuddy/CODEBUDDY.mdin global scope), modular rules under.codebuddy/rules/*.mdwith optionaldescription/paths/alwaysApplyfrontmatter in the Cursor style, and the personalCODEBUDDY.local.mdlocal-root file that mirrorsCLAUDE.local.md. An Always Apply rule no longer carries a redundant universal glob. Only the rules feature ships in this release; other features will follow separately (#2918, closes #2760). - Factory Droid's
DESIGN.mddesign-guidelines channel is emitted. Factory Droid loadsDESIGN.mdas an always-on design-system / UX / visual-guidance file separate fromAGENTS.md. A non-root rule can now opt in with afactorydroid: { channel: design }frontmatter block, which routes its body toDESIGN.mdinstead ofAGENTS.md/.factory/rules/*.md; several opted-in rules concatenate in source order. The file round-trips throughimportandgenerate --delete, is excluded from the root file's reference list because Factory Droid loads it itself, is routed by directory rather than bare basename, and gets its.gitignoreentry derived automatically. Project scope only, since Factory documents no home-directory equivalent (#2916, closes #2749).
Bug Fixes
- AugmentCode shell permissions honor the all-tools
*category and fail closed..augment/settings.jsonwas generated from thebashcategory alone, so{"*": {"rm -rf *": "deny"}, "bash": {"rm *": "allow"}}emitted a barelaunch-processallow and an all-tools-only deny or ask produced nothing at all. The adapter now resolves shell rules through the shared all-tools-aware helper, so a*deny or ask withholds everybashallow it covers, an all-tools deny or ask is folded into every managed tool rather than only the ones with their own entries, the unsupported wildcardtoolName: "*"entry is skipped and legacy copies of it are removed on regenerate, and non-shell all-tools effects that cannot be represented are reported with a warning (#2919, closes #2917). - Command-capable adapters honor the all-tools
*category. Cursor, Codex CLI, Roo Code, Zed, OpenCode, Kiro and the other adapters that build shell command lists silently dropped*rules. They now route through new shared helpers so a*deny or ask correctly shadows the overlappingbashallows, an all-tools ask or deny is honored fully across every bash-derived adapter, and Roo Code warns about categories it cannot express instead of dropping them without a trace (#2905, closes #2831). - Hermes Agent's
command_allowlistis fed frombashallows only, and restrictions that cover them are honored. The allowlist was built from theallowpatterns of every canonical category, so{"read": {"secrets/**": "allow"}}auto-approved a shell pattern and{"*": {"*": "allow"}}auto-approved every command, while a*deny or ask, or abashask, never withheld the allows it covered. Onlybashallows now reach the allowlist, all-tools deny / ask rules withhold the allows they cover (compared as globs in both directions), an all-tools allow is skipped with a warning, restrictions Hermes cannot express are reported instead of dropped, andimporttreats the allowlist as authoritative forbashallows only so a withheld or non-command allow survives a generate → import round trip (#2903, closes #2829). - Warp and Goose no longer create an empty global permissions file. When no permission rule mapped and the global file did not exist,
generatecreated asettings.tomlholding only[agents.profiles]or apermission.yamlholding three empty lists. Both adapters now skip creation for an empty payload, mirroring the earlier deepagents fix; an existing file is still rewritten so stale lists are cleared and unrelated keys survive (#2898, closes #2894). - Frontmatter cleanup is bounded against alias bombs, circular anchors and prototype keys. The recursive frontmatter cleanup had no size bound and no cycle detection, so a small file with a few levels of nested YAML aliases expanded into tens of megabytes (and exhausted the heap), a self-referencing anchor overflowed the stack, and a
__proto__:block replaced the cleaned record's prototype, letting a hidden key such asallowed-toolssurface through the loose schemas. One shared walker now charges every visited value, mapping key, nullish leaf and string character against explicit budgets, caps nesting depth, refuses the document with a clear file-prefixed error when a budget is exceeded, drops references back to an ancestor, keeps a body reparse from reintroducing the problem, and skips__proto__/constructor/prototypekeys on both the parse and stringify paths. Ordinary frontmatter is unaffected (#2906, closes #2751). - Hook adapters resolve native event names with own-property lookups. Translating a native event name back to the canonical one used a plain-object bracket read, so a config whose event key is an
Object.prototypemember such astoStringresolved to an inherited function and the imported hook was filed under a stringified prototype member. A new own-property-onlylookupOwnhelper is used by the shared converter and by every per-adapter site (Copilot, Copilot CLI, Cursor, Deep Agents, Hermes Agent, Kimi Code, Kiro, Kiro IDE, Qwen Code, Reasonix, Vibe), on both the import and generate sides (#2904, closes #2757). - Undocumented AugmentCode hook keys survive a regenerate. The
hooksobject in.augment/settings.jsonwas replaced wholesale, erasing hand-authored keys such asafterFileEdit,beforeShellExecutionand a lowercasestopthat Auggie recognizes but rulesync does not model. Keys that are not rulesync-owned native event names are now preserved before the patch is applied, following the pattern already used fortoolPermissions(#2913, refs #2670). generatewarns when a root file deactivates Junie's import-only rule roots. Once.junie/AGENTS.mdexists, Junie reads it exclusively and.junie/rules/*.md/.junie/playbook.mdbecome unreachable;importalready warned about this,generatedid not. It now warns once per run, names the paths that stop being read, and points atrulesync import --targets junieas the way to carry the content over first; the global-mode path is covered too (#2909, closes #2744).importwarns when a fold target still has folded non-root rules. ForcollisionPolicy: "fold"targets such ascodexcliandjunie, importing the root file back while the original non-root rules remain in.rulesync/rules/duplicated their content on the nextgenerate, once per cycle, with nothing to say why. The import path now warns when non-root rules already target the fold tool. A follow-up made the check side-effect free so it no longer trips the generate-time "no root rule found" warning or thelocalRootvalidation during import, andlocalRootrules are excluded from the warning in global mode (#2910, closes #2743; #2914).- Skill packaging metadata shapes that tools tolerate are no longer rejected. The
license,compatibilityandmetadatafrontmatter fields were strictly typed for Kilo Code, OpenCode and deepagents, so a numeric license, array compatibility or scalar metadata aborted the wholeimport/generaterun for that file, even though none of those tools validates the fields (confirmed against their sources). The three targets now accept any shape, matching the earlier Factory Droid fix; Kiro and Rovo Dev stay strict because their docs defer to the Agent Skills specification, which does pin the types. Kiro skill frontmatter also emitsname/descriptionfirst again when thekirosection carries other keys (#2911, closes #2750). - The orphan sweeps case-fold and Unicode-normalize path identity before classifying orphans. On a case-insensitive filesystem, renaming a rule or skill by case only (
my-skill→My-Skill) madegenerate --deleteclassify the pre-existing spelling as an orphan and delete the file or directory it had just rewritten. Every sweep now compares the same case-folded, normalized identity used for cross-root de-duplication, while still deleting the original spelling (#2912, closes #2740). - Case-insensitive command collisions across import sources are resolved. Commands from a secondary import source (OpenCode's inline
commandblock, or a shared root matched by basename) were de-duplicated by exact string, socommit.mdandCommit.mdboth passed and the last copy written silently overwrote the other on macOS and Windows. The merge now goes through the sharedClaimedIdentitieshelper, the primary root keeps precedence, and a warning names which spelling won and where it came from, distinguishing same-source from cross-source collisions (#2907, closes #2741). fetchskips Windows-foldable segments and reserved device names in every fetched path. The check for names that Win32 folds (trailing dot or space,NAME~1short-name aliases) now covers every segment of every fetched path, and treatsCON,PRN,AUX,NUL,COM1–COM9andLPT1–LPT9(any case, with or without an extension) as unsafe as well. A folded nested segment drops that file, keeps the skill from being pruned, and is reported with the segment quoted as is; a skill directory with such a name is dropped whole. Lookalikes such asconsole,com10anddata~2parserare still fetched (#2899, closes #2892).- The skill picker's whitespace reason survives a cut label.
describeConfusableNameslisted the "carries more whitespace than the row shows" reason after the lookalike reason, and the latter alone exceeded the note budget, so the whitespace warning was cut off entirely. It is now listed first, since it is the one reason a reader cannot check by eye (#2900, closes #2839). fetchdrops names that begin with a combining mark and notes stray enclosing marks. A skill name whose first character is a combining mark is drawn over the checkbox or the quote before it, so such names are now dropped as hidden and shown without the mark. An enclosing mark (\p{Me}) that is not part of a keycap sequence is reported with a new sixth reason, placed right after the whitespace reason; non-spacing marks are left alone so Devanagari, Arabic and Vietnamese names are not flagged (#2901, closes #2838).watchsanitizes control characters in trigger-path log output. TheChange detected: ...line rendered watch-event paths verbatim, so a file name carrying ANSI escapes or bidirectional overrides in the watched tree could forge what the log line displays. Both the relative and the absolute fallback path are now stripped before logging, consistent with the rest of the codebase (#2908, closes #2753).- Rovo Dev import follows the configured MCP config path.
rulesync import --targets rovodev --features mcpread.rovodev/mcp.jsoneven whenmcp.mcpConfigPathpointed elsewhere. The pointer is now resolved for project-relative and home-anchored paths that stay within the import scope, and the default file is used with a warning when the pointer is unset, empty or escapes the scope (#2902, closes #2736).
Other Changes
- The Codex CLI, Muse Code and Rovo Dev MCP adapters share one guarded
getRawMcpServeraccessor for unfiltered source entries, with own-property, prototype-pollution and malformed-map handling covered directly (#2878, closes #2794). - The five existing
mcpConfigPathstates in the Rovo Dev MCP adapter are classified by a pure function and handled by an exhaustive switch, preserving every outcome and message (#2881, closes #2795). - The Homebrew formula was updated to v16.21.0 (#2897).