Skip to content

ci(code-quality): scope the hardcoded-value guard to changed lines (#13950) - #13962

Merged
mrveiss merged 2 commits into
Dev_new_guifrom
issue-13950
Aug 11, 2026
Merged

mrveiss merged 2 commits into
Dev_new_guifrom
issue-13950

Conversation

@mrveiss

@mrveiss mrveiss commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Closes #13950

Thinking Path

Four PRs in one session failed code-quality on violations git blame attributes to other commits:

PR inherited touched an offending line?
#13924 7 no — its diff covered lines ~1078–1120, violations at 199…2758
#13797 3 no — branch had one commit; none of the three blamed SHAs
#13934 3 no
#13945 1 no

The rule is good and all 14 were worth fixing — they are fixed. The cost is that each occurrence looks like a genuine regression until a per-line blame plus a check of the branch's own commit list proves otherwise, and it recurs on every touch of a large legacy module. It also makes avoiding old files the cheapest way to stay green.

The sharper problem: in two of the four, the constant the guard suggested was the wrong concept.

Both hold the same value today, so the mechanical fix the message invites goes green while encoding a different meaning. Advice that is unsafe to follow blind is a reason to give it only where the author is already in the code.

What Changed

--changed-lines-only on the wrapper. It computes the added-line set from git diff -U0, runs the hook unchanged, and partitions its violation blocks by file:line.

Opt-in, not default. Every other hook running through this shared wrapper keeps its behaviour byte for byte; only code-quality's hardcoded-values step passes the flag. Pre-commit and a direct run still scan whole files, so the backlog stays measurable rather than invisible.

Fails closed. If the hook exits non-zero and the filter parses no file:line violations, its output is not one this filter understands — so it prints everything and exits with the hook's status. Suppressing an unparsed failure would turn a real regression green, which is #13880's defect reached from the other direction.

Suppressed violations are still printed under their own heading. The hook's COMMIT BLOCKED trailer is dropped in scoped mode: it counts what the hook found across whole files, not what the wrapper is about to decide, so leaving it would contradict the verdict three lines later.

Verification

pipeline-scripts/check-pre-commit-hook-pr_test.py .... 22 passed

Four new tests, the second being the one that matters — this is a scope change, not an off switch:

test asserts
test_a_pre_existing_violation_in_a_touched_file_does_not_fail the reported symptom is gone, and the violation is reported, not hidden
test_a_violation_the_pr_adds_still_fails a newly added violation still fails
test_a_clean_pr_stays_clean no false positives
test_the_default_mode_is_unchanged the blast radius stays at one step

3 mutations applied, 3 killed: filter suppressing everything, filter keeping everything, and the default silently gaining scoping.

One thing the first version got wrong, worth recording: it counted the hook's banner and trailer as violation blocks and reported "6 violation(s) on lines this PR added" for a PR that added one clean line — a summary contradicting the correct suppression printed directly above it. Blocks now only count when they carry a VIOLATION header.

Unblocks

#13946 is currently red on 33 inherited violations in code_sync.py, most of them the /opt/autobot/ literals tracked by #13149. Fixing those inline would mean doing #13149 inside an unrelated PR, against paths that control where deploys write.

Model Used

claude-opus-5[1m]

…13950)

The guard reads each changed FILE whole, so editing one line of a legacy module
makes a PR answerable for that file's entire backlog. Four PRs in one session
failed on violations git blame attributes to other commits: #13924 (7), #13797
(3), #13934 (3), #13945 (1). None touched an offending line.

The rule is good and all 14 were worth fixing — they are fixed. The cost is
that each occurrence looks like a genuine regression until a per-line blame
plus a check of the branch's own commit list proves otherwise, and it recurs on
every touch of a large legacy module. It also makes avoiding old files the
cheapest way to stay green.

Worse, in two of the four the constant the message SUGGESTED was the wrong
concept: #13934's 'user_role' is an RBAC input, not the chat-vocabulary
CategoryDefaults.ROLE_USER, and #13945's le=100 is a ceiling, not
KNOWLEDGE_DEFAULT_LIMIT. Both hold the same value today, so the mechanical fix
the message invites goes green while encoding a different meaning. Advice that
is unsafe to follow is a reason to only give it where the author is in the code.

Opt-in via --changed-lines-only rather than a default, so every other hook that
runs through this wrapper keeps its behaviour byte for byte. Only code-quality's
hardcoded-values step passes it; pre-commit and a direct run still scan whole
files, so the backlog stays measurable rather than invisible.

Fails closed. If the hook exits non-zero and the filter parses no 'file:line'
violations, the output is not one it understands, so it prints everything and
exits with the hook's status — suppressing an unparsed failure would turn a real
regression green, which is #13880's defect reached from the other direction.

Suppressed violations are still printed under their own heading; the hook's own
'COMMIT BLOCKED' trailer is dropped in scoped mode because it counts what the
hook found across whole files, not what this wrapper is about to decide.

3 mutations killed: filter suppressing everything (guard becomes an off switch),
filter keeping everything (no scoping), and the default silently gaining
scoping. A first version also counted the hook's banner and trailer as kept and
reported '6 violations on lines this PR added' for a PR that added one clean
line — the summary contradicting the correct suppression directly above it.
@github-actions

Copy link
Copy Markdown
Contributor

✅ SSOT Configuration Compliance: Passing

🎉 No hardcoded values detected that have SSOT config equivalents!

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