Commit graph

1447 commits

Author SHA1 Message Date
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
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
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
iamtoruk
b8ca581d02 Merge remote-tracking branch 'origin/main' into pr995-rebase 2026-08-18 08:49:11 -07:00
Resham Joshi
05eeab2fce
Merge pull request #991 from avs-io/codex/issue-975-connector-guidance
fix(optimize): separate connector and local MCP guidance
2026-08-18 08:44:32 -07:00
iamtoruk
d5d42e4d26 fix(optimize): disambiguate connector namespaces against local MCP config
The prefix alone could not tell a claude.ai connector from a local MCP
server that happens to be named claude_ai_*, so the latter lost its
removal command and its apply plan. A namespace is now a connector only
when no readable local config claims that exact name; localMcpServerNames
supplies the set from the same files the remove plan edits (settings.json,
.mcp.json, and ~/.claude.json top-level plus per-project mcpServers).

A prefixed name that local config does own stays local: normal removal
command, apply plan, class fix, full savings attribution. Because the
transcript still cannot rule out a same-name connector, the finding adds a
manual note about it instead of asserting the server is one. Config that
cannot be read contributes no names, which leaves every prefixed namespace
on the conservative connector path.
2026-08-18 08:39:53 -07:00
iamtoruk
c07a5a795c changelog: own the connector split and the per-session MCP cost model (#975) 2026-08-18 08:29:43 -07:00
iamtoruk
cddf5d5a53 test(optimize): pin per-session MCP schema charging without connectors
The rewritten cost pass charges each session only for the schemas that
session loaded, which changes local-only estimates too (on a real corpus
the mcp-low-coverage estimate roughly halves). Pin it so the change is
deliberate rather than a side effect of the connector split.
2026-08-18 08:27:22 -07:00
iamtoruk
117aa833cc fix(optimize): scope the apply-able subtotal to the local MCP subset
A mixed local + claude.ai connector finding is class `fix`, but `--apply`
only mutates the local servers. classTotals now credits the `fix` group
with `applyTokensSaved` when present, so the "Fix now (apply-able)"
subtotal, the "apply-able: ~$X" headline and `summary.byClass.fix` (CLI,
TUI and desktop all read these) describe what apply can actually recover.
The finding keeps the whole opportunity in its own `tokensSaved`.

Also fixes the desktop connector fixture, which predated the class/basis
fields, and adds class-level coverage: connector-only findings resolve to
`nudge` (no apply payload), a local server named like a connector stays
manual-only, and local-only findings keep their full subtotal.
2026-08-18 08:22:33 -07:00
iamtoruk
c5df60e69a merge: main into connector guidance branch
Kept both sides' act-report tests: the PR's partial-action baseline
capture block and main's applied-fix verdict block.
2026-08-18 08:17:25 -07:00
Resham Joshi
9f0bbd0389
Merge pull request #1028 from getagentseal/fix/menubar-login-item-smappservice
menubar(mac): register the login item via SMAppService, no System Events prompt
2026-08-18 08:15:03 -07:00
Resham Joshi
11bc6d0e8e
Merge pull request #1027 from getagentseal/feat/menubar-install-windows
menubar: install and launch the Windows menubar from codeburn menubar
2026-08-18 08:14:59 -07:00
iamtoruk
185d6b3b31 mac: register the login item with SMAppService
The menubar told System Events to make its login item, so macOS asked for
Automation access on first launch. SMAppService.mainApp does it in-process
with no Automation grant. No AppleScript fallback: a failure must not bring
the prompt back. Package floor is macOS 14, so the 13+ API needs no
availability guard.

Fixes #1026
2026-08-18 07:48:03 -07:00
iamtoruk
527e58078d menubar: install and launch the Windows tray app from codeburn menubar
Shares the mac release resolution behind a per-platform spec (tag prefix, asset name, error text), so the Windows path reuses the pinned-version URL, the release-API fallback scan, the retrying download and the sha256 verify unchanged. Windows then runs msiexec out of %SystemRoot%\System32 with /i /passive /norestart, treats 3010 and 1602 as non-failures, and launches the exe named by the product's Uninstall registry key.
2026-08-18 06:55:09 -07:00
Resham Joshi
9905e8f8d0
Merge pull request #1024 from getagentseal/feat/windows-menubar
Some checks are pending
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) Waiting to run
Windows Menubar CI / check (windows-latest) (push) Waiting to run
windows: Windows menubar (Tauri tray app) at 0.9.20, with CI and release workflow
2026-08-18 04:45:46 -07:00
Resham Joshi
bfd21db37a
Merge pull request #1025 from getagentseal/test/vitest-global-timeout
test: raise vitest testTimeout to 30s
2026-08-18 04:41:18 -07:00
iamtoruk
76c63cbfd1 windows: cfg-gate the tray badge so the Linux build has no dead code
The spend badge is a second tray icon carrying the number as its bitmap, which
only the Tauri tray backend provides; Linux runs its own SNI tray and has no
equivalent. The module was compiled there anyway, so every item in it - and the
two tray ids in lib.rs - tripped dead_code under clippy -D warnings on the
ubuntu leg.

