diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6ed1c8b..27a957d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,7 +3,6 @@ on: push: branches: [main] pull_request: - branches: [main] jobs: test: diff --git a/CHANGELOG.md b/CHANGELOG.md index de27e42..24e4cbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,51 @@ # Changelog +## v0.20.0 — 2026-07-28 — the waste ledger: see what you paid for and didn't use + +**New: `statusline.sh report [--days N]` — the waste ledger.** The +advisor (v0.19) prevents waste prospectively; this proves it +retroactively. It replays the usage log the statusline has been writing +all along, detects every window close (consecutive samples disagreeing +on `resets_at`, normalized to the minute — the ratio learner's identity +rule), and ledgers each closed 7d window as used% / expired%, converted +into 5h-windows-worth via the learned `pct_per_window` exchange rate: + +``` +7d windows closed: 1 + Tue 07-28 00:00 used 51% expired 49% (~4.7 x 5h windows unused) +5h windows closed: 3 avg 95% at close 2 hit the cap +exchange rate: one full 5h window = ~10.46% of the week (~9.6 windows/week, learned) +week in progress: 5% used, resets Mon 08-03 23:59 +``` + +"week in progress" runs the same learned walk the advisor's heading +uses — the surfaces cannot disagree. Honest limits are documented: the +final utilization is the last sample before a reset, so usage from other +clients after your last local render is invisible, and never-sampled +windows don't appear. + +**Widened snapshots: log today what the learner needs next month.** +Every `usage` line in usage.jsonl now also records `limits[]` (scoped +per-model weekly caps, verbatim), `model` (the id active in the logging +session), and `predicted_end` — the learned walk's end-of-week +projection *at sample time*. That last one is the calibration seed: once +windows close, predictions can be scored against observed finals, and +the forecast's accuracy becomes measurable instead of assumed. Learning +lags logging by weeks; fields absent today are patterns that can't be +learned next month. + +**Fixed: `catch .` gave every empty `resets_at` one shared fake window +identity.** jq's `catch .` yields the error *message*, not the original +input — so snapshots with an empty/unparseable `resets_at` all +normalized to the same error string and could pair across real windows +in the ratio learner, quietly skewing `pct_per_window` (observed on real +data: phantom window closes and a drifted ratio). Both norm sites now +fall back to the raw input string and treat empty as empty. + +Mechanics: subcommands dispatch after all function definitions, take no +stdin, and are read-only against the state dir. 327 tests (315 +statusline + 12 installer). + ## v0.19.0 — 2026-07-28 — smart advisor line, claude-watch retired **New: the advisor — a second statusline row that interprets the badges.** diff --git a/README.md b/README.md index dbd39b9..1216380 100644 --- a/README.md +++ b/README.md @@ -285,6 +285,36 @@ notifications), see `claude.py --watch-usage` in [claudex](https://github.com/thevibeworks/claudex) — it consumes the same state dir this script maintains (see `docs/api/state-dir.md`). +## The waste ledger + +The advisor prevents waste prospectively; `report` proves it +retroactively. It replays the usage log the statusline has been writing +all along and ledgers every closed window — what you used, what expired: + +```bash +$ ~/.claude/statusline.sh report # or --days 90 +usage report - work (last 28d, 79 samples) + +7d windows closed: 1 + Tue 07-28 00:00 used 51% expired 49% (~4.7 x 5h windows unused) + +5h windows closed: 3 avg 95% at close 2 hit the cap +exchange rate: one full 5h window = ~10.46% of the week (~9.6 windows/week, learned) + +week in progress: 5% used, resets Mon 08-03 23:59 +``` + +That "expired 49%" line is the subscription math nobody shows you: half +a week of paid capacity, gone. The windows-worth figure uses the same +learned `pct_per_window` ratio the advisor's feasibility check uses, and +"week in progress" runs the same learned projection — the surfaces +cannot disagree. + +Honest limits: a window's final utilization is the last sample before +its reset, so usage from other devices after your last local render is +invisible, and a week you never opened a session in never appears at +all. The ledger reports what the log observed, nothing more. +
OAuth and API behavior Quota, profile, and extra-usage requests use Claude Code's OAuth credentials @@ -332,7 +362,7 @@ run. Setting only `CLAUDE_CACHE_DIR` keeps the legacy single-dir behavior. npm exec --yes bats -- t/ ``` -321 tests across `t/statusline.bats` (309 statusline + integration) and +327 tests across `t/statusline.bats` (315 statusline + integration) and `t/install.bats` (12 installer). CI runs on push and PR to `main`. ## Project Structure diff --git a/docs/api/state-dir.md b/docs/api/state-dir.md index 3a6bf21..58a7536 100644 --- a/docs/api/state-dir.md +++ b/docs/api/state-dir.md @@ -7,7 +7,7 @@ is the ONLY writer; everything here is safe to read concurrently. - Contract version: **1** (bump on any breaking layout/field change; this file is the changelog) -- Synced with: statusline.sh v0.19.0 +- Synced with: statusline.sh v0.20.0 - Permissions: the script runs under `umask 077` — files are owner-only. Caches hold account PII (email, uuid, org names). @@ -91,13 +91,27 @@ Append-only, one JSON object per line, three event types: | `type` | Emitted | Payload | |--------|---------|---------| -| `usage` | every successful usage fetch | `session_id`, `timestamp`, `user{email,name,uuid,…}`, `organization{…}`, `five_hour`, `seven_day`, `seven_day_opus`, `extra_usage` | +| `usage` | every successful usage fetch | `session_id`, `timestamp`, `user{email,name,uuid,…}`, `organization{…}`, `five_hour`, `seven_day`, `seven_day_opus`, `extra_usage`, `limits[]`, `model`, `predicted_end` | | `session_start` | first fetch of a new 5h window | `session_id`, `timestamp`, `five_hour_window_end`, `seven_day_window_end` | | `session_end` | 5h window rolled while a different session was last | `session_id`, `timestamp` | +Since v0.20.0 each `usage` line also records what the learner will need +later (learning lags logging — a field absent today is a pattern that +cannot be learned next month): + +| Field | Type | Meaning | +|-------|------|---------| +| `limits[]` | array | scoped limits verbatim (per-model weekly caps) | +| `model` | string \| null | model id active in the logging session | +| `predicted_end` | int \| null | the learned walk's end-of-week projection at sample time; null until the profile is warm. Compare against the window's observed final to measure forecast accuracy. | + Rotation keeps exactly one `.1` backup; readers wanting full history read `usage.jsonl.1` then `usage.jsonl`. +The `report` subcommand (`statusline.sh report [--days N]`) is the +reference consumer: it replays this log and ledgers what each closed +window expired unused. + ## Consumer rules 1. Read-only. Locks, TTLs, and rotation are the writer's job. diff --git a/docs/index.html b/docs/index.html index 9cf1ce6..c08c1b5 100644 --- a/docs/index.html +++ b/docs/index.html @@ -4,7 +4,7 @@ Claude Code Statusline — quota, context, cost. Live, in one bash file. - +