Skip to content

Commit c9bbae6

Browse files
jgravelleclaude
andcommitted
release: v1.108.136 — receipt calendar windows, --rates, per-day savings meter
Three receipt/meter changes, shipped together (v1.108.134-136 in the CHANGELOG): receipt calendar windows (--since / --until / --by-day): - --since / --until accept dates. A bare date means local midnight, and --until is exclusive, so adjacent windows never double-count a call that lands on the boundary. - --by-day adds a per-day series to the JSON export. The series sums to the window total by construction. - The export gains a `window` block describing the range that produced the figures. --days behavior is unchanged when neither new flag is given. receipt --rates: - Emits the model input-price table as JSON and exits; scans no transcripts. Exists so anything that prices token counts (the console, scripts, exports) reads the one table the suite ships with, instead of keeping its own copy that drifts whenever published pricing changes. savings meter per-local-day rollup: - The per-call savings meter (_savings.json) now writes a `daily` map alongside the lifetime total. Same read-modify-write flush chokepoint, so it stays multi-process safe. Capped at 750 days. - Why: windowed views ("today", "this month") can now be answered by the meter itself, which is the authoritative record of what each call actually avoided. Transcript scans can't fill that role: they miss history that's been cleared and they model conservatively. No INDEX_VERSION or wire change. 25 new tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 38427c4 commit c9bbae6

8 files changed

Lines changed: 575 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,71 @@
22

33
All notable changes to jcodemunch-mcp are documented here.
44

5+
## [1.108.136] - 2026-07-17 - the savings meter records a per-day rollup
6+
7+
The lifetime meter (`_savings.json`, written on every tool call) is the
8+
authoritative record of what each call actually avoided, but it stored only one
9+
number — so any windowed view ("today", "this month") had to fall back to
10+
scanning local transcripts, which miss cleared history and model savings with
11+
deliberately conservative multipliers. On a heavy install the gap is not subtle:
12+
the transcript scan could prove ~2.2M tokens all-time while the meter had
13+
recorded 34.3B — four orders of magnitude. A dashboard drawing windows from
14+
transcripts next to a lifetime tile from the meter showed \$7.73 "this month"
15+
beside \$171K all-time, which reads as a broken product rather than two sources.
16+
17+
The flush now also credits each batch to a per-local-day bucket in a `daily` map
18+
alongside `total_tokens_saved`. Same file, same read-modify-write chokepoint, so
19+
multiple server processes keep accumulating correctly; capped at 750 days so the
20+
file stays a few KB. Flush batches are small, so crediting a batch to its flush
21+
day mis-dates at most one batch across midnight. History accrues from the first
22+
flush under this version forward — the single lifetime total can't be
23+
back-distributed into days it never recorded.
24+
25+
New `tests/test_v1_108_136.py` (6). NO INDEX_VERSION / tool-count / wire change;
26+
the file was already disclosed in the README's background-behavior section.
27+
28+
## [1.108.135] - 2026-07-17 - `receipt --rates` publishes the model price table
29+
30+
The savings model values tokens at a model's input rate, and the rate table lived
31+
only inside `receipt`. Any consumer pricing its own token counts had to keep a
32+
copy — and a copy drifts silently: the jMunch Console's duplicate sat at the
33+
retired \$15 Opus rate long after this table moved to \$5, so its two dollar
34+
figures disagreed by 3x with nothing to catch it.
35+
36+
`receipt --rates` prints the table as JSON (`rates_usd_per_mtok` + `default_model`)
37+
and exits. It scans no transcripts, so a consumer can read it cheaply and offer
38+
every model this table prices — including any model added later — without a code
39+
change on its side. `--model`'s choices already derive from the same table
40+
(1.108.131), so the CLI and its published rates cannot disagree.
41+
42+
+3 tests in `tests/test_v1_108_134.py`. NO INDEX_VERSION / tool-count / wire change.
43+
44+
## [1.108.134] - 2026-07-17 - `receipt` gains calendar windows and a per-day series
45+
46+
`receipt --days N` is a rolling window measured back from now, so it can express
47+
"the last 24 hours" but not "today", and "yesterday" not at all. Two additions:
48+
49+
- **`--since` / `--until`** bound the window explicitly. A bare date (`2026-07-16`)
50+
means local midnight — "yesterday" means yesterday where the person is sitting,
51+
not in UTC — and a full ISO datetime works too. `--until` is **exclusive**, so
52+
two adjacent calendar windows can never both claim a call that lands on the
53+
boundary. `--days` still applies when neither is given, so existing callers are
54+
unaffected.
55+
- **`--by-day`** adds a `by_day` series to the JSON export: one row per calendar
56+
day with any calls, carrying calls/actual/baseline/savings plus the dollar value
57+
at the selected model's rate. `iter_calls` already yielded a per-call timestamp
58+
and `aggregate` discarded it; the new `aggregate_by_day` keeps it. The series
59+
sums to the window total by construction, so a chart drawn from it always
60+
reconciles with the headline figure beside it — and a caller wanting several
61+
ranges can slice one scan instead of paying one scan per range.
62+
63+
The JSON export also gains a `window` block recording the bounds it actually used.
64+
Both new flags are additive: without them the output is unchanged. The `server.py`
65+
CLI dispatcher (which re-declares the receipt flags) forwards them too — the same
66+
duplicate-parser surface that stranded `--model fable` in 1.108.131.
67+
68+
New `tests/test_v1_108_134.py` (16). NO INDEX_VERSION / tool-count / wire change.
69+
570
## [1.108.133] - 2026-07-16 - cache hits count toward the savings meter
671

