Repository navigation
ci(code-quality): scope the hardcoded-value guard to changed lines (#13950) - #13962
Merged
Merged
Conversation
…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.
Contributor
✅ SSOT Configuration Compliance: Passing🎉 No hardcoded values detected that have SSOT config equivalents! |
4 tasks
This was referenced Aug 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #13950
Thinking Path
Four PRs in one session failed
code-qualityon violationsgit blameattributes to other commits: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.
"user_role": "user"is an RBAC input thatworker_nodereads in_validate_user_role; the guard suggestedCategoryDefaults.ROLE_USER, the chat vocabulary.le=100is a ceiling; the guard suggestedKNOWLEDGE_DEFAULT_LIMIT, a default.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-onlyon the wrapper. It computes the added-line set fromgit diff -U0, runs the hook unchanged, and partitions its violation blocks byfile: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:lineviolations, 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 BLOCKEDtrailer 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
Four new tests, the second being the one that matters — this is a scope change, not an off switch:
test_a_pre_existing_violation_in_a_touched_file_does_not_failtest_a_violation_the_pr_adds_still_failstest_a_clean_pr_stays_cleantest_the_default_mode_is_unchanged3 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
VIOLATIONheader.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]