Commit graph

1482 commits

Author SHA1 Message Date
iamtoruk
911bd3f486 fix(plans): say monthly budget, not calendar month, and fit 80 columns
The budget window comes from computePeriodFromResetDay, which builds an
anniversary period from plan.resetDay (1-28, settable per plan with
`codeburn plan set --reset-day`). "Calendar-month budget" and "Next
calendar reset" are therefore wrong for anyone who moved the reset day,
which is the same class of inaccuracy this change set exists to remove.
Say "budget" and "Next budget reset" instead, and use one wording across
the TUI and the desktop cards.

Both TUI lines truncate end-first at the terminal width. The headline had
grown past the point where an 80-column terminal still showed the
percentage, so it drops "vs ... /mo" for "/ $300.00 budget", and the
status line drops the clause repeating "budget" from the headline. At 80
columns the longest label (custom plans carry their provider) now fits
the percentage, and the status line still shows the projection.
2026-08-19 11:30:07 -07:00
iamtoruk
a4384d28f2 Merge remote-tracking branch 'origin/main' into pr1041-rebase 2026-08-19 11:19:11 -07:00
Resham Joshi
c9ad778cbc
Merge pull request #1036 from getagentseal/ci/upgrade-path-verification
ci: verify the 0.9.20 upgrade path on every platform
2026-08-19 11:10:41 -07:00
Aditya Vikram Singh
610c70ecb9 fix(plans): update desktop FX budget-copy assertion 2026-08-19 20:53:18 +05:30
Aditya Vikram Singh
9646890638 fix(plans): call sticker-price bars a budget, not live quota
SuperGrok Heavy is a $300/mo preset compared to parsed API-equivalent
spend on a calendar month. CodexBar's live weekly window is a different
class. TUI and desktop copy now say budget / calendar month and deny
a live provider window. No Grok Connect is invented.
2026-08-19 20:07:02 +05:30
iamtoruk
a336b6bbe5 Merge remote-tracking branch 'origin/main' into ci/upgrade-path-verification 2026-08-18 12:48:30 -07:00
Resham Joshi
bb1974dab8
Merge pull request #1035 from getagentseal/fix/scoped-save-republish
Some checks failed
CI / semgrep (push) Waiting to run
Tests / test (22) (push) Waiting to run
Tests / test (22.13.0) (push) Waiting to run
Windows Menubar CI / check (ubuntu-latest) (push) Has been cancelled
Windows Menubar CI / check (windows-latest) (push) Has been cancelled
cache: stop republishing unchanged month shards on ranged runs
2026-08-18 12:48:16 -07:00
iamtoruk
3093887a14 Merge remote-tracking branch 'origin/main' into fix/scoped-save-republish
# Conflicts:
#	CHANGELOG.md
2026-08-18 12:48:02 -07:00
Resham Joshi
6325b428d4
Merge pull request #1031 from getagentseal/fix/pre-release-hardening
hardening: cache-scope escape hatch, promptSource on large lines, distinct Grok 4.5 rows
2026-08-18 12:47:59 -07:00
iamtoruk
ed9057f230 Merge remote-tracking branch 'origin/main' into fix/scoped-save-republish
# Conflicts:
#	CHANGELOG.md
2026-08-18 12:43:52 -07:00
iamtoruk
10ceb467a8 Merge remote-tracking branch 'origin/main' into fix/pre-release-hardening
# Conflicts:
#	CHANGELOG.md
2026-08-18 12:43:39 -07:00
Resham Joshi
a1ef0cf975
Merge pull request #1033 from getagentseal/fix/daily-cache-partial-survival
daily-cache: never lose a day's history when its transcripts have partially aged out
2026-08-18 12:42:51 -07:00
iamtoruk
ab98a04c51 fix(cache): stop republishing month shards a scoped run never read
A date-ranged query loads only the months its range can report on, so a
file in an out-of-range month has no visible cache entry: the reconcile
re-parses it and writes the identical entry back, which marks a bucket
this run never loaded dirty. The save then merged and republished that
month under a fresh nonce name on every run, with byte-identical
content.

