Two cleanups surfaced by #2981, neither worth bundling into a bugfix PR.
1. read_tools.py is ~3000 lines and holds Gmail query grammar that is not tool code. _parse_gmail_date_value, normalize_gmail_date_operators, has_gmail_operator and operatorize_query are pure, backend-agnostic functions imported cross-module by reply_tools.py and api_routes.py — they sit in the free-function prelude, not in ReadToolsMixin. #2981 put its new duration grammar in a peer module (gmail_query.py, mirroring body_normalize.py's placement) rather than growing the file further, but deliberately did not move the existing four. Moving them is mechanical: no self coupling, three import sites.
2. util/lint.py does not cover hub/. read_tools.py currently has black drift in check_suspicious_mail (~line 3157) that no gate catches, which is why it accumulated silently. Formatting-only, but it means every hub PR carries invisible drift and reviewers see spurious diffs when someone eventually reformats.
Acceptance criteria
Two cleanups surfaced by #2981, neither worth bundling into a bugfix PR.
1.
read_tools.pyis ~3000 lines and holds Gmail query grammar that is not tool code._parse_gmail_date_value,normalize_gmail_date_operators,has_gmail_operatorandoperatorize_queryare pure, backend-agnostic functions imported cross-module byreply_tools.pyandapi_routes.py— they sit in the free-function prelude, not inReadToolsMixin. #2981 put its new duration grammar in a peer module (gmail_query.py, mirroringbody_normalize.py's placement) rather than growing the file further, but deliberately did not move the existing four. Moving them is mechanical: noselfcoupling, three import sites.2.
util/lint.pydoes not coverhub/.read_tools.pycurrently hasblackdrift incheck_suspicious_mail(~line 3157) that no gate catches, which is why it accumulated silently. Formatting-only, but it means every hub PR carries invisible drift and reviewers see spurious diffs when someone eventually reformats.Acceptance criteria
gmail_query.py;read_tools.py,reply_tools.pyandapi_routes.pyimport them from thereutil/lint.py(or CI) covershub/, and the existing drift is cleaned in the same change so the gate starts green