fix(cli): let render_summary own the stage prefix exactly once (#504) - #505
Merged
Conversation
`render_summary` prefixes every line with `"{stage}: "`, but sixteen of the
notice sites named their own stage as well, so those lines rendered doubled
-- `Identity: Identity: applied 1, skipped 0.` The probe-derived notices
(`probe.unavailable`, `probe.empty_message`) never named a stage and
rendered correctly, so a single summary block mixed two formats depending
on which branch produced the outcome.
Strip the stage name from all sixteen notice sites and leave
`render_summary` as the sole owner of the prefix. That aligns every line
with the probe-derived ones, which were already right -- this adopts the
convention half the paths were using, it does not invent one. Each notice's
post-prefix text is preserved byte for byte, so rendered output changes
only by losing the duplicate.
`StageOutcome.notice` is consumed at exactly one place and `render_summary`
has exactly one caller, so nothing else observes the change.
`ctx.ollama_unavailable_notice` stores one of these strings but is only ever
tested for `is not None`, never printed on its own. Both docstrings now
state the invariant.
The bug survived because all 26 summary assertions were substring checks,
and `"Identity: applied 1"` is a substring of `"Identity: Identity: applied
1"`. 22 of them are now full-line assertions through a new `_lines()`
helper; the other 4 were not summary-line assertions. Verified by
reintroducing the prefix at one site: six tests fail.
Closes #504
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.
Closes #504
The defect
render_summary(src/openkos/cli/curate.py:1213) prefixes every line with"{stage}: ". Sixteen notice sites named their own stage as well, so those lines rendered doubled:The probe-derived notices —
probe.unavailable(curate.py:294) andprobe.empty_message(curate.py:325,513,716,912) — never named a stage, so they rendered correctly. A single summary block therefore mixed two formats depending on which branch produced the outcome:The issue as originally filed claimed uniform doubling and listed seven sites. Both were wrong and are corrected in this comment: the defect is inconsistency, and there are sixteen sites. The original repro was misleading because it constructed notices with the stage name instead of using real ones — it proved
render_summaryprepends, not that real notices double.The fix
Strip the stage name from all sixteen sites; leave
render_summaryas the sole owner of the prefix. This adopts the convention the probe-derived paths were already using rather than inventing one. Each notice's post-prefix text is preserved byte for byte, so output changes only by losing the duplicate.Blast radius verified as contained:
StageOutcome.noticeis consumed at exactly one place (curate.py:1213);render_summaryhas exactly one caller (src/openkos/cli/main.py:11326).ctx.ollama_unavailable_noticestores one of these strings but is only ever testedis not None(curate.py:1127), never printed on its own.probe.notice(curate.py:1117) is a different object echoed raw to stderr — untouched.stage.nameuses correctly remain: the stderr refusal (curate.py:241),stage_noticeobservability (curate.py:1100), and the prefix owner itself (curate.py:1213).Both docstrings now state the invariant so the next notice does not reintroduce it.
Why it survived this long
All 26 summary assertions in
tests/unit/cli/test_curate.pywere substring checks, and"Identity: applied 1"is a substring of"Identity: Identity: applied 1". 22 are now full-line assertions through a new_lines()helper; the other 4 are not summary-line assertions (one absence check, two fixture values, one equality against a production-built outcome).That conversion — not the prefix removal — is the part that keeps this fixed.
Verification
'Identity: Identity: applied 1, skipped 0.'sitting beside the correct'Structure: No untyped edges found.'uv run pytest -q→ 4037 passed, 1 skipped (was 4035;test_curate.py98 → 100).uv run mypy .clean on 179 files;ruff checkandruff format --checkclean.Identity:atcurate.py:469alone fails six tests, then reverted with the inverse edit.Disclosure
No review receipt. Two independent reasons, both stated plainly:
Read, Grep, Globwith no shell while their protocol mandatesgentle-ai review inspect-candidateand forbids substituting live files. Three launches on the previous candidate returnedinspection: incomplete.review-505bbb08be465499) was deliberately left unfinalized rather than presented as coverage.No PASS was authored in either case. The change stands on the suite, the lints, and the mutation evidence above.