772
`search_symbols` records the tokens it saves into the persistent lifetime meter

CLAUDE.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# jcodemunch-mcp — Project Brief
22

33
## Current State
4+
- **Version:** 1.108.136 — **The savings meter records a per-day rollup (`daily` map in `_savings.json`).** The lifetime meter is the authoritative savings record but stored ONE number, so windowed views fell back to transcript scans — which on jjg's install prove ~2.2M tokens all-time vs the meter's 34.3B (4 orders of magnitude; jjg on the resulting Console panel: "\$7.73 this month next to \$171K = unhealthy disconnect", "\$0.11 today. Preposterous"). `_flush_locked` (`storage/token_tracker.py`) now also credits each flush batch to `daily[<local-iso-date>]` alongside `total_tokens_saved` — same file, same read-modify-write chokepoint (multi-process safe), `_DAILY_MAX_DAYS=750` cap, corrupt-shape tolerated, batch-across-midnight mis-dates ≤1 batch. **History accrues only from first flush under this version** — the lifetime total can't be back-distributed. **Long-running MCP servers keep pre-change code until restarted (editable install: new process = new code).** Consumer: console v0.8.81 draws window tiles from this book when it FULLY covers the window (today/yesterday immediately; week/month/year flip over as history accrues; `all` = lifetime total, closing the disconnect), transcript fallback + honest note otherwise; receipt scan remains sole source for per-tool breakdown + call counts. New `tests/test_v1_108_136.py` (6). NO INDEX_VERSION / wire change; file already README-disclosed.
5+
- **Version:** 1.108.135 — **`receipt --rates` publishes the model price table (kills the duplicate-constant drift class).** The rate table lived only inside `receipt`, so any consumer pricing its own token counts kept a COPY — and a copy drifts silently: the jMunch Console's duplicate sat at the retired **\$15** Opus rate long after this table moved to **\$5**, so its two dollar tiles disagreed 3x with nothing to catch it. New `--rates` prints `{rates_usd_per_mtok, default_model}` as JSON and exits; **scans no transcripts** (instant, cacheable) so a consumer can poll it cheaply and offer every priced model — including ones added later — with no code change on its side. `--model`'s choices already derive from the same table (.131), so CLI and published rates can't disagree. New `render_rates()`; forwarded through the `server.py` receipt subparser (the duplicate-parser surface — edit BOTH). +3 tests in `tests/test_v1_108_134.py` (incl. a guard that `--rates` never calls `iter_calls`). NO INDEX_VERSION / tool-count / wire change. Consumer: console v0.8.80 pricing-model picker.
6+
- **Version:** 1.108.134 — **`receipt` gains calendar windows (`--since`/`--until`) + a per-day series (`--by-day`).** `--days N` is a rolling window back from now: it can say "last 24h" but not "today", and "yesterday" not at all. `--since`/`--until` bound the window explicitly — a bare date is **local** midnight (yesterday means yesterday where the user sits; transcript timestamps are UTC), a full ISO datetime also parses, and **`--until` is EXCLUSIVE** so adjacent calendar windows can't both claim a boundary call. `--days` still applies when neither is given (existing callers byte-identical). `--by-day` adds `by_day[]` to the JSON export (one row per day with calls; calls/actual/baseline/savings + `savings_usd`) — enabled by the fact that `iter_calls` already yielded a per-call timestamp that `aggregate` discarded; new `aggregate_by_day` keeps it. **The series sums to the window total by construction**, so a chart drawn from it always reconciles with the tiles beside it, and a caller wanting N ranges slices ONE scan instead of paying N. JSON export also gains a `window` block (the bounds actually used). New `parse_window_bound()` (naive input → local tz) + `_window_label()` for the text header. **The `server.py` receipt subparser re-declares these flags and forwards them — that duplicate-parser surface is the same one that stranded `--model fable` in 1.108.131; edit BOTH.** New `tests/test_v1_108_134.py` (16). NO INDEX_VERSION / tool-count / wire change. Consumer: jMunch Console v0.8.79 savings range chips (Today/Yesterday/This Week/This Month/This Year/All Time) — [[project_jmunch_console]].
7+
- **Version:** 1.108.133 — **Cache hits now count toward the savings meter (undersell fix).** `search_symbols` records `record_savings(tokens_saved)` into the persistent lifetime meter (`_savings.json`) + community counter on its MISS path, but the cache-hit early-return (`search_symbols.py:638`) returned before recording — so every repeat query (identical avoided read = identical real saving) was invisible to the meter, a systematic undercount on repetitive/heavy workloads (jjg's 18h/day pattern; the Tim-presentation figure). Fix: the cache-hit block re-records the cached per-call `tokens_saved` (read from the cached `_meta`) and refreshes `_meta.total_tokens_saved`. Safe because `_result_cache_get` returns a fresh copy of BOTH top-level AND `_meta` (`search_symbols.py:60-61`), so the stored figure stays pristine and each hit re-records the same original value; no-op when the cached call saved zero (a hit never fabricates a recording). **Audit context (jjg-requested "is the collection mechanism underselling?"): the meter is conservative in every other respect — `_BYTES_PER_TOKEN=4` undercounts denser code tokens (~8-15%, intentional per the tiktoken-99.6% methodology doc), `raw_bytes` credits each matched file once (`search_symbols.py:946-952` seen_files dedup), empty/negative results credit zero — so all error points DOWN; this cache-hit fix moves the figure toward true, never overstates.** **Scope note (NOT done, jjg's call): `find_references` + `get_blast_radius` cache paths are consistent because those graph tools record savings on NEITHER path — they capture nothing for the real reads they spare (find_references = grep replacement). Whether they should record is a separate design question flagged in CHANGELOG.** NO wire-shape/INDEX_VERSION change. New test `tests/test_search_result_cache.py::test_cache_hit_records_savings_into_meter` (monkeypatches `record_savings`, asserts the hit re-records the cached figure). CI 8-job matrix + Replay green BEFORE twine.
48
- **Version:** 1.108.132 — **`receipt` surfaces the persistent lifetime savings meter.** `receipt` scans local Claude transcripts (cleared on reinstall → its windowed figure understates a heavy user; jjg's all-time transcript scan showed only 504 calls / $10.17 while his real cumulative was 34.3B tokens). Now also reads `_savings.json` under the index root (the per-call meter the MCP server writes on every call, survives Claude Code reinstalls) and reports lifetime tokens saved + dollar value at the selected model's INPUT rate, next to the windowed figure (shown even when the transcript window is empty). New `lifetime_meter(root=)` reader (honors CODE_INDEX_PATH; honest-None on absent/unreadable/zero); wired into `render_text` + `render_json` (`--export json` gains a `lifetime` block). Reconciles the "anemic transcript number vs big lifetime number" gap in ONE place — the fix for the Tim-presentation verifiability concern. New `TestLifetimeMeter` (9). NO INDEX_VERSION / wire change. (Related, NOT in the repo: jjg's personal `~/.claude/hooks/gsd-statusline.js` was corrected from `* 25.00` (Opus OUTPUT rate) to `* 5.00` (Opus INPUT) — input-token savings valued at input pricing; $857K→$171K, defensible.)
59
- **Version:** 1.108.131 — **`receipt`/`org-report` `--model` choices derive from the price table (Fable now selectable).** v1.108.130 added Fable to `cli/receipt.py`'s `_MODEL_PRICES_USD_PER_MTOK`, but `server.py`'s CLI dispatcher had a DUPLICATE hardcoded `--model` choices list (`{sonnet,opus,haiku}`) that gated `receipt --model fable` before it reached receipt.py's own parser (jjg hit this on the installed editable src — the code was live, the dispatcher wasn't updated). Both the `receipt` and `org-report` subparsers now build `choices=sorted(_MODEL_PRICES_USD_PER_MTOK)` from the single price table (imported locally in `main()`), so any priced model is selectable and the CLI can't drift from the rates. New parametrized end-to-end test in `tests/test_receipt.py` runs the server CLI for every priced model + asserts none rejected. NO INDEX_VERSION / tool-count / wire change.
610
- **Version:** 1.108.130 — **`receipt` CLI price table updated to current Anthropic pricing (+ Fable 5).** Anthropic has reduced input pricing across the Opus line since these models launched; the `receipt` model input-price table (`cli/receipt.py` `_MODEL_PRICES_USD_PER_MTOK`) now tracks current published rates (Opus $5 / Sonnet $3 / Haiku $1) and adds **Claude Fable 5 ($10)** as a selectable model + alternate comparison line (both derive from the table). Source comment cites the dated pricing source (anthropic.com/pricing 2026-06-24); `tests/test_receipt.py` pins the table to it (+ a Fable test). `receipt` values measured token savings at the selected model's current rate; the underlying token savings are unchanged. **Does NOT feed the public token-savings counter, which stores tokens and values them at display time.** jcm's own `storage/token_tracker.PRICING` was already at $5. Sibling parity: jdoc v1.97.0 / jdata v1.19.0 update the same constants in their `token_tracker.PRICING` (feeds per-response `_meta.cost_avoided`). NO INDEX_VERSION / tool-count / wire change. **Framing note: this tracks a vendor price REDUCTION (Anthropic cut Opus input pricing since launch) — NOT a correction of an inflated figure. Never describe it as "overstating."**
@@ -280,7 +284,7 @@ src/jcodemunch_mcp/
280284
| `hook-taskcomplete` | TaskCompleted hook: post-task diagnostics — dead code, untested symbols, dangling refs (reads JSON stdin) |
281285
| `hook-subagent-start` | SubagentStart hook: inject condensed repo orientation for spawned agents (reads JSON stdin) |
282286
| `whatsnew` | Refresh README recency block + write `whatsnew.json` from `CHANGELOG.md` (release flow) |
283-
| `receipt` | Token-economy ledger from Claude transcripts — modeled tokens-saved + dollar value at Sonnet/Opus/Haiku rates; `--explain`, `--export csv\|json`, `--days`, `--model` |
287+
| `receipt` | Token-economy ledger from Claude transcripts — modeled tokens-saved + dollar value at Fable/Opus/Sonnet/Haiku rates; `--explain`, `--export csv\|json`, `--days` (rolling), `--model`. v1.108.134: `--since`/`--until` for calendar windows (local dates; `--until` exclusive) + `--by-day` for a per-day series in the JSON export. v1.108.135: `--rates` dumps the model price table as JSON (scans nothing) so consumers price from the one table instead of a drifting copy |
284288
| `digest` | Agent stand-up briefing — composes since-last-session delta + risk surface + dead-code candidates; tracks per-repo last-seen SHA at `~/.code-index/digest_state/`; also exposed as MCP tool `digest`. v1.108.68 adds a one-line retrieval-regret summary when the ledger has clusters |
285289
| `reflect` | (v1.108.68) Surface retrieval regret as SUGGESTED config corrections — `reflect [repo] [--project-path] [--window-days N] [--all] [--apply-weights] [--json]`. Thin CLI over the `suggest_corrections` tool; read-only (only `--apply-weights` writes, and only the tuning.jsonc sidecar) |
286290
| `delivery` | (v1.108.69) Print durable-change delivery metrics for a window — `delivery [repo] [--window-days N] [--rework-horizon-days N] [--cost DOLLARS] [--json]`. Thin CLI over `get_delivery_metrics`; `--cost` prints the headline cost-per-durable-change (how much got done for how little). Read-only git archaeology |

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "jcodemunch-mcp"
3-
version = "1.108.133"
3+
version = "1.108.136"
44
description = "Token-efficient MCP server for source code exploration via tree-sitter AST parsing"
55
readme = "README.md"
66
requires-python = ">=3.10"

0 commit comments

Comments
 (0)