Skip to content

v16.25.0

Choose a tag to compare

@dyoshikawa dyoshikawa released this 08 Sep 04:22
· 1131 commits to main since this release
Immutable release. Only release title and notes can be modified.
9d53825

What's Changed

ZCode: hooks support in both project and global scope

zcode joins the hooks matrix. rulesync generate writes canonical hooks from .rulesync/hooks.jsonc into ZCode's configuration file — .zcode/config.json for a project, ~/.zcode/cli/config.json in global mode — and rulesync import --targets zcode --features hooks reads them back.

Details worth knowing:

  • ZCode gates its whole hooks subsystem on hooks.enabled, which is off by default. Rulesync states enabled: true when it authors hooks, but an existing enabled: false is treated as the user's kill switch and preserved, so generating never re-enables a subsystem someone deliberately turned off.
  • Only the event keys inside the hooks mapping are owned by rulesync; anything else in the file is left untouched.
  • ZCode's native process hook type has no canonical equivalent, so such entries are skipped with a warning on import rather than being mangled into a command hook.

Thanks to @gdm257 for the implementation.

Hooks: keep handlers rulesync did not write, without losing the ability to retract its own

Until now, regenerating a tool's hooks replaced the destination's hook list wholesale. A handler another tool or a person had added by hand there — a formatter, a guard script — disappeared on the next generate. That is still the default, but a project can now opt out of it with "preserveUnownedHooks": true in rulesync.jsonc:

{
  "preserveUnownedHooks": true
}

With it on, handlers rulesync did not write are kept and the generated ones are appended. It applies to Claude Code, Codex CLI and Cursor, in both project and global scope; the Claude Code plugin bundle, which rulesync owns outright, always replaces.

The part that makes this safe is that "owned" is a record rather than a guess. Hooks execute arbitrary shell, so a preservation rule that cannot tell rulesync's own output apart from a third party's would permanently strand any hook rulesync ever wrote. Each run therefore records what it generated in a .rulesync-hooks-lock.json next to the hooks file (.claude/.rulesync-hooks-lock.json, and so on) — commit it alongside the generated configuration. On the next run an existing handler is:

  • replaced when it is generated again,
  • retracted, with a warning, when the lock says rulesync wrote it and the sources no longer define it,
  • preserved otherwise.

A missing, malformed or foreign-version lock degrades to preserving everything, so the failure mode is always the non-destructive one. The one caveat, documented in the guide: the first run after opting in has no lock yet, so a hook rulesync wrote before is kept once and retracted from the run after that.

There is no CLI flag — this is a project policy, not a per-invocation one — and the option deliberately lives in rulesync.jsonc rather than in .rulesync/hooks.jsonc, which is a fetchable, committable source file that would otherwise carry a destructive-behavior switch into whatever project fetched it.

Thanks to @RaviTharuma for reporting (#2940) and implementing this.

Contributors

Full Changelog

v16.24.1...v16.25.0