Files
hermes-agent/scripts/code_health/model.py
teknium1 4f70e88c0e fix(code-health): one-to-one move matching, real deadlines, index-exact hook
Second-round review on #132646 (F1-F10), each reproduced on 2531fb2 first:

- Moves are matched one-to-one: unchanged units are reserved first, then
  same-file body, then cross-file body only when the origin's name is gone,
  then same name. A copy of unchanged debt is new debt; a function moved
  over a same-name stub keeps its own cap (F1, F3).
- Every base hit is owned by exactly one head scope, so a file rename no
  longer counts each old hit twice (F2).
- Ruff runs when either side has Python, so a delete-only .py diff with a
  TS edit no longer crashes on a missing ruff result (F4).
- Deadlines must be finite: timeout=None, **{} and asyncio.timeout(None)
  are no deadline; awaited calls inside `async with asyncio.timeout(x)`
  are bounded (F5, F8).
- HX005 walks only what runs at import: the import-time branch of a
  `__name__` guard (either operator), loops, match, try*, decorators,
  class bases; a lambda value is deferred (F9).
- `health: allow` counts only in real comment tokens (Python tokenize, TS
  comment trivia), never in string literals (F7).
- TS function lines are owned by the innermost function, so siblings on
  one line give it up once (F10).
- The pre-commit hook runs `scripts/check --staged`: health compares the
  index tree, and when the working tree differs from the index the other
  checkers run on a checkout-index copy with a copied index, so commits are
  judged on the staged bytes and unstaged work is never touched (F6).
2026-10-04 21:29:39 -07:00

66 lines
1.9 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)
@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)
def add_hit(self, rule: str, scope: str, line_no: int) -> None:
text = " ".join(self.source_line(line_no).split())
hit = Hit(rule, scope, text)
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