Skip to content

fix(rules): stop false-positive counterfeiter and Parse*Default findings - #115

Merged
bborbe merged 2 commits into
masterfrom
fix/pr-reviewer-false-positives
Aug 19, 2026
Merged

fix(rules): stop false-positive counterfeiter and Parse*Default findings#115
bborbe merged 2 commits into
masterfrom
fix/pr-reviewer-false-positives

Conversation

@bborbe

Copy link
Copy Markdown
Owner

What

Fixes two mechanical ast-grep rules that fire on compliant code, the root cause of the github-pr-reviewer re-reporting present code as missing across three review rounds on bborbe/github-update-go-watcher PR #1.

Changes

  1. counterfeiter-directive-on-interface — the old rule used not.follows: {kind: comment, regex: counterfeiter:generate}, but ast-grep's follows relation only reaches the immediate previous sibling. Real Go layout is directive → doc comment → interface, so the doc comment masked the directive and the rule fired on every compliant interface. Converted to the over-inclusive + adjudicate design used by sibling rules: the agent verifies the comment block above the interface before reporting. Added rule-test.
  2. new-prefix-constructor-naming — fired on Parse<X>Default functions, directly contradicting paired-parse-and-parsedefault which mandates that exact name. Added Parse[A-Z].*Default exemption. Added rule-test.
  3. Docs + rules/index.json regenerated.

Verification

  • ast-grep test -c sgconfig.yml — 46 passed, 0 failed (new tests for both rules)
  • make precommit — pass
  • Manual scan of the reviewed SHA (f5abe33) confirms the old rule flagged compliant interfaces; the new rule anchors them as candidates for adjudication.

Pair with bborbe/github-pr-review-agent# (prompt-side adjudication fix).

github-pr-reviewer re-reported present code as missing across three
review rounds on bborbe/github-update-go-watcher PR #1. Root cause: two
mechanical rules firing on compliant code.
- counterfeiter-directive-on-interface: not.follows only sees the
immediate previous sibling, so a doc comment between the directive and
the interface masked the directive and fired on every compliant
interface. Converted to the over-inclusive + adjudicate design used by
its sibling rules (counterfeiter-mocks-required, main-test-with-
compiles, external-call-logs-response): the agent verifies the comment
block above the interface before reporting.
- new-prefix-constructor-naming: fired on Parse<X>Default functions,
contradicting paired-parse-and-parsedefault which mandates that exact
name. Added a Parse[A-Z].*Default exemption.
Both rules get ast-grep rule-tests; index and docs regenerated.

@ben-s-pull-request-reviewerben-s-pull-request-reviewerBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No cleanup needed — already at PR head. Here is my consolidated report:


PR Review: fix(rules): stop false-positive counterfeiter and Parse*Default findings

Must Fix (Critical)

None.

Should Fix (Important)

  • rules/index.json — The entry for go-architecture/counterfeiter-directive-on-interface still carries "enforcement_type": "mechanical". The rule's new design is over-inclusive by design (flags every exported interface) and delegates the false-positive suppression entirely to the agent reading the comment block above the interface. This is a judgment-tier pattern, not a mechanical one. The index entry should reflect "enforcement_type": "judgment" (or equivalent) so downstream dispatch logic does not skip the adjudicator. The YAML rule frontmatter (rules/go/counterfeiter-directive-on-interface.yml) has no enforcement_type field — it may need one added so the index regenerates correctly.

Nice to Have (Optional)

  • rule-tests/__snapshots__/go-architecture/counterfeiter-directive-on-interface-snapshot.yml — The snapshot file holds byte/char offsets for AST label ranges. For the first snapshot (no-directive case), manual character-count verification of the snippet suggests the primary label's start: 72 may be off by one relative to the type keyword position in the snippet (// UserService is...type Service… → closing }). This warrants a single test run with RERUN_SNAPSHOTS=1 or equivalent to confirm all offsets are accurate and the snapshot tests pass after the redesign. The diff shows this file was newly added, so no prior baseline exists.
  • CHANGELOG.md:1 — Mechanical finding: no ## Unreleased bullet present. In an autoRelease repo the release agent promotes ## Unreleased post-merge; without one, no version ships for this PR. Add a conventional-prefixed bullet under ## Unreleased before merging. (rule: changelog/unreleased-entry-required)

