Skip to content

feat(dead): Python earns the dead verdict via pythonVoice#124

Merged
luuuc merged 5 commits into
mainfrom
feat/dead-code-python-voice
Jun 2, 2026
Merged

feat(dead): Python earns the dead verdict via pythonVoice#124
luuuc merged 5 commits into
mainfrom
feat/dead-code-python-voice

Conversation

@luuuc
Copy link
Copy Markdown
Owner

@luuuc luuuc commented Jun 2, 2026

Problem

Every Python symbol with no resolved caller was reported as core_no_language_voice — a single, uninformative "Sense can't reason about this stack" bucket. Python is a top-tier language for Sense, and an agent deserves precise, actionable reasons (and a sound dead verdict where one is provable) the way Ruby, Go, Rust, and TS/JS already get.

Summary

Python is now the fifth language whose symbols can earn the dead verdict. An underscore-visibility pass plus a per-language harvest feed a new pythonVoice that earns dead only for an underscore-private function/method with no caller, no mention, and no invisible-reach idiom — and raises precise hand-raise reasons for everything else.

Changes

  • extract: Symbol.Visibility populated for Python (leading-underscore non-dunder → private); a harvest streams the mention set, the getattr/setattr/hasattr dispatch set, the decorator/route/Django reach sets, and the __all__ export set via a new PythonHarvestEmitter.
  • scan: the four Python reach sets are aggregated and persisted to flat sense_meta keys (py_decorated, py_routes, py_django, py_all_exports).
  • dead: pythonVoice registered in the arbiter with reasons py_dunder, py_route, py_django, py_decorator, py_all_export, py_public, py_class, py_constant. Only underscore-private functions/methods fall through to dead.
  • tests: a smoke fixture proving the two-sided gate end to end, a hand-labeled eval corpus with a 100%-precision binding gate, and a planted-false-dead self-test on a live signal receiver.

Architecture Highlights

  • Mirrors the Ruby decision: every public (non-underscore) symbol stays possibly_dead (duck-typed dispatch), so the eligible-for-dead set is small and intentionally private. The arbiter's broad mention gate is the soundness backstop — the dead tier never rests on the underscore alone.
  • Dunders are matched by the __x__ pattern rather than a fixed table, so the whole protocol surface (incl. PEP 562 module __getattr__, dataclass __post_init__) is covered without a table to maintain.
  • Framework reach sets are project-wide and name-based — sound over-approximation, identical to the existing dispatch / TS-decorator sets.

Test Plan

  • go test ./... passes
  • sense binary builds cleanly
  • Synthetic corpus: dead-precision == 100%, zero false dead; planted-false-dead self-test fails the gate as expected
  • Smoke golden pins the two-sided gate (one earned dead; py_dunder / py_decorator / py_route / core_name_mentioned hand-raises)
  • flask benchmark before/after: 0 → 1 dead (verified genuinely unused), zero false dead

luuuc added 5 commits June 2, 2026 14:23
Populate Symbol.Visibility for Python (leading-underscore non-dunder name
-> private, else public) and harvest the facts the dead-code voice reads:
the broad mention set (shared walker), the getattr/setattr/hasattr dispatch
set, the decorator / route / Django reach sets, and the __all__ export set.

- visibility.go: the underscore convention, the only structural "not public
  API" signal Python offers, applied to every emitted symbol.
- harvest.go: a PythonHarvestEmitter streaming py_decorated / py_routes /
  py_django / py_all_exports, plus mentions and reflective dispatch, all from
  one set of tree walks. Route/Django are classified by decorator last-name.
- Python opts into MentionHarvester so the per-language soundness gate treats
  it as harvested.

Golden fixtures gain the visibility field (additive only).
Aggregate the Python decorator / route / Django / __all__ reach sets streamed
by the extractor and write them to flat sense_meta keys (py_decorated,
py_routes, py_django, py_all_exports), alongside the existing per-language
mention and dispatch sets. The dead-code Python voice reads these to keep a
decorated / routed / Django-dispatched / declared-public symbol open-world.

Updates the harvested-langs control test: Python now harvests mentions, so the
non-harvesting negative control moves to a generic-spec Java file.
Register the pythonVoice in the arbiter: Python is the fifth language whose
symbols can earn `dead`. Only an underscore-private function or method with no
caller, no mention, and no invisible-reach idiom falls through to `dead`,
mirroring the Ruby rule. Everything else raises a hand with an exact reason:

- py_dunder (a __x__ protocol method, matched by pattern so module __getattr__
  and dataclass __post_init__ are covered)
- py_route / py_django (framework-dispatched handlers)
- py_decorator (any other decorated symbol)
- py_all_export (a name in __all__, overriding the underscore convention)
- py_public (duck-typed reach), py_class / py_constant (never earn `dead`)

The smoke fixture gains payments.py proving the two-sided gate end to end: one
earned `dead`, plus py_dunder / py_decorator / py_route hand-raises and the
core_name_mentioned soundness backstop. Golden regenerated.
Add the Python slice of the dead-code trust corpus: an underscore-private
earned `dead`, a Django @receiver signal handler, a dunder protocol method, a
model class, and an __all__ export. The binding precision gate asserts
dead-precision == 100% with zero false `dead` through scan + the live arbiter,
and a planted-false-dead self-test on the live signal receiver proves the gate
measures rather than rubber-stamps.
Record the Python dead-code voice under Unreleased: underscore visibility, the
py_* reason vocabulary, the harvest seam, and the binding gate (100% precision
on the synthetic corpus plus a verified zero-false-dead run on the flask
benchmark). Flags the Django gap honestly: no Django repo in the suite.
@luuuc luuuc self-assigned this Jun 2, 2026
@luuuc luuuc merged commit a2a9258 into main Jun 2, 2026
5 checks passed
@luuuc luuuc deleted the feat/dead-code-python-voice branch June 2, 2026 14:33
@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 2, 2026

Codecov Report

❌ Patch coverage is 96.00000% with 14 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/extract/python/harvest.go 87.61% 7 Missing and 7 partials ⚠️

📢 Thoughts on this report? Let us know!

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