Skip to content

fix(context-assembly): kill 123 pre-existing mutation survivors in condensers.py (#228) - #299

Merged
cdeust merged 1 commit into
mainfrom
fix-condensers-mutation-survivors-228
Jul 30, 2026
Merged

fix(context-assembly): kill 123 pre-existing mutation survivors in condensers.py (#228)#299
cdeust merged 1 commit into
mainfrom
fix-condensers-mutation-survivors-228

Conversation

@cdeust

@cdeust cdeust commented Jul 30, 2026

Copy link
Copy Markdown
Owner

What

Issue #228 asked for the 125 pre-existing mutation survivors in
core/context_assembly/condensers.py — every function outside #196's
condense_assembled_context — to be killed by contract tests or
documented as equivalent (§12.1: no third disposition).

The file was also 391 lines (over the 300-line §4.1 cap), with two
functions over the 40-line §4.2 cap (condense_assistant_message 63,
condense_assembled_context 66) — so the behaviour-preserving split
came first, per the issue's own framing: "if the file also breaches §4
sizes, the behaviour-preserving split comes first (existing suite
passing unchanged is the proof), then the tests."

Split (behaviour-preserving)

condensers.py (391 lines) → one file per condenser family, plus a thin
re-export facade so every existing import path (including the
mock.patch("...condensers.condense_assembled_context") target in
tests_py/core/test_pg_recall_assemble_context_condensation.py) keeps
resolving unchanged:

File Contents Lines
condense_text.py condense_user_message, condense_timeline_event + sentence helpers 100
condense_code.py condense_code_block, condense_assistant_message + fence helpers 189
condense_structured.py condense_entity_triples 46
condense_dispatch.py condense_memory_content (shape/tag dispatch) 76
condense_stage.py condense_assembled_context (#196 wiring) 113
condensers.py re-export facade (docstring + imports only) 70

Proof of zero behaviour change: the pre-existing 36-test
test_condensers.py + the 3-test pg_recall wiring suite (39 tests, 216
across the whole context_assembly test directory) pass unmodified
against the split, verified before a single new test was written.

One dead-code branch surfaced by the mutation run is deleted, not kept
(§9/§12.1 — "every mutant of an unreachable branch survives... remove
it, don't test it"): condense_assistant_message's trailing
if prose_parts and prose_budget > 0: ...; return join(code_parts) # pragma: no cover fallback, every mutant of which survived. The
reachability proof moves to the use site in condense_code.py and is
pinned by two tests (see ledger). A stale docstring claim about a "late
import" of assemble_prompt (the import was already module-scope, not
late) is corrected in the same pass — a seen defect in touched material
(§14).

Mutation run

Full methodology, per-mutant verification (mutmut show, not asserted
from memory), and a reproducible 2nd run confirming the same 6 survivor
IDs: docs/audits/condensers-mutation-run-2026-07-30.md.

mutants killed survived
This PR 383 377 6 (all equivalent, all documented at the use site)

condense_assembled_context (#196's own function, already triaged to 0
non-equivalent survivors) re-confirms at 0 after the split — no
regression.

Completion Ledger

Diff is git diff origin/main...HEAD. No new production branches are
introduced (this is a behaviour-preserving split + one dead-code
deletion); every pre-existing branch that had a test in test_condensers.py
now additionally has an exact-equality mutation-hardening test in the file
mirroring its new module.

Path / behaviour Test(s) Evidence
condense_user_message boundary (<= fast path, mid-question keep, tighter-budget rebuild) test_condense_text.py::test_user_message_at_exact_budget_is_returned_verbatim, ::test_user_message_keeps_every_middle_question_in_order, ::test_user_message_result_at_exact_budget_is_not_truncated, ::test_user_message_truncates_the_rejoined_result_when_over_budget exact-equality, ran green
condense_user_message 2-sentence fallback (len(sentences) <= 2) test_condense_text.py::test_two_sentence_message_truncates_the_original_not_the_rejoin ran green
condense_timeline_event 3 date-format arms + no-date + boundary + over-budget test_condense_text.py::test_timeline_event_reads_the_bracketed_date_marker, ::test_timeline_event_reads_an_iso_date, ::test_timeline_event_reads_a_month_day_year_date, ::test_timeline_event_at_exact_budget_is_returned_verbatim, ::test_timeline_event_compressed_at_exact_budget_is_not_truncated, ::test_timeline_event_truncates_the_compressed_form_not_the_raw_text ran green
_split_sentences/_first_sentence helpers test_condense_text.py::test_first_sentence_returns_the_first_sentence_not_the_whole_text, ::test_split_sentences_strips_and_drops_empty_parts ran green
condense_assistant_message code-only branch (code_tokens >= token_budget), boundary, oversized-block degrade test_condense_code.py::test_assistant_code_only_path_stops_at_the_budget_boundary, ::test_assistant_code_equal_to_budget_takes_the_code_only_path, ::test_assistant_message_at_exact_budget_is_returned_verbatim ran green
condense_assistant_message prose-interleave branch + floor + blank-fallback test_condense_code.py::test_assistant_interleaves_prose_and_code_in_original_order, ::test_assistant_prose_share_scales_with_the_leftover_budget, ::test_assistant_blank_only_content_falls_back_to_truncation ran green
Deleted dead-code branch's unreachability proof test_condense_code.py::test_assistant_unclosed_fence_is_a_single_code_segment, ::test_split_by_code_blocks_segments_preserve_original_newlines ran green
condense_code_block signature scan, boundary, prefix roster test_condense_code.py::test_code_block_at_exact_budget_is_returned_verbatim, ::test_code_block_keeps_every_declared_signature_prefix, ::test_code_block_signature_accounting_stops_at_the_budget_boundary ran green
_has_code_blocks/_split_by_code_blocks helpers test_condense_code.py::test_has_code_blocks_indent_run_threshold_is_three, ::test_has_code_blocks_detects_a_fence ran green
condense_entity_triples boundary + accounting test_condense_structured.py::test_entity_triples_at_exact_budget_is_returned_verbatim, ::test_entity_triples_stop_at_the_budget_boundary ran green
condense_memory_content tag-dispatch (code/file/timeline/event arms) test_condense_dispatch.py::test_code_tag_routes_to_the_code_block_condenser, ::test_file_tag_routes_to_the_code_block_condenser, ::test_timeline_tag_routes_to_the_timeline_condenser, ::test_event_tag_routes_to_the_timeline_condenser ran green
condense_memory_content shape-dispatch (arrows, speaker prefix, indent) test_condense_dispatch.py::test_two_unicode_arrows_reach_the_triple_threshold, ::test_two_ascii_arrows_reach_the_triple_threshold, ::test_assistant_prefixed_transcript_routes_to_the_assistant_condenser, ::test_user_prefixed_transcript_naming_the_assistant_routes_to_assistant, ::test_indent_runs_alone_route_to_the_assistant_condenser ran green
condense_memory_content fast-path boundary test_condense_dispatch.py::test_memory_content_at_exact_budget_is_returned_verbatim ran green
condense_assembled_context under/over-budget split (unchanged) pre-existing test_condensers.py (36 tests, unmodified) ran green
Facade re-exports every public name unchanged test_condensers.py + test_pg_recall_assemble_context_condensation.py (3 tests) import through the old path ran green
§4 size caps on all 6 source + 4 test files AST script (below) 0 findings
Mutation adequacy mutmut scoped run 383/377/6, 6 documented equivalent

Pre-push gate output (quoted)

§4 sizes (AST, verbatim script from the task):

$ python3 - <files> <<'PY' ... PY
scan complete: no output above this line means all clear

(0 findings across all 6 source files + 4 new test files.)

ruff:

$ .venv/bin/python -m ruff check .
All checks passed!
$ .venv/bin/python -m ruff format --check mcp_server/core/context_assembly/ tests_py/core/context_assembly/
30 files already formatted

doc claims + badges:

$ .venv/bin/python scripts/check_doc_claims.py
doc claims OK (1 declared not-a-claim exemption(s))
  CONTRIBUTING.md:36: exempt from the tests claim
$ .venv/bin/python scripts/generate_repo_badges.py --check
badges OK (4 checked)

Targeted tests:

$ .venv/bin/python -m pytest tests_py/core/context_assembly/ tests_py/core/test_pg_recall_assemble_context_condensation.py -q
........................................................................ [ 33%]
........................................................................ [ 66%]
........................................................................ [100%]
216 passed in 2.34s

Full suite (post-rebase onto origin/main@2590922):

$ .venv/bin/python -m pytest tests_py/ -q
6725 passed, 5 skipped, 116 subtests passed in 190.29s

Conflict markers / JSON: none; .bestpractices.json parses.

Mutation (scoped, pyproject.toml [tool.mutmut] patched then
restored — no diff on pyproject.toml in this PR):

383 mutants, 377 killed, 6 survived (all documented equivalent)

Full per-file breakdown, per-mutant mutmut show diffs, and the
independent second-run reproduction: docs/audits/condensers-mutation-run-2026-07-30.md.

Boy-scout items fixed in this PR

  • Dead code deleted (condense_assistant_message's unreachable
    code-only fallback), not kept as future-proofing.
  • Stale "late import" docstring claim about assemble_prompt corrected
    (the import was already module-scope).
  • docs/module-inventory.md's core/context_assembly/ file count,
    already stale by one file before this PR (said "9", measured "10"),
    corrected to the current measured 15.

No unfixed, un-issued seen defects remain in touched material.

Closes #228

Co-Authored-By: Claude Opus 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_012Yu6EnWspTfqHoGkExyS6u

…ndensers.py (#228)

A scoped mutmut run over core/context_assembly/condensers.py left 123
survivors outside the #196-changed condense_assembled_context: every
non-wiring condenser (condense_code_block, condense_assistant_message,
condense_memory_content, condense_timeline_event, condense_user_message,
condense_entity_triples) plus the fence-splitting helpers.

The file was also 391 lines (over the 300-line file cap) with two
functions over the 40-line method cap, so the behaviour-preserving split
came first: one file per condenser family (condense_text.py,
condense_code.py, condense_structured.py, condense_dispatch.py,
condense_stage.py) behind an unchanged condensers.py re-export facade.
Verified against the pre-existing 36-test suite (plus the #196
pg_recall wiring tests) passing unmodified before any new test was
added.

Four new test files add exact-equality contract tests (boundary pairs,
accounting ladders, literal rosters, exact routing) mirroring the split.
Re-scoped mutation run: 383 mutants, 377 killed, 6 documented-equivalent
survivors (three <=/< boundary ties resolving to an identical no-op
truncation, two loop-index </<= ties provably unreachable, and the #196
priority 3->4 tie re-confirmed after the split) -- see
docs/audits/condensers-mutation-run-2026-07-30.md.

One genuine dead-code branch surfaced by the run
(condense_assistant_message's trailing code-only fallback, every mutant
of which survived) is deleted per SS9/12.1 rather than kept as
speculative future-proofing; the unreachability proof moves to the use
site and is pinned by two tests. A stale "late import" docstring claim
about assemble_prompt (the import was already module-scope) is corrected
in the same pass.

Closes #228

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Yu6EnWspTfqHoGkExyS6u
@cdeust
cdeust merged commit fc19ffc into main Jul 30, 2026
19 checks passed
@cdeust
cdeust deleted the fix-condensers-mutation-survivors-228 branch July 30, 2026 14:06
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.

condensers.py: 125 pre-existing mutation survivors outside the #196-changed function

1 participant