process.execPath on Homebrew resolves symlinks and returns the versioned
Cellar path (e.g. /usr/local/Cellar/node/25.8.1/bin/node). After
brew upgrade node, the old Cellar binary fails with dyld: Library not
loaded because shared libraries have changed SOVERSION.
- Add normalizeNodePath() helper that maps Cellar paths to stable
Homebrew symlinks (/usr/local/bin/node or /opt/homebrew/bin/node)
- resolveNodeRunner() now calls normalizeNodePath() before quoting
- rewriteLegacyManagedNodeHookCommands() also normalizes baked Cellar
runner paths in existing hook commands so reinstall doesn't re-bake them
- Export normalizeNodePath for testability
- Add 22 tests covering all cases (Cellar paths, stable symlinks,
NVM, system node, Windows, null/empty, both function surfaces)
Closes#3181
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>