Skip to content

refactor: one transcript reader, and stop printing into every session - #16

Merged
imkp1 merged 1 commit into
mainfrom
refactor/one-transcript-reader
Jul 27, 2026
Merged

refactor: one transcript reader, and stop printing into every session#16
imkp1 merged 1 commit into
mainfrom
refactor/one-transcript-reader

Conversation

@imkp1

@imkp1 imkp1 commented Jul 27, 2026

Copy link
Copy Markdown
Owner

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) and
dialogue.extract_dialogue (stages 2/3) walk the same file for different
subsets and had drifted into two copies of one loop. Five things were defined
twice — the parse-and-skip-partial-lines scan, the promptSource filter, the
message-content unwrap, the edit-tool path pull, and EDIT_TOOLS — and now
have one definition each in transcript.py.

One quote normalizer. _normalize existed in both triage.py and
seed.py, written differently, for the same job.

The shim stops printing into every session. grask shim printed a success
line 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.py accepted
record 1 2 3, record ../../etc/passwd, and a bare record.

Plus: ask._unservableask.unservable (cli.py has always called it across
the boundary), a docstring paragraph deduped between _serve and
_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 _normalize implementations agreed today and nothing held them
together. 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.py is the file where a loose rule costs something. It spends the
developer's standing approval on a Bash command without prompting, and its own
docstring claimed "only record takes a positional, and only one: the probe
id" 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
promptSource filter 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 pytest passes — 348 passed, 1 deselected (was 341)
  • uv run ruff check . passes
  • uv run mypy passes
  • New behaviour has a test that fails without the change — shim silence,
    the three record shapes now refused, and both readers agreeing on human
    turns
  • No test added here calls a real model (calibration-marked tests excepted)
  • Nothing derived from a real transcript appears in the diff — fixtures,
    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/projects and is deliberately not committed;
nothing it read appears in this diff.

🤖 Generated with Claude Code

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>
@imkp1
imkp1 merged commit cc12954 into main Jul 27, 2026
10 checks passed
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