Notes

The two rule changes are directionally correct:

  1. counterfeiter-directive-on-interface — Switching from a strict not.follows sibling check to an over-inclusive anchor + adjudicator is the right call. The //counterfeiter:generate directive legitimately sits above multi-line doc comment blocks on real interfaces, and ast-grep's immediate-sibling relations cannot bridge that gap. The YAML test file (rule-tests/counterfeiter-directive-on-interface-test.yml) covers the three key cases correctly: (a) no directive → flagged, (b) directive one line above → not flagged, (c) directive above doc comment → not flagged. The ignores list (main.go, **/mocks/**, test files) is appropriate.

  2. new-prefix-constructor-naming — Adding the Parse[A-Z].*Default$ regex exemption is correct. The pattern ^Parse[A-Z].*Default$ correctly matches ParseConfigDefault, ParseGzipDefault, etc. and correctly excludes ParseHTTPScheme (no Default suffix). The test added in rule-tests/new-prefix-constructor-naming-test.yml validates this. The alternation in the YAML (not: any: - regex: '^New' - regex: '^Parse[A-Z].*Default$') is the right ast-grep shape.

Traceability: This review used the pre-computed mechanical findings from ast-grep-runner (1 finding — CHANGELOG), the Step 0c diff, and manual inspection of all 7 changed files. The selector-mode guide (docs/selector-mode-guide.md) was inaccessible in this environment, so the judgment-tier adjudication was performed in-session rather than via the guide's structured classify/adjudicate steps.


Verdict: request-changes — One Should Fix (index.json enforcement_type mismatch) and one Should Fix (CHANGELOG missing ## Unreleased). The rule design is sound, but the index metadata and changelog must be corrected before merge.

{
"verdict": "request-changes",
"summary": "The rule redesigns are correct and well-tested, but the index.json entry for counterfeiter-directive-on-interface still carries enforcement_type: mechanical despite being judgment-tier by design, and the CHANGELOG is missing its ## Unreleased section in an autoRelease repo.",
"comments": [
{
"file": "rules/index.json",
"line": 244,
"severity": "major",
"message": "go-architecture/counterfeiter-directive-on-interface has enforcement_type: 'mechanical' but the rule's new design is over-inclusive anchor + agent adjudicator (judgment-tier). The index entry must reflect 'judgment' so downstream dispatch does not skip the adjudicator. Add enforcement_type to the YAML frontmatter so it regenerates correctly."
},
{
"file": "CHANGELOG.md",
"line": 1,
"severity": "major",
"message": "PR changes source but CHANGELOG.md has no '## Unreleased' bullet. In an autoRelease repo the release agent promotes '## Unreleased' post-merge; with none, no version ships. Add a conventional-prefixed bullet under '## Unreleased' before merging. See docs/changelog-guide.md."
},
{
"file": "rule-tests/__snapshots__/go-architecture/counterfeiter-directive-on-interface-snapshot.yml",
"line": 1,
"severity": "minor",
"message": "Snapshot holds byte-offset label positions. Manual count on the first snapshot (no-directive case) suggests start:72 may be off by one relative to the type keyword position in the snippet. Recommend running the snapshot tests (RERUN_SNAPSHOTS=1 or equivalent) to confirm all offsets are accurate after this redesign."
}
],
"concerns_addressed": [
"correctness: Rule switched to over-inclusive anchor + adjudicator — design verified sound; YAML test cases cover all three key scenarios",
"correctness: ParseDefault exemption regex verified — ^Parse[A-Z].*Default$ correctly matches ParseConfigDefault and excludes non-Default Parse functions",
"tests: Snapshot file added — offsets cannot be fully verified without running tests; flagged for confirmation",
"correctness: index.json enforcement_type mismatch flagged — counterfeiter rule is judgment-tier but indexed as mechanical"
]
}

- go-architecture-patterns.md Enforcement line now mirrors the sibling
counterfeiter-mocks-required rule: mechanical first-pass + judgment-tier
LLM adjudication (index regenerated). enforcement_type stays mechanical —
the YAML genuinely runs in the funnel (selected by enforcement regex);
the adjudication is explicit in the doc and enforced by the review prompt.
- CHANGELOG: add ## Unreleased section (repo has release.autoRelease: true;
without it no version ships post-merge).
@ben-s-pull-request-reviewer
ben-s-pull-request-reviewerBot dismissed their stale reviewAugust 19, 2026 20:27

