From a1972bf1a2ebdcd1cccf8797bb79a11b04031bc4 Mon Sep 17 00:00:00 2001 From: AndresL230 <190146319+AndresL230@users.noreply.github.com> Date: Fri, 29 May 2026 02:39:57 -0400 Subject: [PATCH 1/4] feat(wave3): thread inlineParallelCapable through regex path, dedup key, intelligence graph (#127) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to PR #126 (#116). The inlineParallelCapable fingerprint flag was wired through the AST detector + core-scanner but not the detection-layer consumers that already handle batchCapable. Each gap parallels an existing batchCapable site: - Regex pattern path: openai-compatible.ts now emits reg?.inlineParallelCapable (registry-only, no hint-regex fallback); local-waste-detector.ts gains an inline-parallel finding producing the n/count suggestion on the regex-only (RECOST_DISABLE_AST=1) path, using a distinct inline_parallel id so it never collapses with a batch finding. - Pattern dedup key: patterns/utils.ts includes inlineParallelCapable so distinct match shapes aren't collapsed. - Intelligence graph: ApiCallNode + builder.ts coerce the flag so AI-review context distinguishes batch from inline-parallel shapes. Also reconciles PROGRESS.md: Waves 5 (#118/#119) and 1 (#84/#85/#112) marked shipped; remaining accuracy work is Wave 2 -> 4. Tests: scanner-patterns (emit + absence), local-waste-detector (regex-only fanout finding), builder (coercion true/false). Full test:scanner green, build:ext clean, benchmark Δ +0.00pp on all 5 metrics. --- docs/superpowers/plans/PROGRESS.md | 11 ++++--- src/intelligence/__tests__/builder.test.ts | 4 +++ src/intelligence/builder.ts | 1 + src/intelligence/types.ts | 1 + src/scanner/local-waste-detector.ts | 37 ++++++++++++++++++++++ src/scanner/patterns/openai-compatible.ts | 4 +++ src/scanner/patterns/types.ts | 1 + src/scanner/patterns/utils.ts | 1 + src/test/local-waste-detector.test.ts | 12 +++++++ src/test/scanner-patterns.test.ts | 14 ++++++++ 10 files changed, 81 insertions(+), 5 deletions(-) diff --git a/docs/superpowers/plans/PROGRESS.md b/docs/superpowers/plans/PROGRESS.md index 4ee44ac..d54473e 100644 --- a/docs/superpowers/plans/PROGRESS.md +++ b/docs/superpowers/plans/PROGRESS.md @@ -130,7 +130,7 @@ Two trackers in one file: Post-foundation work organized into 10 waves + 2 standalones, tracked via `wave/*` + `area/*` GitHub labels. **Execution order is severity-weighted** (platform issues outrank accuracy): Wave 9 → 6 → 7 → 8 → 10 → standalones → accuracy waves (5 → 3 → 1 → 2 → 4). -**As of 2026-05-27:** all platform waves (6–10) are SHIPPED, plus accuracy Wave 3. Remaining open work: accuracy waves **5 → 1 → 2 → 4** + standalones #45/#52. +**As of 2026-05-29:** all platform waves (6–10) are SHIPPED, plus accuracy Waves 3, 5, and 1. Remaining open work: accuracy waves **2 → 4** + Wave 3 follow-ups #127/#128 + standalones #45/#52. ## Overall Wave Status @@ -143,10 +143,10 @@ Post-foundation work organized into 10 waves + 2 standalones, tracked via `wave/ | **10** | Config hygiene | #97, #98 | 🟢 | Shipped via PR [#124](https://github.com/recost-dev/extension/pull/124). | | — | Standalone #45 | #45 | ⬜ | Opt-in Project ID persistence — independent feature. | | — | Standalone #52 | #52 | ⬜ | Dashboard theming — can defer indefinitely. | -| **5** | Housekeeping (accuracy) | #118, #119 | ⬜ | Cheapest accuracy work. Both in-repo (benchmark runner + `benchmark.yml`). | -| **3** | Resolver follow-ups (accuracy) | #114, #115, #116 | 🟢 | Shipped via PR [#126](https://github.com/recost-dev/extension/pull/126). Follow-ups #127 (detection threading) + #128 (dashboard badge). | -| **1** | Findings quality (accuracy) | #84, #85, #112 | ⬜ | User-facing. | -| **2** | Traceability (accuracy) | #81, #113 | ⬜ | Corpus expansion + dual locations. | +| **5** | Housekeeping (accuracy) | #118, #119 | 🟢 | Both closed 2026-05-27 (benchmark runner sort + D1 CI gate verified). | +| **3** | Resolver follow-ups (accuracy) | #114, #115, #116 | 🟢 | Shipped via PR [#126](https://github.com/recost-dev/extension/pull/126). Follow-ups #127 (detection threading) + #128 (dashboard badge) — in progress. | +| **1** | Findings quality (accuracy) | #84, #85, #112 | 🟢 | All closed 2026-05-28 (C2 dedupe, C3 confidence, CACHE/BATCH_GUARD tightening). | +| **2** | Traceability (accuracy) | #81, #113 | ⬜ | Corpus expansion + dual locations. Next wave. | | **4** | Recall recovery (accuracy) | #117 | ⬜ | Risky — depended on Wave 3 (#116), now unblocked. | ## Wave 9 — Local-mode IPC architecture (CLOSED) @@ -232,6 +232,7 @@ Post-foundation work organized into 10 waves + 2 standalones, tracked via `wave/ > Append `YYYY-MM-DD HH:MM — `. Newest at top. +- 2026-05-29 — **Tracker reconciliation + Wave 3 follow-ups started.** Waves 5 (#118/#119, closed 05-27) and 1 (#84/#85/#112, closed 05-28) marked 🟢 — both had merged but were never reflected here. Remaining accuracy work is now Wave 2 → 4. Picked up the Wave 3 follow-ups #127 (thread `inlineParallelCapable` through regex pattern path + dedup key + intelligence graph) → #128 (dashboard capability badge) on branch `feat/wave3-followups`. - 2026-05-27 — **Wave 3 shipped (PR #126)** — #114/#115/#116 closed. Subagent-driven across 2 parallel worktree tracks; gates green (test:scanner, build:ext, benchmark Δ +0.00pp). Follow-ups filed: #127 (detection threading) + #128 (dashboard badge). **Tracker reconciliation:** also corrected stale statuses — Waves 7 (PR #122), 8 (PR #123), and 10 (PR #124) had merged earlier but were never marked 🟢 here. All platform waves (6–10) now confirmed shipped; remaining work is accuracy waves 5→1→2→4 + standalones. - 2026-05-21 — **Wave 7 PR opened (#122).** Closes #92 + #93 as design-resolved via labeling, not reconciliation. 7 commits on `wave7/cost-simulator-labeling` (worktree `../extension-wave7`): shared `EstimateDisclaimer` component, render on Simulate tab, code comments on `LOCAL_PRICING` + `FREQUENCY_CLASS_MULTIPLIERS`, CLAUDE.md "Cost numbers: heuristic vs authoritative" section. All gates green (build, 4/4 tests, D1 Δ +0.00pp). Spec + plan committed to main. Wave 8 (#46/#94/#100) is next per severity order. - 2026-05-15 — **Wave 9 closed docs-only via PR #120**, **Wave 6 shipped** (api#40 + extension#121). Both detailed in `memory/wave9_local_mode_resolution.md` and `memory/wave6_status.md`. diff --git a/src/intelligence/__tests__/builder.test.ts b/src/intelligence/__tests__/builder.test.ts index 74e9cc9..3b9ea41 100644 --- a/src/intelligence/__tests__/builder.test.ts +++ b/src/intelligence/__tests__/builder.test.ts @@ -25,6 +25,7 @@ run("buildRepoIntelligenceSnapshot normalizes paths, stores null providers, and library: "openai", cacheCapable: false, batchCapable: false, + inlineParallelCapable: true, }, { file: "./src/payments.ts", @@ -124,11 +125,14 @@ run("buildRepoIntelligenceSnapshot normalizes paths, stores null providers, and assert.equal(workerCall.provider, "openai"); assert.equal(workerCall.library, "openai"); assert.equal(workerCall.filePath, "src/worker.ts"); + assert.equal(workerCall.inlineParallelCapable, true); const stripeCall = Object.values(snapshot.apiCalls).find((apiCall) => apiCall.filePath === "src/payments.ts" && apiCall.line === 15); assert.ok(stripeCall); assert.equal(stripeCall.provider, "stripe"); assert.equal(stripeCall.library, "stripe"); + // Absent on input → coerced to false (parallels batchCapable). + assert.equal(stripeCall.inlineParallelCapable, false); const regexOnlyCall = Object.values(snapshot.apiCalls).find((apiCall) => apiCall.filePath === "src/regex-only.ts" && apiCall.line === 22); assert.ok(regexOnlyCall); diff --git a/src/intelligence/builder.ts b/src/intelligence/builder.ts index c4578b9..ea4e281 100644 --- a/src/intelligence/builder.ts +++ b/src/intelligence/builder.ts @@ -197,6 +197,7 @@ export function buildRepoIntelligenceSnapshot( costModel: call.costModel ?? null, frequencyClass: call.frequencyClass ?? null, batchCapable: Boolean(call.batchCapable), + inlineParallelCapable: Boolean(call.inlineParallelCapable), cacheCapable: Boolean(call.cacheCapable), streaming: Boolean(call.streaming), isMiddleware: Boolean(call.isMiddleware), diff --git a/src/intelligence/types.ts b/src/intelligence/types.ts index ac6cf79..c18a6a1 100644 --- a/src/intelligence/types.ts +++ b/src/intelligence/types.ts @@ -22,6 +22,7 @@ export interface ApiCallNode { costModel: ApiCallInput["costModel"] | null; frequencyClass: ApiCallInput["frequencyClass"] | null; batchCapable: boolean; + inlineParallelCapable: boolean; cacheCapable: boolean; streaming: boolean; isMiddleware: boolean; diff --git a/src/scanner/local-waste-detector.ts b/src/scanner/local-waste-detector.ts index add15a1..7842c19 100644 --- a/src/scanner/local-waste-detector.ts +++ b/src/scanner/local-waste-detector.ts @@ -293,6 +293,42 @@ function detectBatchFinding(relativePath: string, site: MatchedCallSite): LocalW ); } +// Inline-parallel: the endpoint itself accepts an n/count parameter (a +// registry-only signal, e.g. DALL·E `images.generate`), so fanning out one +// request per item is wasteful. Mirrors the AST inline-parallel detector for +// the regex-only path (RECOST_DISABLE_AST=1). Distinct from `detectBatchFinding` +// — uses an `inline_parallel` id so it never collapses with a batch finding. +function detectInlineParallelFinding(relativePath: string, site: MatchedCallSite): LocalWasteFinding | null { + const ext = path.extname(relativePath).toLowerCase(); + if (!JS_TS_EXTENSIONS.has(ext)) return null; + if (!site.match.inlineParallelCapable) return null; + if (site.batchGuard) return null; + if (!(site.loopDepth > 0 || site.promiseAll || site.mapFanout || site.arrayFanout)) return null; + + const evidence: string[] = []; + pushEvidence(evidence, site.loopDepth > 0, "Call executes inside a loop."); + pushEvidence( + evidence, + site.promiseAll || site.mapFanout || site.arrayFanout, + "Parallel collection fanout issues one request per item." + ); + evidence.push("This endpoint accepts an n/count parameter that returns multiple results from a single request."); + const score = baseScore(site) + 1 - (site.smallBounded ? 1 : 0); + const confidence = scoreToConfidence(score, site, true); + if (confidence < 0.35) return null; + return { + id: `local-inline_parallel-${relativePath}:${site.line}`, + type: "batch" as SuggestionType, + severity: scoreToSeverity(score), + confidence, + description: + "This endpoint accepts an n/count parameter — request multiple results in a single call instead of issuing one request per item.", + affectedFile: relativePath, + line: site.line, + evidence, + }; +} + function detectRedundancyFinding(relativePath: string, site: MatchedCallSite, clientInitCount: number): LocalWasteFinding | null { const ext = path.extname(relativePath).toLowerCase(); if (!JS_TS_EXTENSIONS.has(ext)) return null; @@ -418,6 +454,7 @@ export function detectLocalWasteFindingsInText(relativePath: string, text: strin for (const site of callSites) { findings.push(detectCacheFinding(relativePath, site)); findings.push(detectBatchFinding(relativePath, site)); + findings.push(detectInlineParallelFinding(relativePath, site)); findings.push(detectRedundancyFinding(relativePath, site, clientInitCount)); findings.push(detectNPlusOneFinding(relativePath, site)); findings.push(detectRateLimitFinding(relativePath, site)); diff --git a/src/scanner/patterns/openai-compatible.ts b/src/scanner/patterns/openai-compatible.ts index 8b4a82e..c904564 100644 --- a/src/scanner/patterns/openai-compatible.ts +++ b/src/scanner/patterns/openai-compatible.ts @@ -173,6 +173,9 @@ export const openAiCompatibleMatcher: LineMatcher = { const streaming = reg?.streaming ?? /stream/i.test(action); const batchCapable = reg?.batchCapable ?? /batches|batch/.test(path); + // No hint-regex fallback: inline-parallel capability (an n/count param) + // is a registry-only signal — there is no reliable lexical hint for it. + const inlineParallelCapable = reg?.inlineParallelCapable ?? false; const cacheCapable = reg?.cacheCapable ?? /responses|chat|assistants|threads/.test(path); results.push({ @@ -187,6 +190,7 @@ export const openAiCompatibleMatcher: LineMatcher = { host, streaming, batchCapable, + inlineParallelCapable, cacheCapable, rawMatch: match[0], }); diff --git a/src/scanner/patterns/types.ts b/src/scanner/patterns/types.ts index bacbf9d..5899e52 100644 --- a/src/scanner/patterns/types.ts +++ b/src/scanner/patterns/types.ts @@ -22,6 +22,7 @@ export interface ApiCallMatch { loopContext?: boolean; streaming?: boolean; batchCapable?: boolean; + inlineParallelCapable?: boolean; cacheCapable?: boolean; inferredCostRisk?: string[]; rawMatch?: string; diff --git a/src/scanner/patterns/utils.ts b/src/scanner/patterns/utils.ts index 2787a97..fe1f648 100644 --- a/src/scanner/patterns/utils.ts +++ b/src/scanner/patterns/utils.ts @@ -39,6 +39,7 @@ export function uniqueMatches(matches: ApiCallMatch[]): ApiCallMatch[] { match.host ?? "", match.streaming ? "1" : "0", match.batchCapable ? "1" : "0", + match.inlineParallelCapable ? "1" : "0", ].join("|"); if (seen.has(key)) continue; diff --git a/src/test/local-waste-detector.test.ts b/src/test/local-waste-detector.test.ts index 943205c..cd3252e 100644 --- a/src/test/local-waste-detector.test.ts +++ b/src/test/local-waste-detector.test.ts @@ -59,4 +59,16 @@ run("reduces confidence when polling already has backoff and concurrency guards" assert.ok((rateLimit?.confidence ?? 1) < 0.75); }); +run("flags inline-parallel fanout for an n/count-capable endpoint (regex-only path)", () => { + const text = [ + "async function makeThumbnails(prompts) {", + " return Promise.all(prompts.map((prompt) => client.images.generate({ prompt })));", + "}", + ].join("\n"); + const findings = detectLocalWasteFindingsInText("src/lib/thumbnails.ts", text); + const inlineParallel = findings.find((finding) => finding.id.includes("inline_parallel")); + assert.ok(inlineParallel, "expected an inline-parallel finding for images.generate fanout"); + assert.match(inlineParallel?.description ?? "", /n\/count parameter/); +}); + console.log("All local waste detector tests passed"); diff --git a/src/test/scanner-patterns.test.ts b/src/test/scanner-patterns.test.ts index ed76c3d..87ab956 100644 --- a/src/test/scanner-patterns.test.ts +++ b/src/test/scanner-patterns.test.ts @@ -99,4 +99,18 @@ run("keeps route definition detection", () => { assert.ok(routes.some((r) => r.method === "POST" && r.url === "/webhook/stripe")); }); +run("emits inlineParallelCapable from the registry for images.generate", () => { + const match = pickProvider("await client.images.generate({ prompt: 'a cat', n: 4 });", "openai"); + assert.ok(match); + assert.equal(match?.inlineParallelCapable, true); + // images.generate is inline-parallel, not batch-capable (reclassified in #116). + assert.notEqual(match?.batchCapable, true); +}); + +run("leaves inlineParallelCapable unset for non-inline-parallel methods", () => { + const match = pickProvider("await client.chat.completions.create({ model: 'gpt-4.1' });", "openai"); + assert.ok(match); + assert.notEqual(match?.inlineParallelCapable, true); +}); + console.log("All scanner matcher tests passed"); From e9d7d7e2e0a38161ba9a9848a857ebf057e2efc5 Mon Sep 17 00:00:00 2001 From: AndresL230 <190146319+AndresL230@users.noreply.github.com> Date: Fri, 29 May 2026 02:43:19 -0400 Subject: [PATCH 2/4] feat(wave3): surface inlineParallelCapable as an endpoint capability badge (#128) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to PR #126 (#116). The inlineParallelCapable flag was absent from the UI layer, so since #116 reclassified images.generate off batchCapable, DALL·E endpoints lost their old "batch" chip and showed no capability indicator. This restores a correct one. - EndpointRecord in dashboard/src/lib/types.ts + webview/src/types.ts now carry inlineParallelCapable (parallels batchCapable). - Endpoints.tsx pushes an "inline-parallel" capability chip when the flag is set. - scan-results.ts populates endpoint.inlineParallelCapable at all three endpoint-construction sites (aggregate, fresh, synthetic) so the webview badge has data — mirrors the existing batchCapable mapping. Note: the web dashboard reads endpoints from the API, whose schema has no inline_parallel column yet, so the dashboard chip only lights up once an API migration persists/serves the field. The VS Code webview badge works now. Build: build:webview + build:ext clean; test:scanner green (typechecks scan-results.ts). Dashboard deps not installed in this env — change is an additive optional field + conditional chip (type-safe by construction). --- dashboard/src/lib/types.ts | 1 + dashboard/src/pages/Endpoints.tsx | 1 + src/scan-results.ts | 3 +++ webview/src/types.ts | 1 + 4 files changed, 6 insertions(+) diff --git a/dashboard/src/lib/types.ts b/dashboard/src/lib/types.ts index 1fd12b7..5bc1cb0 100644 --- a/dashboard/src/lib/types.ts +++ b/dashboard/src/lib/types.ts @@ -80,6 +80,7 @@ export interface EndpointRecord { costModel?: "per_token" | "per_transaction" | "per_request" | "free"; frequencyClass?: string; batchCapable?: boolean; + inlineParallelCapable?: boolean; cacheCapable?: boolean; streaming?: boolean; isMiddleware?: boolean; diff --git a/dashboard/src/pages/Endpoints.tsx b/dashboard/src/pages/Endpoints.tsx index 62f3d10..528a169 100644 --- a/dashboard/src/pages/Endpoints.tsx +++ b/dashboard/src/pages/Endpoints.tsx @@ -76,6 +76,7 @@ function EndpointCard({ ep }: { ep: EndpointRecord }) { const caps: string[] = []; if (ep.streaming) caps.push('stream'); if (ep.batchCapable) caps.push('batch'); + if (ep.inlineParallelCapable) caps.push('inline-parallel'); if (ep.cacheCapable) caps.push('cache'); if (ep.isMiddleware) caps.push('middleware'); diff --git a/src/scan-results.ts b/src/scan-results.ts index 0c9ec3c..0c1b583 100644 --- a/src/scan-results.ts +++ b/src/scan-results.ts @@ -362,6 +362,7 @@ export function mergeRemoteAndLocalEndpoints( if (!endpoint.costModel && call.costModel) endpoint.costModel = call.costModel; endpoint.frequencyClass = pickMostSevereFrequency(endpoint.frequencyClass, call.frequencyClass); if (call.batchCapable) endpoint.batchCapable = true; + if (call.inlineParallelCapable) endpoint.inlineParallelCapable = true; if (call.cacheCapable) endpoint.cacheCapable = true; if (call.streaming) endpoint.streaming = true; if (call.isMiddleware) endpoint.isMiddleware = true; @@ -417,6 +418,7 @@ export function mergeRemoteAndLocalEndpoints( costModel: call.costModel, frequencyClass: call.frequencyClass, batchCapable: call.batchCapable, + inlineParallelCapable: call.inlineParallelCapable, cacheCapable: call.cacheCapable, streaming: call.streaming, isMiddleware: call.isMiddleware, @@ -450,6 +452,7 @@ export function mergeRemoteAndLocalEndpoints( if (!synthetic.costModel && call.costModel) synthetic.costModel = call.costModel; synthetic.frequencyClass = pickMostSevereFrequency(synthetic.frequencyClass, call.frequencyClass); if (call.batchCapable) synthetic.batchCapable = true; + if (call.inlineParallelCapable) synthetic.inlineParallelCapable = true; if (call.cacheCapable) synthetic.cacheCapable = true; if (call.streaming) synthetic.streaming = true; if (call.isMiddleware) synthetic.isMiddleware = true; diff --git a/webview/src/types.ts b/webview/src/types.ts index 581dd00..70761fe 100644 --- a/webview/src/types.ts +++ b/webview/src/types.ts @@ -43,6 +43,7 @@ export interface EndpointRecord { costModel?: "per_token" | "per_transaction" | "per_request" | "free"; frequencyClass?: string; batchCapable?: boolean; + inlineParallelCapable?: boolean; cacheCapable?: boolean; streaming?: boolean; isMiddleware?: boolean; From 0ffd95136c8aba570748d121b8a553fe85ee49fd Mon Sep 17 00:00:00 2001 From: AndresL230 <190146319+AndresL230@users.noreply.github.com> Date: Fri, 29 May 2026 02:44:00 -0400 Subject: [PATCH 3/4] docs(progress): mark Wave 3 follow-ups #127/#128 landed --- docs/superpowers/plans/PROGRESS.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/superpowers/plans/PROGRESS.md b/docs/superpowers/plans/PROGRESS.md index d54473e..68cb080 100644 --- a/docs/superpowers/plans/PROGRESS.md +++ b/docs/superpowers/plans/PROGRESS.md @@ -144,7 +144,7 @@ Post-foundation work organized into 10 waves + 2 standalones, tracked via `wave/ | — | Standalone #45 | #45 | ⬜ | Opt-in Project ID persistence — independent feature. | | — | Standalone #52 | #52 | ⬜ | Dashboard theming — can defer indefinitely. | | **5** | Housekeeping (accuracy) | #118, #119 | 🟢 | Both closed 2026-05-27 (benchmark runner sort + D1 CI gate verified). | -| **3** | Resolver follow-ups (accuracy) | #114, #115, #116 | 🟢 | Shipped via PR [#126](https://github.com/recost-dev/extension/pull/126). Follow-ups #127 (detection threading) + #128 (dashboard badge) — in progress. | +| **3** | Resolver follow-ups (accuracy) | #114, #115, #116 | 🟢 | Shipped via PR [#126](https://github.com/recost-dev/extension/pull/126). Follow-ups #127 (detection threading) + #128 (dashboard badge) landed on `feat/wave3-followups`. | | **1** | Findings quality (accuracy) | #84, #85, #112 | 🟢 | All closed 2026-05-28 (C2 dedupe, C3 confidence, CACHE/BATCH_GUARD tightening). | | **2** | Traceability (accuracy) | #81, #113 | ⬜ | Corpus expansion + dual locations. Next wave. | | **4** | Recall recovery (accuracy) | #117 | ⬜ | Risky — depended on Wave 3 (#116), now unblocked. | @@ -232,7 +232,8 @@ Post-foundation work organized into 10 waves + 2 standalones, tracked via `wave/ > Append `YYYY-MM-DD HH:MM — `. Newest at top. -- 2026-05-29 — **Tracker reconciliation + Wave 3 follow-ups started.** Waves 5 (#118/#119, closed 05-27) and 1 (#84/#85/#112, closed 05-28) marked 🟢 — both had merged but were never reflected here. Remaining accuracy work is now Wave 2 → 4. Picked up the Wave 3 follow-ups #127 (thread `inlineParallelCapable` through regex pattern path + dedup key + intelligence graph) → #128 (dashboard capability badge) on branch `feat/wave3-followups`. +- 2026-05-29 — **Wave 3 follow-ups #127 + #128 landed** on `feat/wave3-followups`. #127 (`a1972bf`): threaded `inlineParallelCapable` through the regex pattern path (`openai-compatible.ts` emit, registry-only), the regex-only `local-waste-detector.ts` (new inline-parallel finding with the n/count suggestion, distinct `inline_parallel` id), the pattern dedup key (`utils.ts`), and the intelligence graph (`ApiCallNode` + `builder.ts`). #128 (`e9d7d7e`): `EndpointRecord` in both UI type files + `Endpoints.tsx` inline-parallel chip + `scan-results.ts` population at all 3 endpoint-construction sites — restores the indicator DALL·E lost when #116 reclassified `images.generate` off `batchCapable`. Gates: full `test:scanner` green (3 new tests), `build` (webview+ext) clean, benchmark Δ +0.00pp on all 5 metrics. **Caveat:** the web dashboard reads endpoints from the API, whose schema has no `inline_parallel` column — dashboard chip needs an api-repo migration to light up; VS Code webview badge works now. Next: accuracy Wave 2 (#113 → #81). +- 2026-05-29 — **Tracker reconciliation.** Waves 5 (#118/#119, closed 05-27) and 1 (#84/#85/#112, closed 05-28) marked 🟢 — both had merged but were never reflected here. Remaining accuracy work is now Wave 2 → 4. - 2026-05-27 — **Wave 3 shipped (PR #126)** — #114/#115/#116 closed. Subagent-driven across 2 parallel worktree tracks; gates green (test:scanner, build:ext, benchmark Δ +0.00pp). Follow-ups filed: #127 (detection threading) + #128 (dashboard badge). **Tracker reconciliation:** also corrected stale statuses — Waves 7 (PR #122), 8 (PR #123), and 10 (PR #124) had merged earlier but were never marked 🟢 here. All platform waves (6–10) now confirmed shipped; remaining work is accuracy waves 5→1→2→4 + standalones. - 2026-05-21 — **Wave 7 PR opened (#122).** Closes #92 + #93 as design-resolved via labeling, not reconciliation. 7 commits on `wave7/cost-simulator-labeling` (worktree `../extension-wave7`): shared `EstimateDisclaimer` component, render on Simulate tab, code comments on `LOCAL_PRICING` + `FREQUENCY_CLASS_MULTIPLIERS`, CLAUDE.md "Cost numbers: heuristic vs authoritative" section. All gates green (build, 4/4 tests, D1 Δ +0.00pp). Spec + plan committed to main. Wave 8 (#46/#94/#100) is next per severity order. - 2026-05-15 — **Wave 9 closed docs-only via PR #120**, **Wave 6 shipped** (api#40 + extension#121). Both detailed in `memory/wave9_local_mode_resolution.md` and `memory/wave6_status.md`. From 057d66a3b0be6c738de46aa549c1550702bedd95 Mon Sep 17 00:00:00 2001 From: AndresL230 <190146319+AndresL230@users.noreply.github.com> Date: Fri, 29 May 2026 04:05:15 -0400 Subject: [PATCH 4/4] fix(wave3): key local findings dedup on detector id (CodeRabbit #131) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dedupeFindings keyed on `type:file:line`, which would collapse two distinct detectors that share a SuggestionType at the same site — specifically the new inline-parallel finding (type "batch", id `local-inline_parallel-…`) and a batch finding (id `local-batch-…`). Key on the detector-specific `finding.id` instead, falling back to type:file:line when absent, so genuine duplicates still collapse while distinct detectors are preserved. No registry method is currently both batchCapable and inlineParallelCapable, so this is defensive; benchmark Δ +0.00pp. --- src/scanner/local-waste-detector.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/scanner/local-waste-detector.ts b/src/scanner/local-waste-detector.ts index 3ff80f5..03b98ac 100644 --- a/src/scanner/local-waste-detector.ts +++ b/src/scanner/local-waste-detector.ts @@ -452,7 +452,11 @@ function dedupeFindings(findings: Array): LocalWasteFi const deduped = new Map(); for (const finding of findings) { if (!finding) continue; - const key = `${finding.type}:${finding.affectedFile}:${finding.line ?? 0}`; + // Key on the detector-specific id so distinct detectors that share a + // SuggestionType at the same site stay separate — e.g. batch + // (`local-batch-…`) vs inline-parallel (`local-inline_parallel-…`), which + // both emit type "batch". Fall back to type:file:line if an id is missing. + const key = finding.id || `${finding.type}:${finding.affectedFile}:${finding.line ?? 0}`; const existing = deduped.get(key); if (!existing || finding.confidence > existing.confidence) { deduped.set(key, finding);