I built refrainify to make one review task quicker: locating repeated tool calls in a saved JSONL session. It scans the records and shows a candidate with its physical source line, block index, and timestamp. The goal is to get from a suspected pattern back to the surrounding transcript without making claims the record cannot support.
What it checks
The first rule finds maximal consecutive runs of at least three calls with the same name and decoded arguments. JSON object key order does not matter. Arrays keep their order, scalar types stay distinct, and strings remain exact, including whitespace. Calls with the same call ID and payload are treated as replays and counted once. A conflicting payload for one ID is an input error.
A second rule looks for three Read or read_file calls that name the same exact file_path or path string in a read only segment. The path is not normalized. A nonread call clears the count. This keeps the rule narrow and makes the source references easy to inspect.
The parser handles tool_use blocks from message sessions and function_call and custom_tool_call records from rollout sessions. Tool output and reasoning text are ignored. --demo runs a bundled synthetic fixture, --json emits a versioned result, and --fail-on-repeat returns status 1 when a candidate exists. Malformed JSON, duplicate keys, nonstandard numbers, conflicting replays, and oversized input lines return status 2.
Candidates need context
A repeated call may be a retry, a deliberate recheck, or a useful repeated read. The transcript also does not prove that records ran in order, show whether work progressed, or measure token use. Refrainify reports a pattern and its source locations, then leaves the interpretation to the person reviewing the session. Empty sessions say that no supported calls were recognized. Unknown record types are counted in JSON coverage.
Try refrainify --demo or pass a saved JSONL file. Without a file, discovery looks for a recent transcript whose recorded working directory matches the current directory. Equal newest timestamps require an explicit path. The scanner uses only Python's standard library at runtime.
I would like feedback on two things: whether the line and block references are enough to inspect a candidate, and which native records should be added next. The code and clean control fixture are available at https://github.com/Arthur031221/refrainify.

Top comments (0)