A merge into an unloaded month that neither adds, changes nor removes an
entry now keeps the published shard.

Fixes #1032
2026-08-18 12:29:07 -07:00
iamtoruk
e9d922ca2d fix(daily-cache): keep history for days whose transcripts partly aged out
The never-lose contract carried a (day, provider) slice forward only when
the re-derivation found nothing for it. Transcripts expire per file, so a
mostly-aged-out day still gets a few turns from surviving later files: the
fresh slice came back non-empty but truncated and replaced the full cached
one (a real cache lost $2,765.75 / 19,209 calls / 520 sessions over 13 days
on the 17 -> 19 upgrade).

A fresh slice now replaces a settled baseline slice only when it carries at
least as many calls. Comparison is on calls alone - cost and tokens are
re-priced accounting on the same evidence, and session counts drift down on
healthy days. Days inside a 7-day settle window stay authoritative. The
tz-change re-derive gets the exact form of the rule: the subtraction residual
is added on top of a data-carrying fresh slice instead of being dropped. The
cross-file adoption union is unchanged.
2026-08-18 12:23:29 -07:00
iamtoruk
34726fb63c ci(upgrade-path): reference #1032 in the shard-republish note 2026-08-18 12:20:34 -07:00
iamtoruk
b2e59c3a2f ci: fail the upgrade path when a partially aged-out day loses its slice
Claude Code deletes its transcripts after ~30 days, so between one run and
the next a day can go from fully sourced to PARTIALLY sourced. On such a
day the daily cache re-derives a smaller slice from the surviving files
and that slice REPLACES the baseline one rather than being unioned with
it, so the aged-out portion is lost. A day that aged out completely is
carried forward correctly, which is what makes this a hole in never-lose
rather than a missing feature.

The check ages the corpus the way retention does: two days keep a single
anchoring transcript each, a third loses every one. It then compares the
(date, provider) slices in daily-cache.v19.json against the ones the
0.9.20 baseline recorded in daily-cache.v17.json, requiring the partial
days never to shrink and the fully sourceless day to come back exactly.

This is a hard failure, not a note. It is expected to be red until the
fix lands: on the generated corpus the two partial days currently lose
76.6% and 56.8% of their cost, while the fully sourceless control day
returns to the cent.

