Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
29 changes: 28 additions & 1 deletion rules/go/no-fmt-errorf.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions scenarios/004-findings-exist-path.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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)
Expand Down
Loading