fix(observe): readers anchor on the nearest materialized hook; installers on the linked root
Follow-up to #178 from its post-merge review (round 4, filed against main after the squash).
Critical taken
auditRoot preferred the link file over the materialized hook, while the hook writes at its own entry path and the installers materialized at cwd. A re-link or insta observe install from a subdirectory of a linked repo minted a second hook under that subdirectory; sessions there wrote to it, and observe report|sync read the link root and printed empty. Two changes so the halves can't diverge:
- Readers anchor on the nearest materialized hook above cwd (what the Codex wrapper and the Claude entry actually run), then the link file, then cwd.
nearestHookRoot()/auditRoot()incommands/observe.ts. - Installers anchor on the linked project root (
installRoot(), same rule aswriteProject), so a re-link orobserve installfrom a subdirectory refreshes the project's hook instead of creating a second one.observe uninstalluses the same root.
Suggestions taken
- Fourth
auditRootcase in the test: link file at the repo root and a hook below it β the hook wins; above the hook the link file still anchors. Plus aninstallRoottest. - Codex wrapper exits 0 when the child could not be started or was killed (
status === null); a non-zero exit there is reported by Codex as a failed hook after every tool call, which is worse than one missed audit line. The hook's own status still passes through when it ran. - The
git rm -r --cachedhint is printed even when everyskills addfailed: tracked files are independent of today's adds. The ignore entries still wait for a successful add.
Verification
npm run typecheck clean; vitest 715/715 (2 new tests, one extended).
π€ Generated with Claude Code
https://claude.ai/code/session_0144rBPvDXaGgoje2QnmfxzY
Recorded decision
Cleanup of nested hooks already materialized by 0.0.59 (John-bot, non-blocking) β not added. A stale nested hook is self-consistent (a session below it writes there and observe report from below it reads there), 0.0.59 was the only release that could create one and was live for minutes, and removing it is rm -r <subdir>/.insta/observe plus the two hook entries. A migration that deletes user-visible files from a subdirectory on the next link is more surprising than the state it would fix.