Files
hermes-agent/scripts/code_health/model.py
Teknium e5a6b1f746 fix(code-health): pair repeated names by body; keep the cap of a function moved and edited
Why:
- banditburai (inline, py_structure.py:97) and unsupportedpastels M3: repeated names (Python
  `@method(...) def _`, `f#2`; TS `<anon>#n`) were paired by source-order ordinal, so a new
  CC-22 `_` inserted above a trimmed one took its cap (exit 0), and inserting a small `_`
  above an unchanged CC-22 one made the old one "new" (exit 1). Same-file name matching now
  groups units by name without the ordinal and pairs the most similar bodies first
  (difflib ratio >= 0.6 over code lines, comments/whitespace dropped); the last unit left
  with a name is that name's unit, exactly as a unique name was before. Exact-hash passes
  still run first. If a body span is unknown, the group falls back to the ordinal.
- unsupportedpastels M2: cross-file matching was body-hash only, so a function moved to
  another file with one constant changed lost its cap (P1 exit 1; same edit in place exit 0).
  New last pass: another file, same name, similar body (>= 0.6), only when the name is gone
  from its origin file and exactly one unit of that name left and exactly one arrived
  unmatched. A copy (origin keeps it), two arrivals, or an unrelated body reusing a deleted
  name stay new code (andrexibiza: never arbitrary same-name credit). Its existing hits
  follow it through the departed-occurrence pool (one-to-one), a second swallow is new.
Unit gains an optional end_line; until the measurers report it (py_structure: lane A,
measure.py: lane C) the span is estimated from the unit's own FUNC_LINES. Tests pass both ways.
2026-10-04 21:29:39 -07:00

81 lines
2.7 KiB
Python

"""Data shapes shared by the measurers and the comparison."""
from __future__ import annotations
from collections import Counter
from dataclasses import dataclass, field
MODULE_SCOPE = "<module>"
@dataclass
class Unit:
"""One function (Python def, TS function/method/arrow) and its metrics."""
qualname: str
line: int
metrics: dict[str, int]
body_hash: str
parent: str | None = None # nearest enclosing function (nested defs are their own units)
# Last line of the unit, for comparing bodies of units that are not byte-identical.
# None when the measurer does not report it (the comparison then estimates the span).
end_line: int | None = None
@dataclass(frozen=True)
class Hit:
"""A pattern-rule fingerprint: never a line number, so edits above don't move it."""
rule: str
scope: str
text: str
@dataclass
class FileMeasure:
path: str
metrics: dict[str, int] = field(default_factory=dict)
units: dict[str, Unit] = field(default_factory=dict)
hits: Counter[Hit] = field(default_factory=Counter)
# Line numbers of each hit occurrence, for reporting only.
hit_lines: dict[Hit, list[int]] = field(default_factory=dict)
# 1-based line -> source text, for messages.
lines: list[str] = field(default_factory=list)
# 1-based line -> text of the real comment tokens on it (allow directives live only here,
# so a string literal that happens to say "health: allow" waives nothing).
comments: dict[int, str] = field(default_factory=dict)
# Why this file could not be measured (unparseable, measurer failure): a measurement gap
# must fail the check, never read as "no findings".
error: str | None = None
def code_line(self, line_no: int) -> str:
"""The line's code with its comment removed and whitespace normalised: the identity of
a violation, so editing or dropping a comment (an allow included) never makes old debt
look new."""
line = self.source_line(line_no)
comment = self.comments.get(line_no)
if comment and comment in line:
line = line[: line.rindex(comment)]
return " ".join(line.split())
def add_hit(self, rule: str, scope: str, line_no: int) -> None:
hit = Hit(rule, scope, self.code_line(line_no))
self.hits[hit] += 1
self.hit_lines.setdefault(hit, []).append(line_no)
def source_line(self, line_no: int) -> str:
if 1 <= line_no <= len(self.lines):
return self.lines[line_no - 1]
return ""
@dataclass
class Finding:
path: str
rule: str
scope: str
line: int
detail: str
blocking: bool = True
allowed_reason: str | None = None