fix(gate): close the gaps in the plan-code guard, the button lists and the tooling

- planCodes: the quote and slash lookarounds let the commonest form through (a docstring or test
  title opening with a code, a slash pair); the two real exceptions (an episode titled "E1", ruff's
  E4/E7/E9) are now named and cut first, with cases that fail on the old pattern. audit_metrics
  counts by the same definition.
- button-primitive: both file lists live in the rule body, out of sight of the snapshot and the
  register; each is now pinned to its exact set of ui/ files (red when either is widened).
- check.sh --brief shows a short log whole and keeps the line before each hit, so ruff's rule and
  eslint's file path survive the excerpt.
- lint-changed runs prettier over every changed frontend file, as the gate does, not only code.
- CLAUDE.md and gates.md no longer claim the basedpyright baseline was found harmless: it was never
  triaged, and the reportOptional* entries are the likeliest real bugs.
This commit is contained in:
2026-10-05 00:19:50 +02:00
parent eecc195b7e
commit d1bbe4524c
10 changed files with 137 additions and 33 deletions
+10 -3
View File
@@ -28,9 +28,11 @@ BE_COMPLEXITY = 10 # ruff's C901 default
FE_COMPLEXITY = 20 # the frontend's line
# what planCodes.contract.test.ts holds the code to: an epic (`E28`, `E9.5`) or a roadmap item (`R14`),
# not `E2E`, not a ruff rule family (`E4/E7/E9`), not a TV episode (`S2 · E5`, an episode titled "E1")
EPIC_CODE = re.compile(r'(?<![/"])\b(E\d{1,2}(\.\d+)?|R\d{1,2})\b(?![/"E])')
# not `E2E`, not a TV episode (`S2 · E5`); NOT_CODES are its named exceptions, cut first (an episode
# titled "E1", ruff's default `E4/E7/E9`)
EPIC_CODE = re.compile(r"\b(E\d{1,2}(\.\d+)?|R\d{1,2})\b(?!E)")
EPISODE = re.compile(r"S\d{1,2}\s?·?\s?E\d+")
NOT_CODES = re.compile(r'"E\d{1,2}"|\bE4/E7/E9\b')
NARRATION = re.compile(r"\b(used to|no longer)\b", re.IGNORECASE)
@@ -114,7 +116,12 @@ def _complexity() -> dict[str, object]:
def _noise(be: list[tuple[Path, list[str]]], fe: list[tuple[Path, list[str]]]) -> dict[str, object]:
every = be + fe
epic = [(p, ln) for p, ls in every for ln in ls if EPIC_CODE.search(EPISODE.sub("", ln))]
epic = [
(p, ln)
for p, ls in every
for ln in ls
if EPIC_CODE.search(NOT_CODES.sub("", EPISODE.sub("", ln)))
]
be_c = sum(_comment_lines(ls, True) for _, ls in be)
fe_c = sum(_comment_lines(ls, False) for _, ls in fe)
return {
+16 -9
View File
@@ -48,16 +48,23 @@ lane() { # lane <name> <command…> — run it, remember a failure, never stop
fi
rm -f "$out"
}
# The lines that say what broke: the first 20 error-looking ones, each with the two lines after it
# (knip names the file on the line after "Unused files"); a log with none of them (a tool that
# words its failures some other way) shows its tail instead, which is where a summary sits. The test
# database's own log is left out: the DB lane provokes constraint errors on purpose, and its
# `ERROR:` lines filled the excerpt ahead of the failing test.
# What broke, in a log's own words. A short log is shown whole: a keyword filter keeps ruff's
# summary line and drops the rule and file above it, and eslint's file path sits on a line of its
# own above its errors. A long one shows its first 20 error-looking lines, each with the line before
# it and the two after (knip names the file on the line after "Unused files"); a long log with none
# of them shows its tail, which is where a summary sits. The test database's own log is left out:
# the DB lane provokes constraint errors on purpose, and its `ERROR:` lines filled the excerpt ahead
# of the failing test.
brief_excerpt() {
local hits
hits="$(grep -v -E '^testdb-[0-9]+ ' "$1" |
grep -E -i -A 2 -m 20 'error|fail|✗|×|warn|would reformat|unused|vulnerab|found [0-9]')"
if [ -n "$hits" ]; then printf '%s\n' "$hits"; else tail -n 20 "$1"; fi
local log hits
log="$(grep -v -E '^testdb-[0-9]+ ' "$1")"
if [ "$(printf '%s\n' "$log" | wc -l)" -le 60 ]; then
printf '%s\n' "$log"
return
fi
hits="$(printf '%s\n' "$log" |
grep -E -i -B 1 -A 2 -m 20 'error|fail|✗|×|warn|would reformat|unused|vulnerab|found [0-9]')"
if [ -n "$hits" ]; then printf '%s\n' "$hits"; else printf '%s\n' "$log" | tail -n 20; fi
}
# --- static lanes ------------------------------------------------------------------------------------
+16 -6
View File
@@ -1,7 +1,8 @@
#!/usr/bin/env bash
# The fast lint lanes on the uncommitted files only — ruff (check + format) on Python, eslint +
# prettier on the frontend — for a check after every edit instead of a full gate run. It is a
# convenience, never the gate: scripts/check.sh stays what a merge or a release requires.
# The fast lint lanes on the uncommitted files only — ruff (check + format) on Python, eslint on the
# frontend's code and prettier on all of the frontend — for a check after every edit instead of a
# full gate run. It is a convenience, never the gate: scripts/check.sh stays what a merge or a
# release requires.
#
# bash scripts/lint-changed.sh lint what `git status` shows as changed or new
# bash scripts/lint-changed.sh --hook the same, as a Claude Code Stop hook: silent and exit 0 when
@@ -23,12 +24,19 @@ fi
mapfile -t changed < <(git status --porcelain --untracked-files=all | sed -E 's/^.. //; s/.* -> //' |
while read -r f; do [ -f "$f" ] && printf '%s\n' "$f"; done)
py=() fe=()
# eslint reads code; prettier reads every file the gate's `prettier --check .` does (a locale JSON,
# a stylesheet), and `--ignore-unknown` passes over the ones it has no parser for.
py=() fe=() pretty=()
for f in "${changed[@]}"; do
case "$f" in
backend/*.py | scripts/*.py) py+=("$f") ;;
esac
case "$f" in
frontend/*.ts | frontend/*.tsx | frontend/*.js | frontend/*.mjs) fe+=("${f#frontend/}") ;;
esac
case "$f" in
frontend/*) pretty+=("${f#frontend/}") ;;
esac
done
report="$(
@@ -38,12 +46,14 @@ report="$(
fi
if [ ${#fe[@]} -gt 0 ]; then
(cd frontend && npx eslint --max-warnings 0 "${fe[@]}" 2>&1)
(cd frontend && npx prettier --check --log-level warn "${fe[@]}" 2>&1)
fi
if [ ${#pretty[@]} -gt 0 ]; then
(cd frontend && npx prettier --check --ignore-unknown --log-level warn "${pretty[@]}" 2>&1)
fi
)"
if [ -z "$report" ]; then
[ "$hook" = 1 ] || echo "lint-changed: clean (${#py[@]} py, ${#fe[@]} frontend)"
[ "$hook" = 1 ] || echo "lint-changed: clean (${#py[@]} py, ${#pretty[@]} frontend)"
exit 0
fi
if [ "$hook" = 1 ]; then