mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-08-10 09:04:51 +00:00
daily-cache: document coverage-widening and rebuild-projects limits; cover empty-call turns
Review addendum: the one-day tz widening assumes an offset delta of at most 24h (date-line crossings are a known limit); day-level projects of skinny kept slices are dropped by the coverage rebuild by design. Adds a regression test pinning the existing skip of empty-call turns.
This commit is contained in:
parent
e07f3242f8
commit
269537ba0c
3 changed files with 97 additions and 1 deletions
|
|
@ -286,3 +286,50 @@ src/types.ts:190: skillBreakdown: Record<string, { turns: number; costUSD: numb
|
|||
```
|
||||
|
||||
STATUS: BLOCKED (`npx vitest run` exits 1: 48 pre-existing discovery/import suite failures plus unrelated 5-second CLI timeouts; tried isolated timeout reproduction, a clean HEAD-plus-lane snapshot with locked app dependencies and one worker, and all clean root tests with a 15-second diagnostic timeout)
|
||||
|
||||
## ADDENDUM
|
||||
|
||||
### Touched files
|
||||
|
||||
| file | added | deleted |
|
||||
|---|---:|---:|
|
||||
| `src/daily-cache.ts` | 8 | 1 |
|
||||
| `tests/day-aggregator.test.ts` | 42 | 0 |
|
||||
|
||||
### New test
|
||||
|
||||
| file | test |
|
||||
|---|---|
|
||||
| `tests/day-aggregator.test.ts` | `skips a timestamped empty-call turn without throwing or counting it at day level` |
|
||||
|
||||
### `git diff --exit-code e07f324 -- src/day-aggregator.ts`
|
||||
|
||||
Exit code: `0`
|
||||
|
||||
```text
|
||||
```
|
||||
|
||||
### `npx vitest run tests/daily-cache-carry-forward.test.ts tests/day-aggregator.test.ts`
|
||||
|
||||
Exit code: `0`
|
||||
|
||||
```text
|
||||
RUN v3.2.6 /Volumes/T8/Claude Projects/codeburn
|
||||
|
||||
✓ tests/day-aggregator.test.ts (13 tests) 16ms
|
||||
✓ tests/daily-cache-carry-forward.test.ts (45 tests) 318ms
|
||||
|
||||
Test Files 2 passed (2)
|
||||
Tests 58 passed (58)
|
||||
Start at 17:14:56
|
||||
Duration 740ms (transform 99ms, setup 49ms, collect 115ms, tests 334ms, environment 0ms, prepare 158ms)
|
||||
```
|
||||
|
||||
### `npx tsc --noEmit`
|
||||
|
||||
Exit code: `0`
|
||||
|
||||
```text
|
||||
```
|
||||
|
||||
STATUS: COMPLETE
|
||||
|
|
|
|||
|
|
@ -613,7 +613,10 @@ function setOwn<T>(target: Record<string, T>, key: string, value: T): void {
|
|||
/// provider's oldest fresh day (or the preceding day for timezone changes).
|
||||
export type SecondaryCoveragePolicy = {
|
||||
/// A timezone change can move a turn to an adjacent calendar day, so the day
|
||||
/// immediately before the fresh provider floor is covered as well.
|
||||
/// immediately before the fresh provider floor is covered as well. This
|
||||
/// assumes the old/new timezone offset delta is at most 24 hours. Crossing
|
||||
/// the date line can produce a 26-hour delta (UTC+14 to UTC-12) and move a
|
||||
/// bucket by two calendar days; that rare case is a known, accepted limit.
|
||||
widenByOneDay: boolean
|
||||
}
|
||||
|
||||
|
|
@ -669,6 +672,10 @@ export function mergeDayEntries(
|
|||
// Coverage can suppress one provider while another provider on the same
|
||||
// secondary-only day still needs carrying. Rebuild that day exclusively
|
||||
// from eligible slices so blocked totals cannot leak through day fields.
|
||||
// Day-level projects cannot be attributed per provider: when a kept skinny
|
||||
// slice has no slice.projects, its old day.projects entries are dropped.
|
||||
// Copying them wholesale could restore the blocked provider's project
|
||||
// totals, so this conservative loss is intentional.
|
||||
const copy = emptyDay(day.date)
|
||||
for (const [provider, slice] of providerEntries) {
|
||||
if ((!hasSliceData(slice) && !(slice.sessions ?? 0)) || !mayCarry(day.date, provider, slice)) continue
|
||||
|
|
|
|||
|
|
@ -168,6 +168,48 @@ describe('aggregateProjectsIntoDays', () => {
|
|||
})
|
||||
})
|
||||
|
||||
it('skips a timestamped empty-call turn without throwing or counting it at day level', () => {
|
||||
const projects: ProjectSummary[] = [
|
||||
makeProject({
|
||||
sessions: [{
|
||||
sessionId: 'empty-call-turn',
|
||||
project: 'p',
|
||||
firstTimestamp: '2026-04-09T10:00:00',
|
||||
lastTimestamp: '2026-04-09T10:00:00',
|
||||
totalCostUSD: 0,
|
||||
totalInputTokens: 0,
|
||||
totalOutputTokens: 0,
|
||||
totalCacheReadTokens: 0,
|
||||
totalCacheWriteTokens: 0,
|
||||
apiCalls: 0,
|
||||
turns: [{
|
||||
userMessage: 'no assistant response',
|
||||
timestamp: '2026-04-09T10:00:00',
|
||||
sessionId: 'empty-call-turn',
|
||||
category: 'coding',
|
||||
retries: 0,
|
||||
hasEdits: true,
|
||||
assistantCalls: [],
|
||||
}],
|
||||
modelBreakdown: {},
|
||||
toolBreakdown: {},
|
||||
mcpBreakdown: {},
|
||||
bashBreakdown: {},
|
||||
categoryBreakdown: {} as never,
|
||||
skillBreakdown: {} as never,
|
||||
}],
|
||||
}),
|
||||
]
|
||||
|
||||
let days: ReturnType<typeof aggregateProjectsIntoDays> = []
|
||||
expect(() => { days = aggregateProjectsIntoDays(projects) }).not.toThrow()
|
||||
|
||||
expect(days).toHaveLength(1)
|
||||
expect(days[0]).toMatchObject({ cost: 0, calls: 0, editTurns: 0, oneShotTurns: 0 })
|
||||
expect(days[0]!.categories).toEqual({})
|
||||
expect(days[0]!.providers).toEqual({})
|
||||
})
|
||||
|
||||
it('counts a session under its firstTimestamp date', () => {
|
||||
const projects: ProjectSummary[] = [
|
||||
makeProject({
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue