diff --git a/CHANGELOG.md b/CHANGELOG.md index 069bae9..590facc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ Please choose versions by [Semantic Versioning](http://semver.org/). * MINOR version when you add functionality in a backwards-compatible manner, and * PATCH version when you make backwards-compatible bug fixes. +## Unreleased + +- fix(rules): `go-errors/no-fmt-errorf.yml` rewritten as a structural rule. The original `pattern: fmt.Errorf($$$ARGS)` was parsed by tree-sitter Go grammar as a `type_conversion_expression` (because `Type(arg)` is a valid Go type cast at pattern-compile time), so the rule matched no real call sites — silently emitting zero findings since the YAML shipped. Replaced with `kind: call_expression` + structural `selector_expression` match on `fmt.Errorf`. Verified against scenario 004's fixture (`pkg/scenarios-test-fixture/violations.go` on bborbe/maintainer#2): the `Boom` function's `fmt.Errorf` now fires. Scenario 004's `findings_count` floor lifted ≥4 → ≥5. + ## v0.15.0 - feat(pipeline): dispatcher refactor for `/coding:pr-review` + `/coding:code-review` — Step 4 replaced with `ast-grep-runner` (mechanical funnel) → per-Owner LLM-tier adjudication → citation validation. Decouples LLM-call count from PR file count; small PR-size now equals small LLM-call count for the same rule coverage. Migrated 13 rule-enforcer agents to the dispatcher contract. diff --git a/rules/go/no-fmt-errorf.yml b/rules/go/no-fmt-errorf.yml index be0c946..73d2ece 100644 --- a/rules/go/no-fmt-errorf.yml +++ b/rules/go/no-fmt-errorf.yml @@ -6,7 +6,34 @@ message: | Use errors.Wrapf (wrapping) or errors.Errorf (new error) from github.com/bborbe/errors instead. See docs/go-error-wrapping-guide.md (RULE go-errors/no-fmt-errorf). rule: - pattern: fmt.Errorf($$$ARGS) + # ast-grep 0.43 + Go tree-sitter grammar parses bare + # `fmt.Errorf($A)` / `fmt.Errorf($$$ARGS)` as a + # `type_conversion_expression`, because `Type(arg)` is a valid Go + # type cast at pattern-compile time (no surrounding expression + # context disambiguates). Real call sites like + # `return fmt.Errorf(...)` parse as `call_expression` because the + # surrounding context fixes the parse. Net effect: the unanchored + # pattern matches NOTHING in real code — verified empirically. + # `kind: call_expression` alone does NOT fix it (kind filters + # output; pattern still mis-parses). Use a fully structural rule + # instead: match `call_expression` whose function field is a + # `selector_expression` with operand `fmt` and field `Errorf`. + # Surfaced 2026-06-03 during scenario 004's walk — the YAML had + # been silently emitting zero findings since the rule shipped. + # See [[ast-grep - Structural Code Search]] (Gotchas) for the family. + kind: call_expression + has: + field: function + kind: selector_expression + all: + - has: + field: operand + kind: identifier + regex: '^fmt$' + - has: + field: field + kind: field_identifier + regex: '^Errorf$' ignores: - "main.go" - "**/main.go" diff --git a/scenarios/004-findings-exist-path.md b/scenarios/004-findings-exist-path.md index 047caa9..8525089 100644 --- a/scenarios/004-findings-exist-path.md +++ b/scenarios/004-findings-exist-path.md @@ -16,7 +16,7 @@ The stable fixture is [bborbe/maintainer#2](https://github.com/bborbe/maintainer - `go-concurrency/no-raw-go-func` (bare `go func(){...}()`) - `go-errors/no-fmt-errorf` (`fmt.Errorf` in production code) -The PR stays open in perpetuity — the title says so. Walking 004 = re-pointing the dispatcher at the same SHA and verifying the funnel still surfaces all 5 violations. +All 5 violations now fire after the no-fmt-errorf YAML's structural rewrite (was silently parsing as `type_conversion_expression` until the 2026-06-03 fix). The PR stays open in perpetuity — the title says so. Walking 004 = re-pointing the dispatcher at the same SHA and verifying the funnel still surfaces all 5 violations. ## Setup @@ -39,7 +39,7 @@ The PR stays open in perpetuity — the title says so. Walking 004 = re-pointing ## Expected -- [ ] `cat /tmp/scen004-findings-count` returns ≥ `4` — at least 4 of the 5 deliberate violations surface. The 5th violation (`fmt.Errorf` flagged by `go-errors/no-fmt-errorf`) does not currently fire because the YAML pattern `fmt.Errorf($$$ARGS)` is parsed by tree-sitter Go grammar as a `type_conversion_expression` rather than a `call_expression`, so the pattern matches no real call sites. Tracked as a separate rule bug; the scenario's contract accepts the current 4-of-5 baseline so it can promote to `active` independent of that fix. When the YAML is corrected, lift this floor to `≥ 5`. +- [ ] `cat /tmp/scen004-findings-count` returns ≥ `5` — all 5 deliberate violations surface. The fmt.Errorf violation now fires after `rules/go/no-fmt-errorf.yml` was rewritten as a structural rule (`kind: call_expression` + selector match on `fmt.Errorf`) — the original `pattern: fmt.Errorf($$$ARGS)` was parsed as `type_conversion_expression` and matched nothing. - [ ] `wc -l < /tmp/scen004-rules` returns ≥ `3` — multiple distinct rule_ids surfaced (negative control: if the fixture parses to zero, the funnel didn't run) - [ ] `wc -l < /tmp/scen004-owners` returns ≥ `1` — at least one Owner has findings to adjudicate - [ ] Every line in `/tmp/scen004-agent-presence` ends with `AGENT_PRESENT` — Step 4b can resolve every Owner's agent file (regression risk: a renamed agent file silently no-ops that owner's findings; this catches it)