- `mod tray_badge` and TRAY_ID / BADGE_TRAY_ID are now cfg(not(linux)), so the
  code is absent on Linux rather than present and unused.
- `set_tray_badge` reports the badge as unsupported on Linux instead of
  returning a success that never happened.
- The frontend hides the control wherever it is unsupported, behind
  TRAY_BADGE_SUPPORTED in lib/platform.ts - one constant, three call sites
  (the settings row, the footer menu item, and the effect that would otherwise
  invoke the command).
- AppState's `linux_tray` field is dropped: it was written and never read, which
  is the same lint one file over. init_tray_linux already owns the handle.

Checked rather than reasoned: `cargo clippy --all-targets -- -D warnings` passes
on macOS, and a scratch copy of the crate with the linux/macos cfg arms swapped
(so a normal macOS clippy selects everything a Linux build would keep, and drops
everything it would drop) also passes. Reinstating the ungated `mod tray_badge`
in that copy reproduces the exact CI failure, so the check is real. It stubs
tray_linux.rs, whose ksni and png deps do not build here - that file is still
only covered by CI's ubuntu leg.
2026-08-18 04:37:27 -07:00
iamtoruk
30037b331f test: raise vitest testTimeout to 30s
The 5s default failed three unrelated real-I/O tests on the CI Node-22 runner
in two days (dashboard optimize scan, codex resume differential, OTel DB
prune) purely from runner load. Hung tests still fail at 30s.
2026-08-18 04:32:20 -07:00
iamtoruk
562bab65ba docs: surface the Windows menubar
README gains a Windows section next to the macOS and GNOME ones, a download
badge on the menubar card, and an honest note that the Tauri tray builds on
Linux but is unreleased there. docs/architecture.md picks up windows/ in the
surfaces diagram and gets a section covering the crate layout, the PATH and
System32 spawn rules, and the Claude quota parity.