It runs last and on its own cache dir, so mutating the corpus cannot
disturb the payload parity comparison. The shard-republication defect
found earlier stays a note, now naming its pending follow-up issue.
2026-08-18 12:19:45 -07:00
iamtoruk
74d718fe03 ci: verify the 0.9.20 upgrade path on every platform
Everyone upgrading from the last published CLI crosses the session-cache
v7 -> v9 re-layout (#1005/#1007) and the daily-cache v17 -> v19
re-derivation (#1015) on their first run. That path was covered by unit
tests on one platform against caches the tests wrote themselves.

`npm run verify:upgrade` (and the matching matrix job over
{ubuntu, windows, macos} x node {22.13.0, 22}) instead installs the real
codeburn@0.9.20 into an isolated global prefix, points it at a generated
seven-provider corpus in a HOME whose path contains a space, and lets it
write a genuine session-cache.v7.json + daily-cache.v17.json. This build
then runs against that same cache dir, installed the same way, so
dist/parse-worker.js has to resolve from an entry point outside the
checkout.

It asserts the v7 file is retired, the v9 envelope and shards publish,
the daily history does not shrink, and per-provider calls/tokens/cost
match the baseline exactly for claude, codex, gemini, kiro and cursor.
grok is reported rather than asserted (its accounting changed in #1015)
and dsh is required to be new. It also smoke-tests serve --stdio against
the one-shot payloads, pins CODEBURN_PARSE_WORKERS to 0 and 3 and
requires identical shards and payloads either way, and checks that a
second run re-parses nothing.
2026-08-18 12:14:55 -07:00
iamtoruk
37796a568e models: distinguish grok-4.5-build from grok-4.5 in reports
Reports bucket rows by model id and label them afterwards, so the two ids
collapsing onto one display name printed what looked like the same row twice
with different numbers. Give the variant its own SHORT_NAMES entry, which the
longest-first match picks over the grok-4.5 prefix. Display only: ids are
untouched, so nothing re-parses and no cost moves.

Fixed in the shared table rather than the grok provider so the menubar and
model-breakdown, which call getShortModelName directly, get it too.

Fixes #1029.
2026-08-18 12:01:12 -07:00
iamtoruk
39075edd50 parser: keep promptSource on lines over 32 KB
parseLargeJsonl dropped promptSource for exactly the lines SDK-generated
prompts live on, so the recurring-context detector regex-scanned the ends of
the raw line for it. Add the field to LARGE_ROOT_FIELDS (tiny scalar,
add-only, isSidechain already there) and delete the workaround: it read only
2 KB from each end, so a flag further in was missed.

No cache change: optimize scans the raw JSONL each run, so promptSource never
has to persist on CachedFile.

Fixes #1030. With #994 this closes #1023.
2026-08-18 12:00:29 -07:00
iamtoruk
087656bacc cache: CODEBURN_CACHE_SCOPE=all forces a full shard read
The month-scoped load a ranged query takes is a behaviour change on a warm
cache with no way back except deleting it. Drop the scope in loadCache, the
one place every caller (including the resident serve) routes through, so a
suspect scoped read can be compared against a full one in place.

Read policy only: deliberately not in PROVIDER_ENV_VARS, so setting or
unsetting it invalidates nothing.
2026-08-18 11:59:21 -07:00
Resham Joshi
564c618829
Merge pull request #1014 from ozymandiashh/fix/997-session-legend-titles
fix(dash): lead the session legend with the session title
2026-08-18 11:49:20 -07:00
Resham Joshi
2766224bf5
Merge pull request #1017 from ozymandiashh/fix/sqlite-readonly-parent
fix(sqlite): survive a read-only database parent instead of reporting no sessions
2026-08-18 11:48:51 -07:00
iamtoruk
ca3b692acb Merge remote-tracking branch 'origin/main' into pr1014-rebase
# Conflicts:
#	CHANGELOG.md
2026-08-18 11:42:05 -07:00
iamtoruk
cfc0abb50f Merge remote-tracking branch 'origin/main' into pr1017-rebase
# Conflicts:
#	CHANGELOG.md
2026-08-18 11:41:52 -07:00
Resham Joshi
b9521012eb
Merge pull request #1015 from ozymandiashh/fix/998-grok-authoritative-usage
fix(grok): read the CLI's own completed-turn usage instead of estimating it
2026-08-18 11:40:38 -07:00
iamtoruk
60feaa8651 fix(sqlite): only reach for an immutable URI where node:sqlite honours one
node:sqlite enables SQLITE_OPEN_URI from Node 22.15 on. Below that -- 22.13 is
the package floor -- a `file:...` location is a literal filename, so the
immutable open failed as CANTOPEN and the copy quietly stood in for it. That
was the right outcome by accident; the test asserted the newer behaviour and
failed on the floor.

The support question is now asked once per process, with an in-memory URI that
touches no filesystem whichever answer comes back, and the immutable open is
attempted only when the answer is yes. The test asks the same question rather
than skipping, so both CI lines assert something: rows are correct either way,
in place where URI filenames work and from a copy where they do not.
2026-08-18 10:38:01 -07:00
iamtoruk
9bfe9cc492 fix(sqlite): read a read-only parent in place when there is no WAL to lose
Four things the copy fallback got wrong.

A database whose -wal is absent or empty has no un-checkpointed frames, so
there is nothing to go stale and nothing worth copying: immutable=1 opens the
source in place and SQLite skips the -shm it cannot create. The copy is now
taken only when a non-empty -wal exists, which is the case where dropping it
would lose rows.

A copy is published under a name carrying its fingerprint, so refreshing one
never has to unlink a file another process may still hold open, which Windows
does not allow. The -wal is published before the database so a reader can
never see the database without the sidecar holding its newest rows, and losing
a publish race to an identical copy is not an error. That removes the metadata
sidecar: the name is the fingerprint.

Superseded copies are evicted rather than overwritten -- the one in use plus at
most one predecessor, and anything untouched for a day, which is also what a
source path that no longer exists looks like. Reuse touches the copy, so its
mtime is last use.

A cache directory that cannot be written no longer fails the same way the bug
did. It emits the once-per-database notice naming the database and the reason
before the database is skipped, instead of going quiet.
2026-08-18 10:28:31 -07:00
iamtoruk
ada9382833 test(grok): pin the unpriced-model branch instead of inheriting it from the snapshot
Both multi-model tests assert that chooseAuthoritativeModel skips a
modelUsage id it cannot price and falls back to a priced one. They got that
"cannot price" from the bundled LiteLLM snapshot happening not to carry
grok-4.6-build, so `npm run build` - which re-fetches the snapshot - flipped
both assertions the moment an xai/grok-4.6 entry appeared upstream and the
prefix match started pricing the id.

Stub getModelCosts for that one id instead. The reporter's real ids stay in
the fixtures, so the tests still document the #998 case, and calculateCost is
left alone: module-internal calls are not intercepted, so cost assertions
keep pricing off the real tables.

Verified by re-running both files against a refreshed snapshot that does
carry xai/grok-4.6; 26/26 pass where they previously failed.
2026-08-18 10:26:06 -07:00
iamtoruk
7bb4e7f8e1 docs(grok): state the mixed-session drop and the global daily re-derivation plainly
The daily-cache re-derivation test seeded v18, a version that only ever
existed as an unreleased draft of this change. Seed the shipped v17 so the
test models the 17 -> 19 upgrade path users actually hit, and rename it: the
bump re-derives every day for every provider, not just Grok, because the
daily cache has no per-provider invalidation. The Grok day stays as the
fixture since Grok is what the bump exists to correct.

The changelog entry now says outright that Grok totals change materially on
upgrade (150K -> 96.3M cache-read tokens on a 568-session corpus), that a
turn without a turn_completed record inside an otherwise-covered session is
dropped rather than estimated, and that the one-time daily re-derivation
reads the warm session cache and keeps the superseded file. The
context-bloat denominator fix moves to Fixed and names the providers it
corrects.

docs/providers/grok.md gets the same undercount warning in the token model
and a matching entry under Quirks.
2026-08-18 10:22:36 -07:00
iamtoruk
267749b112 fix(sqlite): fall back for a read-only parent that reports SQLITE_CANTOPEN
A read-only parent with a -wal but no -shm fails as SQLITE_CANTOPEN (14),
not SQLITE_READONLY (8), so the fallback never ran and the un-checkpointed
rows in the -wal stayed invisible. openReadonlyCache already re-throws the
original error when the database itself is missing, which is the other
CANTOPEN, so widening the trigger keeps that case distinguishable.

Also stops copying the source -shm: SQLite rebuilds the wal-index from the
-wal in the writable cache directory, so the copy is dead weight.
2026-08-18 10:18:03 -07:00
iamtoruk
05a2afb064 Merge remote-tracking branch 'origin/main' into pr1015-rebase
# Conflicts:
#	src/main.ts
#	src/optimize.ts
#	src/session-cache.ts
#	tests/models-report.test.ts
2026-08-18 10:09:05 -07:00
iamtoruk
4dc53149bd Merge remote-tracking branch 'origin/main' into pr1014-rebase
# Conflicts:
#	CHANGELOG.md
2026-08-18 10:08:28 -07:00
iamtoruk
cc6e048479 Merge remote-tracking branch 'origin/main' into pr1017-rebase 2026-08-18 10:07:22 -07:00
Resham Joshi
1808d36dd0
Merge pull request #996 from avs-io/codex/issue-969-unpriced-reconcile
fix(models): make unpriced guidance actionable
2026-08-18 10:04:01 -07:00
iamtoruk
6b427d72ce fix(models): honest unpriced guidance, shared ID sanitizing, readable narrow warning
The unpriced-models pointer told every user to run `codeburn model-alias`. A
subscription or flat-rate model is correctly $0, and mapping it onto another
model's per-token rate invents spend that was never billed (#968), so the hint
now states the condition instead of the instruction.

`sanitizeModelForDisplay` guarded only the `--unpriced` path, leaving every
other command and format rendering provider-supplied IDs unfiltered. It moves
to the `formatModel` closures in models-report and audit-report, the two sites
every renderer routes through, covering the raw-ID fallback as well. The
`--unpriced` override stays: it bypasses that path deliberately, because
`model-alias` keys on the raw ID, not the friendly name. README says so.

Below 45 columns of panel the dashboard warning dropped its marker and count
and printed a bare command, so a narrow terminal gave no signal that anything
was wrong. `! N: codeburn models --unpriced` is 31 characters and fits.
2026-08-18 09:58:25 -07:00
iamtoruk
81eb7310a3 merge origin/main into pr996
Resolve src/main.ts to main's #1016 rank-map --unpriced/--top implementation and
drop this branch's inline comparator and topN hunk entirely.
2026-08-18 09:57:06 -07:00
Resham Joshi
8ef3468dca
Merge pull request #1016 from ozymandiashh/fix/969-unpriced-top-and-docs
fix(models): let --unpriced survive --top, and document the flag
2026-08-18 09:53:17 -07:00
iamtoruk
d8213e5827 Merge remote-tracking branch 'origin/main' into pr1016
# Conflicts:
#	CHANGELOG.md
2026-08-18 09:49:08 -07:00
Resham Joshi
215f5011a1
Merge pull request #994 from avs-io/codex/issue-974-sidechains
fix(optimize): exclude sidechains from behavioral signals
2026-08-18 09:47:27 -07:00
iamtoruk
7fa7b36d00 merge origin/main into pr994 2026-08-18 09:41:26 -07:00
iamtoruk
29b531fced optimize: keep sidechain tool calls in the junk-read and read:edit signals
Only duplicate-reads has a structural reason to skip them: a subagent
starts on a fresh context, so re-reading what its parent read is a
necessary read, not a repeat. Reading node_modules or editing without
reading is the same waste whoever does it, and the CLAUDE.md rule both
findings suggest binds subagents too - filtering them there discarded
most of the evidence on a subagent-heavy corpus.
2026-08-18 09:38:11 -07:00
Resham Joshi
b1f85195e6
Merge pull request #995 from avs-io/codex/issue-989-windows-installer
ci(desktop): guard Windows installer artifacts
2026-08-18 09:35:00 -07:00
iamtoruk
1d11f22b2d Merge remote-tracking branch 'origin/main' into pr995-rebase 2026-08-18 09:30:53 -07:00
Resham Joshi
c2cae31438
Merge pull request #992 from avs-io/codex/issue-987-durable-history
fix(parser): retain discovered durable history
2026-08-18 09:30:40 -07:00
iamtoruk
595225da34 changelog: note the one-time lifetime jump when retained history reappears (#987) 2026-08-18 09:27:21 -07:00
iamtoruk
8ecd14ccdf changelog: sidechains leave the optimize session population (#974) 2026-08-18 08:59:51 -07:00
iamtoruk
2d35c8fa24 test(parser): cover durable retention through a month-scoped refresh 2026-08-18 08:52:43 -07:00
iamtoruk
2d4c00a9a8 merge origin/main into pr996
Keep both: #985's --unpriced filter (in main) and #996's top/ordering/sanitize/dashboard changes.
2026-08-18 08:50:38 -07:00
iamtoruk
eb17a03610 merge origin/main into pr994 2026-08-18 08:50:09 -07:00
iamtoruk
b2d0f29b6c Merge origin/main into pr992 2026-08-18 08:49:13 -07:00