ruleprobe by Jake Selby v0.1.0
GitHub

How good are the detectors?

A hit rate is a rate of the detector until somebody says what the detector should have found. So a labelled corpus ships inside the package, at ruleprobe/corpus/: six synthetic sessions in both transcript shapes, every interesting event labelled by hand with the detectors that ought to fire on it, and a deliberate near-miss beside each one - a cat of a line range, a find narrowed by -name, a git push after the gate ran, a heredoc with rm -rf in its body as text rather than as a command.

ruleprobe corpus
detector                                pos  neg   tp   fp   fn   prec  recall     f1  note
-------------------------------------------------------------------------------------------
cache-hygiene/compact                     5    6    5    0    0   1.00    1.00   1.00
cache-hygiene/model-switch                5   10    5    0    0   1.00    1.00   1.00
secrets/secret-in-write                   6    6    6    0    0   1.00    1.00   1.00
transcript-hygiene/unfiltered-find        5    8    5    0    0   1.00    1.00   1.00
transcript-hygiene/whole-file-cat         5    6    5    0    0   1.00    1.00   1.00
verification/no-verify                    6    6    6    0    0   1.00    1.00   1.00
-------------------------------------------------------------------------------------------
total                                    32   42   32    0    0   1.00    1.00   1.00  floor 0.90

pos and neg are what the labels asked for; tp, fp and fn are what happened. ruleprobe corpus --floor 0.9 exits non-zero when a scored detector falls under the floor, and CI in this repository runs exactly that. It is a gate on the repository, not on a run: nothing in ruleprobe report reads the floor, and no report of yours will ever fail because a detector scored badly. --json prints the same numbers as data.

ruleprobe report --validity puts each detector’s p= and r= beside its row. It is off by default because the report is meant to be read in a minute and an eight-column table is not, and because the same two numbers apply to every run - they belong to the detector, not to your transcripts.

Read the number for what it is. The corpus is synthetic and hand-labelled: no real transcript content, no home paths, no personal names. A 1.00 says the detector is right about the shapes somebody thought to write down, which is a weaker claim than it looks - the false positives a detector meets in the wild are the ones nobody anticipated. It is a floor under an obvious mistake and a place to put the next surprising transcript, not a measured field accuracy. Growing it is the cheapest contribution this repository takes: add a session under ruleprobe/corpus/sessions/, label it in ruleprobe/corpus/labels.yaml, and the table above moves.

A detector of your own scores itself. Rather than a corpus, a declarative detector may carry an examples: block of minimal cases, and ruleprobe corpus --rules ./docs/rules scores those:

- id: house-style/sudo-install
  rule: house-style
  event: tool_use
  when:
    command: {starts_with: [sudo, pip]}
  examples:
    fire:
      - bash: sudo pip install ruff
    skip:
      - bash: uv pip install ruff
        note: the tool the rule asks for

fire is a list of cases the detector should fire on and skip a list it should not. A case is bash: <command>, or event: <one event>, or events: [...] for a session detector, with an optional note. Nothing runs them at report time. A detector with no examples and no corpus label prints no examples rather than a number, and the floor steps over it: an unmeasured detector is a gap to see, not a failure to fix.

↑↓ move↵ openPagefind · indexed at build