Commit Graph

7 Commits

Author SHA1 Message Date
Teknium
7ceedd731a style(code-health): wrap the long lines added by the lane-C fixes
No behaviour change: line wrapping in the hook text, parse_switch docstring,
commit_or_none and the new tests; the test helpers lose an unused env parameter.
2026-10-04 21:29:39 -07:00
Teknium
a6274687fd fix(code-health): parse ENFORCEMENT with ast and read it from the main tip locally
unsupportedpastels m3: (a) the switch was found by a regex that accepted only the
exact `ENFORCEMENT = "..."` spelling, so `ENFORCEMENT: str = "off"` silently stayed
blocking; (b) local runs and both hooks read it from the merge-base with
origin/main, so a flip on main reached a branch only after a rebase, contrary to
"CI and local hooks alike".

(a) parse_switch() reads the module-level plain or annotated assignment with ast
(last one wins, as at runtime); missing, unparseable or unknown values are
blocking. (b) Without --base, the base is still the merge-base of the head (a
--head commit, else HEAD) but the switch comes from origin/main when it resolves,
falling back to the base. An explicit --base pins both (CI passes HEAD^1, the
target tip). scripts/check --commit now hands the commit itself to the ratchet so
it derives that commit's merge-base. The head under test never supplies the switch.
(c) A test pins enforcement() on this tree to config.ENFORCEMENT.
2026-10-04 21:29:39 -07:00
Teknium
f970c226f3 fix(code-health): a failing measurement tool exits 2, not with a traceback
unsupportedpastels m5: when the pinned TypeScript is not cached, ts_measure runs
`npm install`; a failed or timed-out install raised CalledProcessError /
TimeoutExpired, which cli.main did not catch (it caught RuntimeError only). The run
then died with a traceback and exit 1, which reads like a finding.

cli.main now reports those as a broken run: one `code health: measurement tooling
failed: ...` line on stderr and exit 2, like every other environment error.
2026-10-04 21:29:39 -07:00
Teknium
15274dfb41 fix(code-health): --json prints JSON on every return path
JoaoMarcos44 (inline cli.py:64): the ENFORCEMENT="off" and "no measured files
changed" branches returned before the --json formatter, so
`python -m scripts.code_health --base HEAD --head HEAD --json` succeeded with prose
on stdout and json.loads() raised.

Both early successes now print the empty findings list `[]` on stdout and move
the human explanation to stderr; findings and zero-finding measured diffs were
already JSON. Exit codes are unchanged on all four paths.
2026-10-04 21:29:39 -07:00
teknium1
6f0d25c1a1 feat(code-health): ENFORCEMENT switch and a pre-push hook
- `ENFORCEMENT` in scripts/code_health/config.py (blocking / advisory / off)
  turns the ratchet down or off with a one-line commit to main. It is read
  from the BASE revision, so it applies to fork PRs (repo variables are not
  passed to fork-triggered workflows), CI and local hooks alike, and a PR
  cannot relax its own check.
- `scripts/check --install-hook pre-push` judges each pushed commit the way
  CI judges the PR (base = that commit's merge-base); `--commit REV` is the
  underlying mode, `--uninstall-hook` removes our hooks and leaves foreign
  ones. Pre-commit and pre-push share one tree snapshot that never touches
  the user's worktree or index.
- A failing report names the local commands; CONTRIBUTING, AGENTS.md and the
  PR template point at `python scripts/check`.
2026-10-04 21:29:39 -07:00
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
teknium1
4156715960 feat: code-health ratchet with per-unit caps for Python and TypeScript
scripts/code_health measures the files a change touches on both sides
(base revision and head) and fails when:
- a function or file over target grows past its own value on the base
  (CC 20, 300 own lines, nesting 6, file 2,000 lines);
- a new function or file is over target;
- a pattern rule gains a fingerprint (rule + enclosing function + line
  text), so fixing one violation never buys another.

Code that moves keeps its cap: functions match by name-independent body
hash, pattern hits by (rule, line text) once their function has left the
old place. Nested functions are their own units, so a closure edit never
counts against the hook or function around it. Python CC is ruff's C901
minus the direct nested defs; TypeScript is parsed with the lockfile's
typescript (no type check).

Rules: swallowed exceptions (S110, BLE001, E722), correctness (F821/F823/
F811/F841, RUF006), hangs (ASYNC230/240, S113, subprocess/urlopen without
timeout, sync config I/O in async def, get_event_loop, gather results
checked against Exception), profile scope (hardcoded ~/.hermes, a new
HERMES_* env var, UnscopedSecretError -> os.getenv, import-time capture,
raw threading.Thread, P05/P06 from profile_scope_patterns.json), argv
process identity, bool() of an env string, and if/elif ladders.

Growth of a file already over 2,000 lines is advisory: replaying 300
merged PRs, a blocking file-size ratchet would have stopped 46 of them
before the 119 over-target files are split. Everything else blocks; the
same replay finds 18 PRs that would have needed a change, every finding
a real hit for its rule.

No baseline file: base is measured on the fly, so an improvement that
lands on main is the new cap at once and parallel PRs never conflict on
ratchet state. `# health: allow <RULE> -- <why>` is the reviewed escape.
2026-10-04 21:29:39 -07:00