Combined finding from the 2026-07-07 multi-agent deep review (tooling perspective, grade C-). Items 1–3 were adversarially verified (item 2 against the actually-published nupkg); item 4 is detailed but was not independently re-verified (verification capped at 5 findings per reviewer). All touch src/Quarry.Analyzers, src/Quarry.Analyzers.CodeFixes, and their packaging.
1 (high, CONFIRMED): QRA402 fires on every solitary query, with O(N²) re-discovery
MultipleQueriesSameTableRule.cs:23 comments "Only analyze at execution sites" but only checks Site.EntityTypeName != null — discovery sets EntityTypeName on ChainRoot AND every builder-method site (Where/Select) via the receiver's generic argument. A single chain db.Users().Where(...).ExecuteFetchAllAsync() yields 3 sites with EntityTypeName "User" → entityOccurrences.Count > 1 → QRA402 ("Multiple independent queries on User; consider combining") fires up to 3× per solitary query. The intended SpanStart dedup (:51-52) is ineffective because chained invocation nodes share the same SpanStart. Only mitigant: Info severity.
- The rule also re-runs the full
UsageSiteDiscovery.DiscoverRawCallSite (symbol resolution + lambda parsing) for every candidate invocation in the enclosing method, once per site — O(N²) semantic discovery per edit. The only test covers the null-entity path.
- Fix: gate on terminal InterceptorKinds, group by ChainId (already on RawCallSite), derive counts from already-discovered sites; add behavioral tests (single chain → no diagnostic; two independent chains → one diagnostic).
2 (high, CONFIRMED against published 0.4.0 nupkg): Quarry.Analyzers.CodeFixes packs to lib/ — all QRA code fixes are dead for NuGet consumers
- The csproj sets
IsPackable=true but omits BuildOutputTargetFolder=analyzers/dotnet/cs and SuppressDependenciesWhenPacking (which Quarry.Analyzers/Generator/Migration all set). Verified on nuget.org: the 0.4.0 package has the DLL at lib/netstandard2.0/ with NO analyzers/ folder — the IDE never probes lib/ for CodeFixProvider exports. All five QRA fixes (CountToAny, SingleInToEquals, RemoveUnusedJoin, ThenByToOrderBy, RawSqlToChain) are dead for package consumers; llm.md advertises "QRA series + code fixes".
- Fix: add the two properties (or pack the code-fix DLL into the Quarry.Analyzers package); add a package-layout verification test/CI step that inspects the produced nupkg.
3 (high, CONFIRMED): QRA201 Remove-unused-join code fix produces non-compiling code and changes semantics
RemoveUnusedJoinCodeFix.cs:51 replaces the Join invocation with its receiver but never rewrites downstream lambdas. The API makes the break unavoidable: joined Select requires Func<T1,T2,TResult> while single-entity Select takes Func<T,TResult> — so in the rule's primary trigger scenario the fix leaves a 2-param lambda on a 1-entity builder → guaranteed compile error. Batch FixAllProvider compounds the blast radius.
- Semantics: the rule fires for INNER/CROSS/RIGHT/FULL joins (
UnusedJoinRule.cs:23-27), which filter/multiply rows even when unprojected — removal changes result sets.
- The rule also inspects only the LAST lambda parameter (
UnusedJoinRule.cs:56), wrong for every non-final join in multi-join chains.
- Fix: rewrite downstream lambda parameter lists in the fix; restrict the rule to row-preserving LEFT joins (or drop the auto-fix and keep a suggestion); track each join's own parameter position; add rewrite-output tests (current
CodeFixTests.cs only checks diagnostic IDs).
4 (high, not independently verified — validate first): Four QRA rules are dead on real input
- At analyzer time
Site.Expression is the parse+annotate-only SqlExpr (UsageSiteDiscovery.cs:1485-1494); binding/translation never runs, so rendering emits bare property names for columns and /* unresolved captured value */ for captures — never @pN or t0."Col". Consequently:
- QRA102 requires
inContent.StartsWith("@p") — never true.
- QRA304 requires
sql.Contains($"t0.\"{col}\"") (WhereOnNonIndexedColumnRule.cs:38-41) — never true.
- QRA101 requires MethodName
Count/CountAsync with Kind ExecuteScalar (CountComparedToZeroRule.cs:20-23) — no such terminal exists.
- QRA401 (the N+1 detector) lists
FetchAllAsync etc. without the Execute prefix (QueryInsideLoopRule.cs:15-18) — misses all fetch terminals.
- All pass their unit tests because tests fabricate RawCallSites with synthetic method names and inject SqlRawExpr fragments (
SimplificationRuleTests.cs:18-26, PatternRuleTests.cs:22).
- Fix: rewrite rules to walk the SqlExpr tree (
InExpr.Values.Count == 1, ColumnRefExpr names) instead of string-matching rendered SQL; fix QRA401's terminal-name set; add end-to-end analyzer tests that run the real discovery path on realistic chains.
5 (medium): Analyzer performance discipline
QuarryQueryAnalyzer enables generated-code analysis (ConfigureGeneratedCodeAnalysis(Analyze | ReportDiagnostics)) so the 23-rule analyzer also processes Quarry's own voluminous generated files; its per-node gate is the same over-broad IsQuarryMethodCandidate (any zero-arg uppercase call) → GetSymbolInfo on nearly every invocation per edit (QuarryQueryAnalyzer.cs:83, UsageSiteDiscovery.cs:144-147).
QuarryQueryAnalyzer.cs:92-115 / RawSqlMigrationAnalyzer.cs:38-56 eagerly walk all syntax trees with semantic models at compilation start.
- Fix: disable generated-code analysis; tighten the candidate gate (shared with the generator-caching issue's predicate work); replace compilation-start full walks with targeted node actions.
6 (medium): Quarry.Analyzers compiles the entire generator pipeline into a second assembly
- The csproj file-links every generator directory (Models, Parsing, IR, CodeGen, ...) — the IDE runs discovery twice per edit with no shared cache, emission code is compiled into an assembly that never emits, and generator refactors silently change analyzer behavior (the dead QRA string-matching is a symptom: rules written against translation-stage output, linked against parse-stage reality).
- Fix (longer-term): extract the minimal discovery/parse surface the analyzers need into a shared component with an explicit stage contract, or run QRA rules on the SqlExpr tree with a documented "parse-stage-only" input contract.
Acceptance criteria
Combined finding from the 2026-07-07 multi-agent deep review (tooling perspective, grade C-). Items 1–3 were adversarially verified (item 2 against the actually-published nupkg); item 4 is detailed but was not independently re-verified (verification capped at 5 findings per reviewer). All touch
src/Quarry.Analyzers,src/Quarry.Analyzers.CodeFixes, and their packaging.1 (high, CONFIRMED): QRA402 fires on every solitary query, with O(N²) re-discovery
MultipleQueriesSameTableRule.cs:23comments "Only analyze at execution sites" but only checksSite.EntityTypeName != null— discovery setsEntityTypeNameon ChainRoot AND every builder-method site (Where/Select) via the receiver's generic argument. A single chaindb.Users().Where(...).ExecuteFetchAllAsync()yields 3 sites with EntityTypeName "User" →entityOccurrences.Count > 1→ QRA402 ("Multiple independent queries on User; consider combining") fires up to 3× per solitary query. The intended SpanStart dedup (:51-52) is ineffective because chained invocation nodes share the same SpanStart. Only mitigant: Info severity.UsageSiteDiscovery.DiscoverRawCallSite(symbol resolution + lambda parsing) for every candidate invocation in the enclosing method, once per site — O(N²) semantic discovery per edit. The only test covers the null-entity path.2 (high, CONFIRMED against published 0.4.0 nupkg): Quarry.Analyzers.CodeFixes packs to
lib/— all QRA code fixes are dead for NuGet consumersIsPackable=truebut omitsBuildOutputTargetFolder=analyzers/dotnet/csandSuppressDependenciesWhenPacking(which Quarry.Analyzers/Generator/Migration all set). Verified on nuget.org: the 0.4.0 package has the DLL atlib/netstandard2.0/with NOanalyzers/folder — the IDE never probeslib/forCodeFixProviderexports. All five QRA fixes (CountToAny, SingleInToEquals, RemoveUnusedJoin, ThenByToOrderBy, RawSqlToChain) are dead for package consumers; llm.md advertises "QRA series + code fixes".3 (high, CONFIRMED): QRA201 Remove-unused-join code fix produces non-compiling code and changes semantics
RemoveUnusedJoinCodeFix.cs:51replaces the Join invocation with its receiver but never rewrites downstream lambdas. The API makes the break unavoidable: joinedSelectrequiresFunc<T1,T2,TResult>while single-entitySelecttakesFunc<T,TResult>— so in the rule's primary trigger scenario the fix leaves a 2-param lambda on a 1-entity builder → guaranteed compile error. Batch FixAllProvider compounds the blast radius.UnusedJoinRule.cs:23-27), which filter/multiply rows even when unprojected — removal changes result sets.UnusedJoinRule.cs:56), wrong for every non-final join in multi-join chains.CodeFixTests.csonly checks diagnostic IDs).4 (high, not independently verified — validate first): Four QRA rules are dead on real input
Site.Expressionis the parse+annotate-only SqlExpr (UsageSiteDiscovery.cs:1485-1494); binding/translation never runs, so rendering emits bare property names for columns and/* unresolved captured value */for captures — never@pNort0."Col". Consequently:inContent.StartsWith("@p")— never true.sql.Contains($"t0.\"{col}\"")(WhereOnNonIndexedColumnRule.cs:38-41) — never true.Count/CountAsyncwith Kind ExecuteScalar (CountComparedToZeroRule.cs:20-23) — no such terminal exists.FetchAllAsyncetc. without theExecuteprefix (QueryInsideLoopRule.cs:15-18) — misses all fetch terminals.SimplificationRuleTests.cs:18-26,PatternRuleTests.cs:22).InExpr.Values.Count == 1, ColumnRefExpr names) instead of string-matching rendered SQL; fix QRA401's terminal-name set; add end-to-end analyzer tests that run the real discovery path on realistic chains.5 (medium): Analyzer performance discipline
QuarryQueryAnalyzerenables generated-code analysis (ConfigureGeneratedCodeAnalysis(Analyze | ReportDiagnostics)) so the 23-rule analyzer also processes Quarry's own voluminous generated files; its per-node gate is the same over-broadIsQuarryMethodCandidate(any zero-arg uppercase call) →GetSymbolInfoon nearly every invocation per edit (QuarryQueryAnalyzer.cs:83,UsageSiteDiscovery.cs:144-147).QuarryQueryAnalyzer.cs:92-115/RawSqlMigrationAnalyzer.cs:38-56eagerly walk all syntax trees with semantic models at compilation start.6 (medium): Quarry.Analyzers compiles the entire generator pipeline into a second assembly
Acceptance criteria
analyzers/dotnet/cs(CI-verified).