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
1 change: 1 addition & 0 deletions dashboard/src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions dashboard/src/pages/Endpoints.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down
12 changes: 7 additions & 5 deletions docs/superpowers/plans/PROGRESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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 | 🟡 | Code complete + reviewed; full benchmark Δ +0.00pp. Awaits manual EDH check of the two UI bits (confidence filter, sources badge). |
| **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) 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. |

## Wave 9 — Local-mode IPC architecture (CLOSED)
Expand Down Expand Up @@ -232,6 +232,8 @@ Post-foundation work organized into 10 waves + 2 standalones, tracked via `wave/

> Append `YYYY-MM-DD HH:MM — <one-line update>`. Newest at top.

- 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-28 — **Wave 1 (findings quality) code-complete** on `claude/superpowers-plugins-skills-1Yaij` (plan `docs/superpowers/plans/2026-05-28-wave1-findings-quality.md`). #112: comment-stripped guard window stops the `CACHE_GUARD`/`BATCH_GUARD` literal-word leak (URL-safe `//` lookbehind). #85: detectors carry a structural `riskScore`; single `deriveSeverity()` (hybrid floor+amplifier, not pure confidence×cost) + `computeCostImpact()` applied at all 5 `Suggestion`-construction sites; `costImpactUsd` internal-only; confidence filter in the sidebar. #84: `collapseSuggestions()` dedupes by `type::file::endpoint|line-bucket`, unions `sources`, max confidence, AI-preferred description; `mergeAiSuggestions` collapses instead of dropping; "detected by N sources" badge. Subagent-driven (impl + spec + code-quality review per unit). Gates: full `test:scanner` green, full benchmark Δ +0.00pp on all 5 metrics. **Pending:** (1) manual EDH check of the two UI bits; (2) follow-ups — pre-existing `scope === "internal"` guard divergence between the two `buildAggressiveSuggestions` copies (scan-results.ts lacks it), and dead `SourceBadge` + duplicated badge inline-style in `ResultsPage.tsx` (extract a shared secondary-badge).
- 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.
Expand Down
4 changes: 4 additions & 0 deletions src/intelligence/__tests__/builder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ run("buildRepoIntelligenceSnapshot normalizes paths, stores null providers, and
library: "openai",
cacheCapable: false,
batchCapable: false,
inlineParallelCapable: true,
},
{
file: "./src/payments.ts",
Expand Down Expand Up @@ -127,11 +128,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);
Expand Down
1 change: 1 addition & 0 deletions src/intelligence/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
1 change: 1 addition & 0 deletions src/intelligence/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions src/scan-results.ts
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,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;
Expand Down Expand Up @@ -547,6 +548,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,
Expand Down Expand Up @@ -580,6 +582,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;
Expand Down
44 changes: 43 additions & 1 deletion src/scanner/local-waste-detector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,43 @@ 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,
riskScore: score,
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,
};
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

function detectRedundancyFinding(relativePath: string, site: MatchedCallSite, clientInitCount: number): LocalWasteFinding | null {
const ext = path.extname(relativePath).toLowerCase();
if (!JS_TS_EXTENSIONS.has(ext)) return null;
Expand Down Expand Up @@ -415,7 +452,11 @@ function dedupeFindings(findings: Array<LocalWasteFinding | null>): LocalWasteFi
const deduped = new Map<string, LocalWasteFinding>();
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);
Expand All @@ -433,6 +474,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));
Expand Down
4 changes: 4 additions & 0 deletions src/scanner/patterns/openai-compatible.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -187,6 +190,7 @@ export const openAiCompatibleMatcher: LineMatcher = {
host,
streaming,
batchCapable,
inlineParallelCapable,
cacheCapable,
rawMatch: match[0],
});
Expand Down
1 change: 1 addition & 0 deletions src/scanner/patterns/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export interface ApiCallMatch {
loopContext?: boolean;
streaming?: boolean;
batchCapable?: boolean;
inlineParallelCapable?: boolean;
cacheCapable?: boolean;
inferredCostRisk?: string[];
rawMatch?: string;
Expand Down
1 change: 1 addition & 0 deletions src/scanner/patterns/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
12 changes: 12 additions & 0 deletions src/test/local-waste-detector.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,18 @@ 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/);
});

run("#112: bare 'cache' in a comment does not suppress a cache finding", () => {
const text = [
"// we should cache this someday but do not yet",
Expand Down
14 changes: 14 additions & 0 deletions src/test/scanner-patterns.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
1 change: 1 addition & 0 deletions webview/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading