Skip to content

Triage the 10 full-run kimera survivors: delete dead code, ignore one equivalent - #7

Merged
giacope merged 7 commits into
mainfrom
claude/laughing-pascal-3vuexa
Sep 26, 2026
Merged

giacope merged 7 commits into
mainfrom
claude/laughing-pascal-3vuexa

Conversation

@giacope

@giacope giacope commented Sep 26, 2026 •

Copy link
Copy Markdown
Owner

A full kimera run on main fails max_survivors: 0 with 10 survivors. They're the same 10 under kimera 0.1.1 and 0.1.2, and all 10 also survive --isolated. None of them is a bug: in each case no test can observe the mutation. This PR takes only the verdicts that belong in hashira.

Net diff against main: one dead call deleted and one ignore entry. An earlier version of this PR rewrote production code so kimera would stop generating some of these mutants. The branch keeps those commits, and a revert commit backs them out (history rewrites are blocked in this session).

Changes

  • Dead code: CognitiveScore#nesting called walked, but its only callers (IfChain, RescueScan) read it mid-walk, after the walk has begun, so the call never did anything. It's deleted.
  • Equivalent mutant, one ignore entry: in Echo, [...] unless words.empty? → unless false. RollCall keeps only word sets that at least 3 files share, so a file with no words never joins a roll. The filter is a real optimization: without it, the coupling pass takes 2.7–3.3s instead of 2.1s on a 2,600-file Rails app, with identical output. Every form of the filter keeps a "keep every file" mutant. So it gets an ignore entry with the mechanism as its reason:, and max_ignored goes from 0 to 1 in the same diff, following kimera skill's rules.

Left for other changes

  • Nine guard-style memo mutants: 7 in boundaries.rb, smells/branches.rb, smells/refs.rb, coupling/roster.rb and smells/ownership.rb, and 2 on CognitiveScore's return if @_walked. That guard had been killed only because nesting re-entered the walk; with the dead call gone, it survives like the rest.
    • They're all return @_x if @_x, or return if @_done; @_done = true, with the guard's condition forced false or the flag set to false. Removing a memo only recomputes an equal result.
    • kimera doesn't mutate @x ||= memos but does mutate this equivalent form. That's being fixed in kimera. Once hashira picks up that release, these nine drop out with no hashira change.
  • Clusters prefilter boundary: fragment.mass >= PREFILTER → > (12). A fragment under 16 tokens can reach a report only through a chain of several near-misses. That's a design question about the prefilter, not a test gap, so it stays visible until decided separately.

Verification

  • bin/ci passes both with LANG=C.UTF-8 and with LANG unset: rubocop, rubocop-kata doctor, 502 examples, the gate, and Ratchet OK: 16 edges, 0 findings, unchanged.
  • kimera ci --since origin/main exits 0.
  • kimera on echo.rb with the entry: 0 survivors, 1 ignored.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PcDfzLuRgjoSnfPLrTaA4h

Five analyzers memoized with a guard (`return @_x if @_x`, or a
`@_done = true` flag) because their builders filled instance variables
as a side effect. kimera mutated each guard away, and all 7 mutants
survived, including in isolation: recomputing from the same trees
yields an equal result, so only the repeated work differed.

The builders now return what they build (Branches#chart, Refs#holder,
Ownership#chart, Roster#registered and #counted), and each view is an
`@_x ||=` memo like the other 93 in lib. kimera treats `||=` as
memoization and judges what it computes instead.

`hashira --json --top 100000` output is byte-identical before and after
on kimera, hashira, rubocop-kata and a 2,600-file Rails app. kimera on
the five files: 115 mutants, 115 killed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PcDfzLuRgjoSnfPLrTaA4h
Deleting `walked` from CognitiveScore#nesting survived, including in
isolation: nesting is read only by IfChain and RescueScan mid-walk, when
the walk has already begun, so the call never did anything.

With it gone, the `return if @_walked` guard showed the same memo
survivors as the analyzers in the previous commit. Its mutants had been
killed only because re-entering the walk from nesting recursed until
the stack overflowed. The walk now returns its increments and calls,
memoized with ||=. Nothing reads them mid-walk, so nothing re-enters.

kimera on the file: 20 mutants, 20 killed. `hashira --json` output is
unchanged on the four codebases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PcDfzLuRgjoSnfPLrTaA4h
`fragment.mass >= PREFILTER` -> `>` survived, including in isolation:
nothing observable separates a 12-token fragment from a 13-token one.
A fragment under BASE_MASS (16) never fits a uniform cluster, and a
thin one needs a canonical of about 42+ tokens, which a near-miss (at
most 1.5x apart) reaches from a 12-token fragment only through a chain
of several near-misses.

Clusters now prefilters at BASE_MASS, and the existing "exact clone
whose mass sits exactly on the floor" spec pins the boundary. Clusters
are identical with the prefilter at 12 or 16 on kimera, hashira,
rubocop-kata and a 2,600-file Rails app (221 clusters), and the
duplication pass takes 6.9s instead of 8.9s on the latter.

kimera on the file: 27 mutants, 27 killed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PcDfzLuRgjoSnfPLrTaA4h
`[...] unless words.empty?` -> `unless false` survives in isolation and
is equivalent: RollCall keeps only word sets that at least 3 files
share, so a file with no words never joins a roll. The filter is a real
optimization, not dead code. Without it the coupling pass takes 2.7-3.3s
instead of 2.1s on a 2,600-file app, with identical output. Any form of
the filter keeps a "keep every file" mutant, so it gets an ignore entry
naming the mechanism, and max_ignored rises to 1 with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PcDfzLuRgjoSnfPLrTaA4h
hashira's own ratchet flagged the new Roster#counted as feature_envy
(two references to `definition`, one to self). Filtering and
de-duplicating are now one step each.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PcDfzLuRgjoSnfPLrTaA4h
These changed production code only so kimera would stop reporting
survivors, and none of them fixed a bug:

- The ||= rewrites of seven guard-style memos (and CognitiveScore's walk)
  are behavior-identical; kimera just doesn't mutate `@x ||=`. Their
  mutants are equivalent (recomputing yields an equal result), and the
  gap is kimera not recognizing guard-style memoization, to be fixed
  there instead.
- Prefiltering at BASE_MASS instead of 12 is a design decision about
  duplication, not a test gap, so it's left for its own change.

This reverts 3ea6da1, 21bf246, 4abf47f and 4c0ec7d.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PcDfzLuRgjoSnfPLrTaA4h
…esting

Deleting it survived, including in isolation: nesting is read only by
IfChain and RescueScan mid-walk, after the walk has begun, so the call
never did anything.

It was also the only reason the `return if @_walked` guard's mutants
were killed: without the guard, nesting re-entered the walk and
recursed until the stack overflowed. Those two mutants now survive like
the other guard-style memos, which is kimera's gap to close, not
hashira's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PcDfzLuRgjoSnfPLrTaA4h
@giacope giacope changed the title Clear the 10 mutants that survived a full kimera run Triage the 10 full-run kimera survivors: delete dead code, ignore one equivalent Sep 26, 2026
@giacope
giacope merged commit ee17738 into main Sep 26, 2026
4 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.

2 participants