Skip to content

feat: catch-all router primitives + explore recipe skeleton#7

Merged
HeinrichvH merged 1 commit intomainfrom
feat/catch-all-router
Apr 22, 2026
Merged

feat: catch-all router primitives + explore recipe skeleton#7
HeinrichvH merged 1 commit intomainfrom
feat/catch-all-router

Conversation

@HeinrichvH
Copy link
Copy Markdown
Owner

Enables a single "matcher": "*" entry in Claude Code's settings.json to route every tool call through Exactor, with per-rule intercept/skip logic living in .exactor.yml instead of duplicated across Claude config and Exactor config. Richer rule primitives + hard fail-open contract so catch-all doesn't turn a config typo into a session-wide block.

Fail-open contract

Under a catch-all matcher, any unexpected error in this hook blocks every tool call. pre_tool_use / post_tool_use now wrap their bodies in an outer try/except — on any exception (malformed stdin, missing worker, YAML parse error, runtime surprise) they log to stderr and exit 0 so the raw tool runs. Also validates route_to against defined workers at config-load time so misconfigs surface via exactor check rather than at tool-fire time. Runtime re-checks worker existence as belt-and-braces. Six new tests pin the contract: unmatched tool, missing config, malformed stdin, malformed YAML, load-time validation, runtime ghost-worker.

query_template

New InterceptRule.query_template field — Python str.format-style template that interpolates multiple tool_input fields into one query. Lets Grep intercepts fold pattern + path + glob into a natural-language question for a code-exploration subagent, instead of losing scope by picking just one field. Missing keys render as empty string via a SafeDict so optional fields don't raise. Takes precedence over query_field when both are set.

unless_match

New InterceptRule.unless_match field — regex on the templated query; rule is skipped if it matches. Symmetric with existing match, more flexible than the named unless predicates for query-shape heuristics (e.g. "skip trivially short patterns"). Both named unless and unless_match can coexist on the same rule.

CLI default subcommand

exactor hook (no subcommand) now defaults to event=pre. Older docs suggested the bare form, and a catch-all matcher exposes argparse failures as blocking hook errors on every tool call. Explicit forms (exactor hook pre, exactor hook post) continue to work unchanged.

recipes/explore/

Skeleton recipe documenting the shape for routing Grep / Glob (and optionally Read) to a code-search subagent. Ships with commented intercept rules, sensible defaults (cache: false — code changes under us), and a README explaining the contract (explore CLI takes one positional query arg, prints a cited report to stdout). Users bring their own subagent (Goose, Aider, Claude API, etc.).

Tests

57 passing, +14 new (fail-open x6, query_template x5, unless_match x2, cli-default x1).

Enables a single `"matcher": "*"` entry in Claude Code's settings.json to
route *every* tool call through Exactor, with per-rule intercept/skip
logic living in `.exactor.yml` instead of duplicated across Claude config
and Exactor config. Richer rule primitives + hard fail-open contract so
catch-all doesn't turn a config typo into a session-wide block.

## Fail-open contract

Under a catch-all matcher, any unexpected error in this hook blocks every
tool call. `pre_tool_use` / `post_tool_use` now wrap their bodies in an
outer try/except — on any exception (malformed stdin, missing worker,
YAML parse error, runtime surprise) they log to stderr and exit 0 so the
raw tool runs. Also validates `route_to` against defined workers at
config-load time so misconfigs surface via `exactor check` rather than
at tool-fire time. Runtime re-checks worker existence as belt-and-braces.
Six new tests pin the contract: unmatched tool, missing config, malformed
stdin, malformed YAML, load-time validation, runtime ghost-worker.

## query_template

New `InterceptRule.query_template` field — Python str.format-style template
that interpolates multiple tool_input fields into one query. Lets Grep
intercepts fold pattern + path + glob into a natural-language question for
a code-exploration subagent, instead of losing scope by picking just one
field. Missing keys render as empty string via a SafeDict so optional
fields don't raise. Takes precedence over query_field when both are set.

## unless_match

New `InterceptRule.unless_match` field — regex on the templated query;
rule is skipped if it matches. Symmetric with existing `match`, more
flexible than the named `unless` predicates for query-shape heuristics
(e.g. "skip trivially short patterns"). Both named `unless` and
`unless_match` can coexist on the same rule.

## CLI default subcommand

`exactor hook` (no subcommand) now defaults to `event=pre`. Older docs
suggested the bare form, and a catch-all matcher exposes argparse
failures as blocking hook errors on every tool call. Explicit forms
(`exactor hook pre`, `exactor hook post`) continue to work unchanged.

## recipes/explore/

Skeleton recipe documenting the shape for routing Grep / Glob (and
optionally Read) to a code-search subagent. Ships with commented intercept
rules, sensible defaults (`cache: false` — code changes under us), and a
README explaining the contract (`explore` CLI takes one positional query
arg, prints a cited report to stdout). Users bring their own subagent
(Goose, Aider, Claude API, etc.).

## Tests

57 passing, +14 new (fail-open x6, query_template x5, unless_match x2,
cli-default x1).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@HeinrichvH HeinrichvH merged commit c34cb27 into main Apr 22, 2026
3 checks passed
@HeinrichvH HeinrichvH deleted the feat/catch-all-router branch April 22, 2026 18:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant