probe-hook — the edit guard as a SHIPPED envelope (the eleventh ask).
Agenda::edit_guard and GuardVoices::for_edit are mutation-tested and
register-driven — and before this crate, every consumer wrapped them in the same
four pieces of unjudged glue: a bash wrapper, inline JSON-parsing Python, a
build-on-demand fallback that could run a stale binary, and hand-authored
settings.json plumbing. All of it outside the mutation boundary, each consumer
with different bugs. This crate is that envelope, inside the boundary:
- speaks the Claude Code hook protocol natively — reads the PostToolUse JSON
from stdin, extracts
tool_input.file_path, honoursCLAUDE_PROJECT_DIR; - discovers the repo's own declarations — voices derived from the tree
(
GuardVoices::for_edit), classes taught fromspec/agenda.register; the consumer writes zero code; - carries the fail-open contract inside the boundary — every internal failure
(malformed JSON, missing path, unreadable file, refused register) is silence,
as a drilled property of [
respond], not a|| exit 0convention; - installs its own wiring — [
install] writes or idempotently merges thesettings.jsonentry, so the plumbing is derived output, never hand-authored.
Honest frame — version skew: a globally installed probe-hook can be newer or
older than the probe-algebra a repo pins, and the two can disagree about register
grammar or voice derivation. The floor shipped here: every non-silent voice block
carries the binary's version on its last line, and the guard is advisory and
fail-open, so skew degrades to weaker advice, never a false refusal. (Re-execing a
repo-local build is the known nicer form; deliberately not built until skew is
observed hurting.)