Skip to content

Commit 63fd598

Browse files
jgravelleclaude
andcommitted
release: v1.108.139 — measured provenance rides the reporting surfaces
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 981a74f commit 63fd598

10 files changed

Lines changed: 133 additions & 11 deletions

File tree

CHANGELOG.md

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

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

5+
## [1.108.139] - 2026-07-18 - measured provenance rides the reporting surfaces
6+
7+
### Added
8+
- **The measured-artifact block now attaches where the numbers get read.**
9+
New `measured_provenance()` (`retrieval/provenance.py`) returns the
10+
committed measurement artifacts behind the suite's savings and quality
11+
claims (the tiktoken token-reduction methodology + the CI-gated replay
12+
retrieval golden, both drift-guarded against
13+
`benchmarks/provenance/measured.json`), and it now rides:
14+
- `receipt --export json` — a `provenance` block beside the totals, so an
15+
exported ledger carries its own receipts;
16+
- `receipt` text output — the methodology footer cites the committed
17+
artifact path;
18+
- `get_session_stats` — a `savings_provenance` block beside the savings
19+
figures;
20+
- `jcodemunch_guide` — a `provenance` key beside the policy snippet, so an
21+
agent reading the guide sees the declared-vs-measured contract up front.
22+
23+
Deliberately kept OFF the hot retrieval path: provenance rides the
24+
reporting surfaces a human or auditor reads, not every query. Suite
25+
parity note: the same reporting-surface shape belongs on jdocmunch /
26+
jdatamunch session stats in their next releases.
27+
528
## [1.108.138] - 2026-07-18 - confidence provenance: every number states its basis
629

730
### Added

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# jcodemunch-mcp — Project Brief
22

