module

Engram::Hooks

Installs/uninstalls the three git hooks (post-checkout, post-merge, post-rewrite) that keep the per-clone cache in sync automatically. Each hook body invokes the absolute path of the engram binary that ran hook install (resolved via Process.executable_path), not a bare engram — git runs hooks under a minimal, noninteractive PATH that most shells' interactive PATH never touches, so a bare command silently no-ops (hook exits nonzero, cache goes stale, git checkout never fails loudly) unless engram happens to be installed to a directory already on that noninteractive PATH. Baking in the absolute path makes the hook work regardless of PATH. Every managed block is wrapped in marker comments so uninstall can remove exactly what install added, regardless of whatever else lives in the hook file; a comment line inside the block also records the baked-in path in a form doctor (and a repeat install) can read back out without re-parsing shell quoting.

Callers must pass the effective hooks directory — the one git itself will actually run hooks from, resolved via git rev-parse --git-path hooks (see Cli.hooks_dir_for). That's not always <repo>/.git/hooks: core.hooksPath can point anywhere, and in a linked worktree the hooks git runs live in the shared common dir, not the worktree's own private gitdir. This module never reconstructs that path itself — it only reads/writes files directly under whatever directory it's given.

Constants

ENGRAM_BIN_PREFIX = "# engram-bin: "

The comment line, inside the marker block, that records the baked-in absolute engram path in a directly-greppable form — no shell-quote parsing required to read it back (used by installed_engram_path and, transitively, doctor).

HOOK_NAMES = ["post-checkout", "post-merge", "post-rewrite"]

The three lifecycle hooks that correspond to "the working tree just changed underneath us".

MARKER_END = "# <<< engram <<<"

Closes the engram-managed block in a hook file.

MARKER_START = "# >>> engram >>>"

Opens the engram-managed block in a hook file.

Class methods

install(hooks_dir : String, engram_path : String = self.resolve_engram_path) : Array(String)

Installs the engram sync snippet into each of HOOK_NAMES under the effective hooks_dir (creating the hook file with a shebang if it doesn't exist yet; appending, guarded by the marker comments, if it does). engram_path defaults to the currently-running binary's own absolute path — that's what gets baked into the hook body, so pass an explicit value only in tests or other unusual callers. Returns the names actually installed or refreshed: a hook whose existing block already bakes in engram_path is left untouched and excluded from the result; a hook whose block bakes in some other path (the binary was rebuilt or moved since the last install) has its block rewritten in place, so re-running hook install after moving the binary actually repairs a hook doctor flagged as stale, rather than being a permanent no-op.

Source
installed_engram_path(path : String) : String | Nil

The absolute engram path baked into path's marker block by install, or nil if path doesn't exist, carries no engram block, or (a hook installed before this hardening landed) has a block with no # engram-bin: line. doctor uses this to confirm an "installed" hook would actually resolve engram at run time rather than silently no-op under git's minimal noninteractive PATH.

Source
resolve_engram_path

The absolute path of the engram binary currently running this process. Falls back to the bare command name only if the OS can't report it (rare, exotic platforms) — that fallback reproduces the pre-hardening PATH-dependent behavior rather than baking in something worse than a bare command.

Source
uninstall(hooks_dir : String) : Array(String)

Removes the engram-managed block from each of HOOK_NAMES under the effective hooks_dir. A hook file left with nothing but a shebang (or nothing at all) after removal is deleted outright. Returns the names actually removed.

Source