Skip to main content

Crate probe_hook

Crate probe_hook 

Source
Expand description

probe-hook — the edit-time ENVELOPE as a SHIPPED binary (the eleventh ask).

The whole edit-time sense organ, every voice priced in silence — this hook is FOR the agent, so it surfaces everything a path-and-text edit can derive:

  • the GUARD (Agenda::edit_guard) — refusals that already exist downstream, pre-fired;
  • the SHAPE TICKER (discover::watch::Ticker) — a coupling move on ANY Rust edit: a theory nets on its sorts, a plain module on its own declared types, and the ticker speaks only when the edit bridges two clusters or opens a new one — a sixth sense, not a firehose;
  • the QUALIFY voice (spec/qualify.spec) — the edit-time LOCK DELTA as a DRIFT LEDGER: the whole current drift of the surface census, re-derived from the tree via boundary_enforce::qualify_census_lines and diffed against the committed lock. Surfaced on ANY edit (un-scoped) but DEDUPED against the last-shown ledger, so it speaks only when the drift moves — the behavioural mirror at the edit, for the one lock a text edit can re-derive (no how-to-bless recipe: the named lock’s own header carries it);
  • the TYPE-LIBRARY voice (library_voice) — the anti-duplication sense (rung 0 of the bundle candidate): when the edited file touches sorts that already carry operators in OTHER files’ census lines, the existing families are named — per-file, deduped, the census intersection doubling as the noise filter;
  • the TIER voice (spec/tiers.spec) — on first edit of a file, its derived tier and the rules it carries (the reader-service the deleted //! Tier: markers gave);
  • the FREEZE-DELTA courier (freeze_delta_voice) — the recommendation movement the last build derived via spec_lock::Lock::delta (a placement re-settling, a seam candidate appearing), inserted once into the window and then cleared.

The first five voices the hook DERIVES from a single text edit. The sixth it does not compute at all: distance, cohesion, and placement need the compiled theory (running eval), which a text edit cannot afford — so the movement of those recommendations is derived where it is cheap, at the build that emits the locks (spec_lock::Lock::delta holds both sides at that instant), and the hook only COURIERS the result into the next context window. The qualify voice is the boundary case that DOES fit a text edit: qualification is a structural property (operator-shaped functions), so its lock delta is computed live here, while the behavioural locks stay couriered. Nothing is watched or reconstructed: the emitter narrates its own delta, the hook carries it (or, for qualify, derives it). (The freedom/survivor census stays at session start, read from the committed mutation locks.) All the voices 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. This crate is that envelope, inside the boundary — and this repo now dogfoods it in its own .claude/settings.json, the retired shape-watch.sh being exactly the glue described:

  • 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, whichever of the guard / shape-ticker / tier voices fire out (joined, one version footer), or None. 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.