Security fix
Path guard fail-open on aliased roots. confineToRoot canonicalized only the candidate path, then compared it against the lexically-resolved project root. When the root and a tool-supplied file path were expressed in different-but-equivalent forms - a symlinked ancestor (/tmp vs /private/tmp on macOS) or a case alias on a case-insensitive volume - the containment check miscompared and returned null for a genuinely in-root path.
Because pre-read, pre-write, and file-changed exit early when confineToRoot returns null, an affected session silently skipped every guardrail, map update, and change-tracking write for those paths.
The guard now canonicalizes both root and candidate (resolving symlinks and case aliasing via fs.realpathSync.native) before the containment check. Symlinked-ancestor escapes and .. traversal are still rejected. Mirrored in the zero-dependency hook runtime and pinned by an extended parity test; new symlink-alias and case-alias regression tests added.
Full CI green (build & test on Node 20/22, CVE audit, CodeQL).