superseded by new automated review

@ben-s-pull-request-reviewerben-s-pull-request-reviewerBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mechanical funnel ran with zero findings and both changed rules are enforcement_type: mechanical (not judgment-tier), so the Step 4b-i candidate set is empty. Per selector-mode short-circuit: with no applicable judgment rules and no mechanical findings, I can proceed directly to Step 5.


PR Review: fix(rules): stop false-positive counterfeiter and Parse*Default findings

Branch:fix/pr-reviewer-false-positivesmaster
Files changed: 8 files, +206 / −29 lines


Must Fix (Critical)

None.

Should Fix (Important)

None.

Nice to Have (Optional)

None.


Selector Mode Traceability

  • Candidates: 0 judgment rules matched by Step 4b-i glob filter
  • Applicable: 0 (candidate set was empty; both changed rules are enforcement_type: mechanical, not judgment)
  • Skipped: N/A — no candidates to evaluate
  • Mechanical funnel: 0 findings across 74 YAMLs (already confirmed clean by workflow)
  • Step 4d-sel: skipped — short-circuit condition met (no candidates + no mechanical findings)

Verdict

The diff is a targeted bugfix to two ast-grep rules:

  1. counterfeiter-directive-on-interface.yml — removed the not.follows filter (which missed directives separated from interfaces by doc comments) and replaced it with an over-inclusive anchor that flags every exported interface; LLM adjudication now handles the false-positive dismissal. The mechanical funnel correctly returns zero findings for this rule.
  2. new-prefix-constructor-naming.yml — added Parse[X]Default exemption to avoid contradicting the go-parse/paired-parse-and-parsedefault rule. The regex ^Parse[A-Z].*Default$ is sound.

Both changes are mechanically correct, the test fixtures and snapshots are consistent, and the docs accurately describe the new adjudication design. No issues found.


{
"verdict": "approve",
"summary": "Bugfix changing two ast-grep rules from strict mechanical checks to over-inclusive anchors with LLM adjudication. counterfeiter-directive-on-interface now flags all exported interfaces and lets the adjudicator confirm directive presence; new-prefix-constructor-naming exempts Parse<X>Default to avoid contradicting paired-parse-and-parsedefault. Mechanical funnel confirms zero findings; test fixtures and snapshots are consistent.",
"comments": [],
"concerns_addressed": [
"correctness: counterfeiter rule now uses judgment-tier adjudication to handle doc-comment-separated directives — no false positives expected",
"correctness: Parse<X>Default exemption regex is sound (^Parse[A-Z].*Default$) — no contradiction with paired-parse-and-parsedefault",
"correctness: over-inclusive anchor acknowledged in rule message and docs — adjudication design is explicit",
"tests: new rule-test covers all key cases (unexported, struct, exported without directive, exported with directive, exported with directive above multi-line doc comment)",
"tests: ParseConfigDefault valid case added to new-prefix-constructor-naming test"
]
}

@bborbe
bborbe merged commit 93f4870 into masterAug 19, 2026
1 check passed
@bborbe
bborbe deleted the fix/pr-reviewer-false-positives branch August 19, 2026 20:29
Sign up for freeto 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

@bborbe