`codeburn menubar` on Windows now points at the windows-v release page instead
of failing with "macOS only". The generalized installer from the source branch
is not brought over: it is 1182 commits behind this file and would drop the
proxy support, retry/backoff, checksum and bundle verification, and persistent
CLI path handling that landed since.
2026-08-18 04:14:05 -07:00
iamtoruk
120747d2fa ci: build and release the Windows menubar
windows-menubar-ci.yml runs on windows/** changes: tsc, clippy with -D warnings,
and cargo test on both windows-latest and ubuntu-latest, plus a release-profile
`tauri build --no-bundle` on Windows. The Linux leg exists because most of the
crate's cfg(windows) code cannot be compiled anywhere else, so the reverse -
keeping the ksni paths and every shared helper clean off Windows - has to be
checked somewhere too.

release-menubar-windows.yml mirrors release-menubar.yml: a `windows-v*` tag (or
a manual dispatch) builds the MSI and publishes it, with a sha256, to a
"Windows Menubar vX" release.

The two release-desktop-*.yml files on the source branch are deliberately not
imported: their tag names and "Desktop" release titles would collide with the
Electron app in app/, which already owns `desktop-v*`.
2026-08-18 04:11:32 -07:00
iamtoruk
7e57fb8d4f windows: fix the security and correctness findings from the import audit
Process spawning (Windows searches the current directory before PATH):

- reg.exe and cmd.exe are now spawned by absolute System32 path via
  cli::system_command, which also carries the CREATE_NO_WINDOW flag the three
  previous copies each set by hand. The tray badge re-ran `reg query` on every
  refresh, so this was the most reliably reachable planted-binary path.
- "Connect Claude" resolves the claude binary itself instead of handing a bare
  name to the console shell.
- CLI lookup ignores empty and relative PATH entries. `;;` or a trailing `;`
  used to yield PathBuf::from("").join("codeburn.cmd"), a current-directory
  lookup, at startup. The filter lives in one place (find_in_dirs) that every
  search - codeburn, claude, and the Linux terminal probe - goes through.
- The Linux terminal path re-validates the whole command against the argument
  allowlist before joining it into the string `bash -lc` parses; anything that
  fails falls through to the argv-only detached spawn.

CLI version gate:

- MIN_CLI_VERSION moves from 0.7.0 to 0.9.9, the first release accepting
  `status --format menubar-json --no-optimize` (every quiet refresh passes it)
  and emitting all the payload fields the popover reads.
- The gate is probed on mount, before the first fetch. It previously only ran
  when a fetch failed with the literal "CLI not found", so an old CLI produced
  a payload whose missing history.daily threw and blanked the popover.
- A successful fetch no longer flips an incompatible CLI to compatible, and the
  settings panel no longer probes on its own - App owns the verdict, so a
  transient probe failure there cannot drop a working app onto the setup screen.
- The payload reads App makes are optional now, so a surprising payload lands on
  an empty state rather than a blank window.

Refresh cadence, mirroring mac RefreshCadence.swift: 60s with optimize findings
while the popover is visible, 120s today/all without them while it is hidden,
and an immediate refresh on show when the visible key is stale. Every hide path
in lib.rs now goes through mark_hidden so the debounce stamp and the frontend
signal cannot drift. Previously it was 60s with optimize regardless of
visibility - about 2880 CLI spawns a day.

Claude quota (plan.rs) stops calling the token refresh endpoint. Claude's
refresh token is single-use and rotates, so spending it invalidated the token
Claude Code itself holds and broke the user's login. Parity with
ClaudeCredentialStore.refreshAfter401: re-read Claude's own store for a token it
has already rotated, and report a transient failure when there is not one yet.

Smaller:

- Snapshot writes refuse a symlinked target and are 0600 on unix, mirroring
  mac SafeFile; no home directory now means no snapshots rather than a file
  dropped in whatever directory the tray was launched from.
- The Windows config lock keeps its file handle open, so the stale sweep can
  only ever unlink a lock whose owner is gone. The doc comment now says what the
  lock actually buys (the CLI never takes it).
- "updated Xs ago" is only stamped by a fetch of the key on screen.
- External links go through tauri-plugin-opener instead of target=_blank.
- The tray badge font is loaded once into a OnceLock instead of read and parsed
  on every render.
- autostart's shared import and constant are cfg-gated so clippy is clean on
  every OS.

Adds unit tests for the PATH filter and the version gate.
2026-08-18 04:10:07 -07:00
iamtoruk
b199af182e windows: import the Tauri tray app as the Windows menubar
Brings the Tauri 2.x tray popover from #1022 onto main as windows/, mirroring
mac/. Product name, bundle identifier, and version line up with the macOS
menubar (org.agentseal.codeburn-menubar, 0.9.20); the crate is renamed off
"desktop" so it no longer collides with the Electron app in app/.

Linux (ksni) stays compiled and dev-usable but is documented as experimental:
gnome/ is the shipping Linux surface.

The five src/ CLI commits on that branch are dropped - they re-implement a
daily-bucketing fix main already carries.
2026-08-18 04:00:59 -07:00
Resham Joshi
a0ada25104
Merge pull request #1021 from getagentseal/feat/optimize-recurring-context
optimize: detect recurring context pasted at the start of sessions
2026-08-18 03:35:21 -07:00
iamtoruk
b249ba1ffe Merge remote-tracking branch 'origin/main' into feat/optimize-recurring-context
# Conflicts:
#	CHANGELOG.md
2026-08-18 03:31:16 -07:00
Resham Joshi
95d5fa6269
Merge pull request #1020 from getagentseal/feat/optimize-verify-after-apply
optimize: re-measure applied fixes and report worked / partial / no effect
2026-08-18 03:30:54 -07:00
iamtoruk
e3a55cdf20 optimize: count only human pastes as recurring context
An SDK session's opening prompt and a subagent's task prompt are written by
a program: they repeat by design and have no home in CLAUDE.md. Both are
flagged on the entry, but a user entry over the parser's large-line
threshold comes back without its root flags - routine for generated prompts,
which are exactly the long ones - so the markers are read off the ends of
the raw line, where the fields sit either side of the oversized message.
2026-08-18 03:27:10 -07:00
iamtoruk
f8bc9b2594 docs: cover what happens after optimize --apply
docs/optimize.md gains an "After you apply" section (the four verdicts,
--auto-revert, what is never auto-reverted); README and CHANGELOG follow.
2026-08-18 03:15:12 -07:00
iamtoruk
3936284811 optimize: show the applied-fix verdicts in the TUI and desktop app
Same section, compact: one line per still-applied fix with the verdict
glyph, and the undo command for the ones that measured nothing. The app
reads appliedFixes[] off the optimize JSON, tolerating its absence from
an older CLI.
2026-08-18 03:15:12 -07:00
iamtoruk
52eb9fdb8d optimize: re-measure applied fixes on every run, with --auto-revert
Every still-applied journal entry now comes back with a verdict on the
next optimize run: worked (>=70% of its window-scaled estimate
realized), partial, no-effect (printed with its undo command), or
measuring while it is younger than the 3-day window. The verdicts come
off the rows act report already computes, so there is one
reconciliation, not two; the AppliedFix type and its formatter live in
act/types.ts so the optimize renderer can use them without importing
report.ts back into optimize.ts.

--auto-revert undoes the no-effect entries through the same code path as
codeburn act undo. It never touches partial or measuring entries, and
never a claude-md-rule - those land in whatever directory the user
happened to be in, the same reason --yes skips them.

--apply now names when the re-measure happens, and --format json carries
appliedFixes[] (add-only).
2026-08-18 03:15:07 -07:00
iamtoruk
8d8848d805 optimize: detect recurring context pasted at the start of sessions
Groups sessions by their opening block (whitespace/ANSI-normalized, hashed
over the first 2 KB) and flags a block of at least 1.5 KB that opens five or
more sessions. Class nudge: CodeBurn will not move the user's own text into
CLAUDE.md, so the fix asks Claude to give the block a permanent home. Only
the repeats count as savings, sized from the block's bytes because provider
usage is per API call and cannot isolate the paste. The opener comes from
the session scan that already runs, so nothing extra is read.
2026-08-18 03:13:40 -07:00
Resham Joshi
117d66f7f7
Merge pull request #1019 from getagentseal/feat/optimize-finding-class
optimize: classify findings as fix/nudge/keep and mark measured vs estimated
2026-08-18 02:44:47 -07:00
iamtoruk
0aed84f4ca Merge remote-tracking branch 'origin/main' into feat/optimize-finding-class
# Conflicts:
#	tests/dashboard.test.ts
2026-08-18 02:40:53 -07:00
iamtoruk
5660909801 optimize: per-group subtotals in every finding render
Each class header now carries its own token/dollar subtotal and finding
count, so the apply-able slice is never mistaken for the whole board; the
headline savings line names that slice explicitly. CLI and TUI share one
classHeaderLine helper, the desktop app reads the same numbers from the
new summary.byClass in --format json (add-only; the three subtotals sum to
findingCount and potentialSavingsTokens).

Also scopes the SHELL_PROFILE_SCOPE comment to what is actually true: the
MCP deferral plans refuse to rewrite a shell profile, but bash-output-cap
appends its own marker block to one.
2026-08-18 02:40:00 -07:00
Resham Joshi
027bc9d1f5
Merge pull request #1018 from getagentseal/test/dashboard-optimize-refresh-flake
test(dashboard): wait for the Optimize scan on real event-loop turns
2026-08-18 02:36:09 -07:00