33
## Current State
4+
- **Version:** 1.108.139 — **Phase B of the self-attesting contract: measured provenance rides the REPORTING surfaces (never the hot path).** New `provenance.measured_provenance()` (fresh-copy of `MEASURED` + contract note) attached at 4 read-the-numbers surfaces: `receipt --export json` (`provenance` block in `render_json`), `receipt` text methodology footer (cites `benchmarks/provenance/measured.json` + "drift-guarded"), `get_session_stats` (`savings_provenance` — tool wrapper in `tools/get_session_stats.py`, storage layer untouched), `jcodemunch_guide` (`provenance` key beside the snippet; verified the guide response stays JSON so the block survives — no custom encoder). NO CLI-flag change (the duplicate receipt subparser surface in server.py NOT touched — output-only). Strengthens the Tim/QPoint verifiability story: an exported ledger now carries its own receipts. New `tests/test_v1_108_139.py` (6). NO INDEX_VERSION / tool-count / schema change. **OPEN PARITY ITEM ([[feedback_suite_parity_for_agent_contracts]]): jdoc/jdata session-stats should ship the same `savings_provenance` shape (needs sibling provenance registries + their own measured artifacts — do NOT copy jcm's numbers into them).** Phase C next: gold corpus per channel (Loc-Bench feeds it); jMRI normative section LAST.
45
- **Version:** 1.108.138 — **Confidence provenance, Phase A of the self-attesting retrieval contract (F-15 #1 from the 2026-07-18 sweep; competitor unnamed in shipped artifacts).** New `retrieval/provenance.py`: `CONFIDENCE_PROVENANCE` registry — every confidence constant carries `basis` ∈ {`declared` (engineering prior, honestly labeled), `measured` (backed by committed artifact `benchmarks/provenance/measured.json`)}. Load-bearing rule: **a prior is NEVER presented as a measurement** — Phase B/C (graduating find_implementations tiers to measured) requires building a gold corpus FIRST (Loc-Bench feeds this); jjg accepted this phased order after my reservations (measurement-validity, fixture-decay drift-guard policy, jMRI-obligation optics). Registry entries: find_implementations 5 channels (all declared), negative_evidence_threshold 0.5, exact_seed_verdict_floor 1.0. `MEASURED` = the two artifacts jcm actually has: token_reduction 99.6%/15 runs/cl100k_base (METHODOLOGY.md) + replay retrieval quality (self_v1_75_0 golden, ndcg/mrr/recall 1.0, CI-gated). ⚠ The 0.992/0.895 Hit@10/MRR figure floated during the sweep is OCTOCODE'S benchmark (versus.php grid cell), NOT ours — never cite it as jcm's. `find_implementations` attaches `_meta.confidence_provenance` (via `channel_provenance()`). Published JSON Schemas in `schemas/` (retrieval-verdict, confidence-provenance, ranked-context-response); `tests/test_provenance.py` (14) drift-guards registry↔code↔artifact↔underlying-benchmark in all directions + jsonschema-validates live responses. README gains "Confidence provenance" subsection. NO new tool / INDEX_VERSION / schema-budget change (JSON-only `_meta` addition; no custom encoder for find_implementations so compact path unaffected). **Phase B next: surface measured munch-bench/methodology numbers as in-band provenance; Phase C: gold corpus per channel; jMRI normative section LAST (SHOULD-with-conformance-path, after suite parity is demonstrable).**
56
- **Version:** 1.108.137 — **Source-shaped exact seeding in `get_ranked_context` (clean-room F-15; competitor unnamed in shipped artifacts).** New `retrieval/query_shape.py` classifies query tokens into identifier shapes (qualified `A::B`/`a.b` w/ file-extension-tail exclusion, CamelCase, snake_case, dunders; ≤3 tokens, prose never matches); the DEFAULT ranking path resolves each shaped token via a cached `name_map` (built beside the BM25 corpus) to exact-name symbols (case-sensitive→insensitive; qualified parent narrows via id/parent substring; PageRank ranks ties; include_kinds/scope honored; caps 3/token, 5/query) and PINS them ahead of the ranked tail (`_exact_seed_symbols` + pinning block after `scored.sort`). Items carry `match_channel:"exact_name"`; `_meta.query_shape={source_shaped,exact_seeded}`; verdict best_score floored at 1.0 (raw-BM25 scale vs 0.5 threshold) when seeded, and a seeded result bypasses the negative-evidence early return (`if not raw_scores and not seeded`). **Pure-prose queries byte-identical; `fusion=True` path unchanged (its WRR identity channel already covers exact).** Motivation: the exact-lookup lane is our one measured weak spot (definition MRR 0.70 vs 0.87 in the fair rerun). ALSO: (1) **`openWorldHint` on every tool** — new `_OPEN_WORLD_TOOLS` frozenset (index_repo, index_folder/index_file/summarize_repo [cloud summarizer opt-in], embed_repo/check_embedding_drift/test_summarizer, order/route front door) annotated True, everything else False, in `_apply_readonly_annotations`; machine-checkable no-network claim beside readOnlyHint. (2) **`PRAGMA journal_size_limit=64MB`** in sqlite_store `_PRAGMAS` + embedding_store + parse_cache (WAL starvation under long-lived readers — a failure class three ecosystem peers patched the same month). New `tests/test_v1_108_137.py` (19). NO INDEX_VERSION / tool-count / schema change. Origin: 2026-07-18 top-15 competitor sweep (memory `project_competitor_sweep_2026_07_18`); F-15 #1 (measured-provenance receipts / self-attesting contract) NOT started.
67
- **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.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,9 @@ is a byte the agent doesn't pay to read.
128128
<!-- WHATSNEW:START -->
129129
#### What's new
130130

131+
- **[v1.108.139](https://github.com/jgravelle/jcodemunch-mcp/releases/tag/v1.108.139)** (2026-07-18) — measured provenance rides the reporting surfaces
131132
- **[v1.108.138](https://github.com/jgravelle/jcodemunch-mcp/releases/tag/v1.108.138)** (2026-07-18) — confidence provenance: every number states its basis
132133
- **[v1.108.137](https://github.com/jgravelle/jcodemunch-mcp/releases/tag/v1.108.137)** (2026-07-18) — source-shaped exact seeding in ranked context
133-
- **[v1.108.136](https://github.com/jgravelle/jcodemunch-mcp/releases/tag/v1.108.136)** (2026-07-17) — the savings meter records a per-day rollup
134134
<!-- WHATSNEW:END -->
135135

136136
![License](https://img.shields.io/badge/license-dual--use-blue)

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.138"
3+
version = "1.108.139"
44
description = "Token-efficient MCP server for source code exploration via tree-sitter AST parsing"
55
readme = "README.md"
66
requires-python = ">=3.10"

src/jcodemunch_mcp/cli/receipt.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,9 @@ def render_text(
443443
out.write(" Methodology: per-tool savings multipliers calibrated against\n")
444444
out.write(" published RAG benchmarks (Express/FastAPI/Gin). Run with --explain\n")
445445
out.write(" to see the full multiplier table; --export csv|json for raw data.\n")
446+
out.write(" Provenance: basis = measured — committed, drift-guarded artifacts\n")
447+
out.write(" at benchmarks/provenance/measured.json (tiktoken methodology +\n")
448+
out.write(" CI-gated replay retrieval golden). --export json carries the block.\n")
446449

447450
return out.getvalue()
448451

@@ -507,11 +510,14 @@ def render_json(
507510
by_day: Optional[list[dict]] = None,
508511
window: Optional[dict] = None,
509512
) -> str:
513+
from ..retrieval.provenance import measured_provenance
514+
510515
payload = {
511516
"totals": agg["totals"],
512517
"per_tool": agg["per_tool"],
513518
"model": model,
514519
"savings_usd": dollar_savings(agg["totals"]["savings_tokens"], model),
520+
"provenance": measured_provenance(),
515521
}
516522
if window:
517523
payload["window"] = window

src/jcodemunch_mcp/retrieval/provenance.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,21 @@
9595
)
9696

9797

98+
def measured_provenance() -> dict:
99+
"""The measured-artifact block for reporting surfaces (receipt,
100+
get_session_stats, jcodemunch_guide).
101+
102+
Returns fresh copies of the ``MEASURED`` entries plus the contract note,
103+
so callers can attach it to a response without aliasing module state.
104+
Deliberately kept OFF the hot retrieval path — provenance rides the
105+
surfaces where a human reads the numbers, not every query.
106+
"""
107+
return {
108+
**{key: dict(entry) for key, entry in MEASURED.items()},
109+
"contract": _CONTRACT_NOTE,
110+
}
111+
112+
98113
def channel_provenance(prefix: str) -> dict:
99114
"""Compact per-channel basis block for a tool's ``_meta``.
100115

src/jcodemunch_mcp/server.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5645,9 +5645,14 @@ async def call_tool(name: str, arguments: dict) -> list[TextContent] | CallToolR
56455645
result = await _apply_model_announcement(model)
56465646
elif name == "jcodemunch_guide":
56475647
from . import __version__ as _ver
5648+
from .retrieval.provenance import measured_provenance as _measured_provenance
56485649
result = {
56495650
"version": _ver,
56505651
"content": _generate_claude_md_snippet(missing_only=False),
5652+
# Self-attesting contract: the measured artifacts behind the
5653+
# suite's savings/quality claims, plus the declared-vs-measured
5654+
# rule. Rides the guide (on-demand) — never the hot path.
5655+
"provenance": _measured_provenance(),
56515656
}
56525657
else:
56535658
result = {"error": f"Unknown tool: {name}"}

src/jcodemunch_mcp/tools/get_session_stats.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ def get_session_stats(storage_path: Optional[str] = None) -> dict:
1212
1313
Returns:
1414
Dict with session and all-time token savings, cost avoided estimates,
15-
per-tool breakdown, and session duration.
15+
per-tool breakdown, and session duration. ``savings_provenance``
16+
cites the committed benchmark artifacts behind the savings model
17+
(basis: measured — see benchmarks/provenance/measured.json).
1618
"""
17-
return _get_session_stats(base_path=storage_path)
19+
from ..retrieval.provenance import measured_provenance
20+
21+
result = _get_session_stats(base_path=storage_path)
22+
result["savings_provenance"] = measured_provenance()
23+
return result

tests/test_v1_108_139.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"""v1.108.139: Phase B — measured provenance rides the reporting surfaces.
2+
3+
The measured-artifact block (benchmarks/provenance/measured.json via
4+
retrieval/provenance.measured_provenance) attaches where a human reads the
5+
numbers — receipt JSON export, receipt text methodology footer,
6+
get_session_stats, jcodemunch_guide — and stays OFF the hot retrieval path.
7+
"""
8+
9+
import json
10+
11+
from jcodemunch_mcp.retrieval.provenance import (
12+
MEASURED,
13+
measured_provenance,
14+
)
15+
16+
17+
class TestMeasuredProvenanceHelper:
18+
def test_block_mirrors_measured_registry(self):
19+
block = measured_provenance()
20+
for key, entry in MEASURED.items():
21+
assert block[key] == entry, key
22+
assert "contract" in block and "measured" in block["contract"]
23+
24+
def test_block_is_a_fresh_copy(self):
25+
block = measured_provenance()
26+
block["token_reduction"]["average_pct"] = 0
27+
assert MEASURED["token_reduction"]["average_pct"] != 0
28+
29+
30+
class TestReceiptSurfaces:
31+
def test_json_export_carries_provenance(self):
32+
from jcodemunch_mcp.cli.receipt import aggregate, render_json
33+
agg = aggregate([])
34+
payload = json.loads(render_json(agg, model="opus"))
35+
prov = payload["provenance"]
36+
assert prov["token_reduction"]["basis"] == "measured"
37+
assert prov["replay_retrieval_quality"]["basis"] == "measured"
38+
assert prov["token_reduction"]["source"] == "benchmarks/provenance/measured.json"
39+
40+
def test_text_render_cites_the_artifact(self):
41+
from jcodemunch_mcp.cli.receipt import aggregate, render_text
42+
agg = aggregate([{"tool": "search_symbols", "result_tokens": 100}])
43+
text = render_text(agg, days=30, model="opus")
44+
assert "benchmarks/provenance/measured.json" in text
45+
assert "drift-guarded" in text
46+
47+
48+
class TestSessionStatsSurface:
49+
def test_session_stats_carries_savings_provenance(self, tmp_path):
50+
from jcodemunch_mcp.tools.get_session_stats import get_session_stats
51+
result = get_session_stats(storage_path=str(tmp_path))
52+
prov = result["savings_provenance"]
53+
assert prov["token_reduction"]["basis"] == "measured"
54+
assert "contract" in prov
55+
56+
57+
class TestGuideSurface:
58+
def test_guide_result_carries_provenance(self):
59+
import asyncio
60+
from jcodemunch_mcp import server as srv
61+
result = asyncio.run(srv.call_tool("jcodemunch_guide", {}))
62+
text = result[0].text if isinstance(result, list) else result.content[0].text
63+
payload = json.loads(text)
64+
prov = payload["provenance"]
65+
assert prov["token_reduction"]["basis"] == "measured"
66+
assert prov["replay_retrieval_quality"]["ci_gated"] is True

whatsnew.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
{
2-
"current": "1.108.138",
2+
"current": "1.108.139",
33
"entries": [
4+
{
5+
"version": "1.108.139",
6+
"date": "2026-07-18",
7+
"title": "measured provenance rides the reporting surfaces",
8+
"summary": "- **The measured-artifact block now attaches where the numbers get read.** New `measured_provenance()` (`retrieval/provenance.py`) returns the committed measurement artifacts behind the suite's savings and quality claims (the tiktoken token-reduction methodology + the CI-gated replay"
9+
},
410
{
511
"version": "1.108.138",
612
"date": "2026-07-18",
@@ -12,12 +18,6 @@
1218
"date": "2026-07-18",
1319
"title": "source-shaped exact seeding in ranked context",
1420
"summary": "- **`get_ranked_context` recognizes source-shaped query tokens and pins their exact symbol matches first.** A query that pastes an identifier straight out of the source \u2014 `Store.get`, `FreshnessProbe`, `get_ranked_context()` \u2014 names the one symbol the caller actually wants, but BM25 tokenization splits it"
15-
},
16-
{
17-
"version": "1.108.136",
18-
"date": "2026-07-17",
19-
"title": "the savings meter records a per-day rollup",
20-
"summary": "The lifetime meter (`_savings.json`, written on every tool call) is the authoritative record of what each call actually avoided, but it stored only one number \u2014 so any windowed view (\"today\", \"this month\") had to fall back to scanning local transcripts, which miss cleared history and model savings with"
2121
}
2222
]
2323
}

0 commit comments

Comments
 (0)