fix(hooks): make pre-commit anti-pattern detection precise - #24
Merged
Conversation
The hook grepped whole staged files with bare patterns, so it fired on commented-out code, string literals, a domain Result<T>, test fixtures, and EF migrations — and one legacy DateTime.Now made every future edit to that file un-committable. On a fixture set covering both directions it produced 13 findings, 7 of them wrong. A git hook runs in a bare shell where the Roslyn MCP server is unreachable, and shelling out to dotnet build in front of every commit costs seconds and fails on a red build, which is exactly when WIP gets committed. So the hook stays text-based, but now earns it: comments and string literals are stripped with state carried across line boundaries, only the lines a commit adds are checked, and rule IDs, severities, and SourceKind exemptions mirror the Roslyn detectors exactly. Where a pattern cannot be resolved without a semantic model the hook stays silent — tier 1 takes false negatives to guarantee zero false positives, and detect_antipatterns catches the rest. Same fixtures now produce 7 findings, all correct. ADR-006 records the three-tier model and the process for adding a rule. Closes #23 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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 #23.
@jtreher asked why
pre-commit-antipattern.shgreps instead of using a Roslyn analyzer. The premise was right — and the kit already had the better tool sitting in the MCP server.The problem
The hook grepped whole staged files with bare patterns. On a fixture set covering both true and false positives it produced 13 findings, 7 of them wrong:
result.Resulton a domainResult<T>— the very pattern the kit recommendsDateTime.Nowmade every future edit to that file un-committabledotnet builddiagnostics; it did notWhy it stays text-based
A git hook runs in a bare shell —
Program.csis an MCP stdio server with no CLI entry point, sodetect_antipatternsis unreachable. Shelling out todotnet buildper commit costs seconds and fails outright on a red build, which is exactly when WIP gets committed.So there are three tiers, each placed where it can actually run:
pre-commit-antipattern.shdetect_antipatterns(Roslyn MCP)What changed
New
hooks/lib/antipattern-scan.awkcarries the rules; the shell script handles staging, classification, and reporting..Resultreported only when the receiver is visibly task-like;new HttpClient(handler)no longer reported (the Roslyn detector grades it medium-confidence)SourceClassifier// cwm:ignore AP004line suppression andCWM_ANTIPATTERN_WARN_ONLY=1report-only modeDesign rule, recorded in ADR-006: tier 1 accepts false negatives to guarantee zero false positives.
Verification
Fixture repo with production, test, migration, and seeder sources, covering both directions.
The false-positive file — commented-out
DateTime.Now,new HttpClient()inside a log string,@"...DateTime.Now...", a multi-line block comment,result.Resulton a domain type,DateTimeOffset.UtcNow, a raw string literal, and an(object, EventArgs)async void — produces zero findings.Also confirmed: an innocent line added to a file with four legacy violations passes clean;
CWM_ANTIPATTERN_WARN_ONLY=1reports without blocking; LF line endings hold (#12's problem).Docs
hooks/README.md— tier table, hook options, and the grep-vs-analyzer rationale for users who skip ADRs[Unreleased]; README and SPEC references updated🤖 Generated with Claude Code