refactor: one transcript reader, and stop printing into every session - #16
Merged
Conversation
Stop announcing the shim in every session. `grask shim` printed a success line on stdout, and a SessionStart hook's stdout is injected into the session's context and shown to the developer — so every session, in every project, carried a notification about grask's plumbing charged to the context window of work that has nothing to do with grask. Success is now silent; only the failure has anything to say, and it says it on stderr. Pinned by a test, because this is the kind of thing that regresses without anyone noticing. Read a transcript in one place. `transcript.extract` and `dialogue.extract_dialogue` walk the same file for different subsets and had drifted into two copies of the same loop: the parse-and-skip-partial-lines scan, the `promptSource` filter that decides what counts as the developer speaking, the message-content unwrap, the edit-tool path pull, and EDIT_TOOLS itself. All five now have one definition. The block-list guard on human turns existed in only one copy, which is the shape this duplication fails in — the filter is load-bearing, and a second copy of it is a second thing that can be wrong. Verified byte-identical against the pre-refactor logic over 120 real transcripts. Normalize quotes the same way at both stages. `_normalize` existed twice — `" ".join(text.split())` in triage, `re.sub(r"\s+", " ", …)` in seed — for the same job: checking a model-claimed quote against what the developer typed. They agreed today and nothing held them together. A quote stage 1 evidences and stage 2 cannot find costs the whole seed, silently, at the last stage that could still have produced a question. Approve only the shape the skill actually runs. approve.py claimed "only `record` takes a positional, and only one: the probe id" and accepted `record 1 2 3`, `record ../../etc/passwd`, and a bare `record`. Nothing here was exploitable — argparse refuses all three downstream — but this hook spends the developer's standing approval on a Bash command without prompting, and an approval rule that merely resembles the shape it approves is the one kind of looseness that costs something. Positionals are now counted and required to be digits. `ask._unservable` becomes `ask.unservable`, since cli.py has always called it across the module boundary. A docstring paragraph duplicated between `_serve` and `_next_payload` is deduped. `.DS_Store`, `.idea/`, and `brag-output/` are gitignored — each was sitting untracked in a working tree mid-release. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
Removes duplicated code across the capture path, and fixes two things that
duplication was hiding.
One transcript reader.
transcript.extract(stage 0) anddialogue.extract_dialogue(stages 2/3) walk the same file for differentsubsets and had drifted into two copies of one loop. Five things were defined
twice — the parse-and-skip-partial-lines scan, the
promptSourcefilter, themessage-content unwrap, the edit-tool path pull, and
EDIT_TOOLS— and nowhave one definition each in
transcript.py.One quote normalizer.
_normalizeexisted in bothtriage.pyandseed.py, written differently, for the same job.The shim stops printing into every session.
grask shimprinted a successline on stdout. A SessionStart hook's stdout is injected into the session's
context and shown to the developer.
The approval hook matches the shape it approves.
approve.pyacceptedrecord 1 2 3,record ../../etc/passwd, and a barerecord.Plus:
ask._unservable→ask.unservable(cli.py has always called it acrossthe boundary), a docstring paragraph deduped between
_serveand_next_payload, and.DS_Store/.idea//brag-output/gitignored.Why
The two extraction copies were not symmetrical, which is how this kind of
duplication actually fails: the block-list guard on human turns existed in only
one of them. That filter is load-bearing — it is the only thing separating a
turn the developer authored from injected skill text — and a second copy of it
is a second thing that can be wrong.
The two
_normalizeimplementations agreed today and nothing held themtogether. Both check a model-claimed quote against what the developer typed, at
stage 1 and stage 2. A quote stage 1 evidences and stage 2 cannot find costs
the whole seed, silently, at the last stage that could still have produced a
question.
The shim line is the one with a per-session cost. It is not a large number of
tokens, but it is spent in every session in every project, on a notification
about grask's plumbing, charged to the context window of work that has nothing
to do with grask. Success is now silent; only the failure has anything to say,
and it says it on stderr.
approve.pyis the file where a loose rule costs something. It spends thedeveloper's standing approval on a Bash command without prompting, and its own
docstring claimed "only
recordtakes a positional, and only one: the probeid" while accepting three shapes that were not that. Nothing here was
exploitable — argparse refuses all three downstream — but an approval rule that
merely resembles the shape it approves is not doing the job it claims to.
On the rules enforced in code rather than prompted for: none moved. The
evidence rule, the one-question rule, and the option gates are untouched. The
promptSourcefilter and the quote check were each enforced in code twice;they are now enforced in code once, which is the same rule with one fewer place
to break.
Checks
uv run pytestpasses — 348 passed, 1 deselected (was 341)uv run ruff check .passesuv run mypypassesthe three
recordshapes now refused, and both readers agreeing on humanturns
logs, or commit messages
Additionally, both readers were verified byte-identical to the pre-refactor
logic across 120 real transcripts on disk (turns, files touched, cwd, branch).
That check read from
~/.claude/projectsand is deliberately not committed;nothing it read appears in this diff.
🤖 Generated with Claude Code