This week on an app's studio page is the number a person judges their crew by, and it is read off the last 200 agent runs rather than counted.
studioApp (src/web/studio/app.ts:106-111) builds it as listRuns(db, repo.id, { kind: "agent", limit: 200 }).filter(r => r.createdAt >= week), then sums costUsd in JavaScript. listRuns clamps limit to 200 (src/core/runs.ts:354), so on a busy app the 201st run of the week simply does not exist: Runs and Spent under-report, and the page says nothing about it. decisionStats(db, repo.id, week) on the very next line is what this should look like — one bounded query, the database doing the counting — and crewStats (src/core/runs.ts:520) is the aggregate already written in this file.
This is the first of the four issues #91 was split into; it covers runs and spend only.
Acceptance
[ ] A new aggregate in src/core/runs.ts — one query, beside crewStats — returns the count of agent runs and the summed cost_usd for one repository since a given ISO instant.
[ ] studioApp uses it for Runs and Spent; listRuns(..., { limit: 200 }) is gone from that path.
[ ] Who may see spend does not change: the aggregate is still only read when ownerFacts is true, exactly as today.
[ ] A test seeds 250 agent runs inside the window and a run outside it, and asserts Runs reads 250 and Spent is the sum of the 250 — it fails on today's code.
[ ] bun test green; the app page's first byte does not get slower.
cost_usd is a float column in USD: SUM it in SQL the way crewStats does and round once where it is printed, so the money on the page does not drift from the ledger's. SUM over no rows is NULL — an app with no runs this week must read 0, not blank, and a test should say so.
This week on an app's studio page is the number a person judges their crew by, and it is read off the last 200 agent runs rather than counted.
studioApp(src/web/studio/app.ts:106-111) builds it aslistRuns(db, repo.id, { kind: "agent", limit: 200 }).filter(r => r.createdAt >= week), then sumscostUsdin JavaScript.listRunsclampslimitto 200 (src/core/runs.ts:354), so on a busy app the 201st run of the week simply does not exist: Runs and Spent under-report, and the page says nothing about it.decisionStats(db, repo.id, week)on the very next line is what this should look like — one bounded query, the database doing the counting — andcrewStats(src/core/runs.ts:520) is the aggregate already written in this file.This is the first of the four issues #91 was split into; it covers runs and spend only.
Acceptance
src/core/runs.ts— one query, besidecrewStats— returns the count of agent runs and the summedcost_usdfor one repository since a given ISO instant.studioAppuses it for Runs and Spent;listRuns(..., { limit: 200 })is gone from that path.ownerFactsis true, exactly as today.bun testgreen; the app page's first byte does not get slower.Files I expect to change
src/core/runs.ts,src/web/studio/app.ts,test/studio.test.ts.Risks
cost_usdis a float column in USD:SUMit in SQL the waycrewStatsdoes and round once where it is printed, so the money on the page does not drift from the ledger's.SUMover no rows isNULL— an app with no runs this week must read0, not blank, and a test should say so.Suggested: Mason.