Skip to main content

Crate probe_hook

Crate probe_hook 

Source
Expand description

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, honours CLAUDE_PROJECT_DIR;
  • discovers the repo’s own declarations — voices derived from the tree (GuardVoices::for_edit), classes taught from spec/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 0 convention;
  • installs its own wiringinstall writes or idempotently merges the settings.json entry, 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.)

Functions§

extract_path
The edited file’s path, out of the hook protocol’s stdin JSON (tool_input.file_path). None for anything else — malformed JSON, a tool event without a path — because the hook’s silence must be total on input it does not understand.
install
Write or merge the .claude/settings.json hook entry — the plumbing as DERIVED output. Idempotent: an entry whose command already invokes probe-hook is left alone; everything else in the file is preserved untouched. Returns a line saying what happened; errs only on a settings.json that exists but does not parse (never overwrite what cannot be read — that file is not ours).
respond
The whole envelope, total: hook JSON in, at most one voice block out. Every failure path is None — the fail-open contract as a return type. The voices are derived from project_dir (the register, the shim evidence), the classes come from spec/agenda.register, and the edited path is normalized repo-relative so messages match what the repo’s own gates would say.