You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
test: a receipt on a real checkout, because fresh is what a fixture cannot fake
Every plain-folder fixture reports freshness `not_tracked`, so a suite built
only from them never exercises the path where both revisions are read and
compared. That path is the whole point of the snapshot binding. The v1.108.180
lesson, applied to Phase 2.
Also covers the absence half on a real checkout: a zero-result scan measures the
working tree, so its receipt binds `clean` rather than `unknown`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: CLAUDE.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,7 +1,7 @@
1
1
# jcodemunch-mcp — Project Brief
2
2
3
3
## Current State
4
-
- **Version:** 1.108.183 — **A receipt says what it proves, and only that (#377 Phase 2, P1+P2).** ⚠ **Phase 1 attested a ref matching a served symbol id OR THE FILE COMPONENT OF ONE, so citing `src/auth/session.py` was attested when `session.py::refresh_token#function` was the only thing retrieved — and the finalized handoff gave a reader no way to tell those two citations apart.** New `evidence/receipts.py` binds one canonical subject (symbol id + file + line range + `content_sha256`) to one snapshot and to the operation that actually ran, and derives the id from exactly those three, so **different snapshots cannot share an evidence id**. Read at `munch://evidence/<id>`; **NO new tool** (tool-schema budget); the response carries an **id**, not the receipt. Opt-in `receipt` arg on the four registered producers only. ⚠ **P2 is the load-bearing half and shipped together with P1 on purpose — an envelope without registration is a contract anything can mint into.** `evidence/producers.py` declares per producer: verdict shape, proof kinds, canonical projector, completeness/freshness/coverage/integrity semantics. **The early-return lesson made STRUCTURAL: minting requires a verdict of the shape the producer registered**, so an exit that asserts an answer without calling `build_verdict` cannot mint — not as a listed exception, but because it never produced the thing a receipt derives from. ⚠ **finalize_handoff: THE INPUT PICKS THE CONTRACT.** No receipt cited ⇒ historical broadening stands, body byte-identical, but the receipt now NAMES it (`evidence_precision`, `broadened_refs`). A receipt cited ⇒ a file-level ref backed only by a served symbol is REFUSED as over-broad. ⚠ **Absence receipts LINK to the scan `note_absence` recorded (`handoff.absence_ref_for`) so `absence_refusal` stays the ONLY implementation of the refusal rules; `capabilities.proves_absence` is advisory and finalization re-derives it.** ⚠ **Divergence found while wiring: `build_symbol_verdict` never got the item-4 four-state treatment, so ITS `channels.index` says `fresh` for a plain folder with no revision — a `get_symbol_source` receipt therefore probes `repo_freshness` DIRECTLY (trusting the channel would be the 1.108.176 mistake again). Visible inside one envelope and pinned.** ⚠ **Subject read from the SERVED row only, never re-read from the index** — `search_symbols` at standard detail serves no `end_line`/hash, so the receipt omits both and says so in `limitations`. Full digests + fail-closed on collision (an id that ever named two receipts names neither). `_meta.receipts` in `UNIVERSAL_META_JSON`. `schemas/evidence-receipt.schema.json` published in the same commit. ⚠ **NOT done: session keying / full expiry taxonomy / deep-freeze = P3; redacted rendering + `absence_scans_attested` rename = P4; suite parity = P5.** ⚠ **Two ungated absence assertions FOUND, not fixed, said out loud: `search_symbols`'s SEMANTIC exit emits "Do not claim this feature exists" with NO `_meta.verdict` at all (so no absence gate since 1.108.166 applies to it), and both FUSION exits emit neither. Neither can mint — the gate working — and tests pin it against the source. Fixing the prose would change bytes on calls that never asked for a receipt, so it is its own change.** New `tests/test_v1_108_183.py` (78). No tool-count/INDEX_VERSION change.
4
+
- **Version:** 1.108.183 — **A receipt says what it proves, and only that (#377 Phase 2, P1+P2).** ⚠ **Phase 1 attested a ref matching a served symbol id OR THE FILE COMPONENT OF ONE, so citing `src/auth/session.py` was attested when `session.py::refresh_token#function` was the only thing retrieved — and the finalized handoff gave a reader no way to tell those two citations apart.** New `evidence/receipts.py` binds one canonical subject (symbol id + file + line range + `content_sha256`) to one snapshot and to the operation that actually ran, and derives the id from exactly those three, so **different snapshots cannot share an evidence id**. Read at `munch://evidence/<id>`; **NO new tool** (tool-schema budget); the response carries an **id**, not the receipt. Opt-in `receipt` arg on the four registered producers only. ⚠ **P2 is the load-bearing half and shipped together with P1 on purpose — an envelope without registration is a contract anything can mint into.** `evidence/producers.py` declares per producer: verdict shape, proof kinds, canonical projector, completeness/freshness/coverage/integrity semantics. **The early-return lesson made STRUCTURAL: minting requires a verdict of the shape the producer registered**, so an exit that asserts an answer without calling `build_verdict` cannot mint — not as a listed exception, but because it never produced the thing a receipt derives from. ⚠ **finalize_handoff: THE INPUT PICKS THE CONTRACT.** No receipt cited ⇒ historical broadening stands, body byte-identical, but the receipt now NAMES it (`evidence_precision`, `broadened_refs`). A receipt cited ⇒ a file-level ref backed only by a served symbol is REFUSED as over-broad. ⚠ **Absence receipts LINK to the scan `note_absence` recorded (`handoff.absence_ref_for`) so `absence_refusal` stays the ONLY implementation of the refusal rules; `capabilities.proves_absence` is advisory and finalization re-derives it.** ⚠ **Divergence found while wiring: `build_symbol_verdict` never got the item-4 four-state treatment, so ITS `channels.index` says `fresh` for a plain folder with no revision — a `get_symbol_source` receipt therefore probes `repo_freshness` DIRECTLY (trusting the channel would be the 1.108.176 mistake again). Visible inside one envelope and pinned.** ⚠ **Subject read from the SERVED row only, never re-read from the index** — `search_symbols` at standard detail serves no `end_line`/hash, so the receipt omits both and says so in `limitations`. Full digests + fail-closed on collision (an id that ever named two receipts names neither). `_meta.receipts` in `UNIVERSAL_META_JSON`. `schemas/evidence-receipt.schema.json` published in the same commit. ⚠ **NOT done: session keying / full expiry taxonomy / deep-freeze = P3; redacted rendering + `absence_scans_attested` rename = P4; suite parity = P5.** ⚠ **Two ungated absence assertions FOUND, not fixed, said out loud: `search_symbols`'s SEMANTIC exit emits "Do not claim this feature exists" with NO `_meta.verdict` at all (so no absence gate since 1.108.166 applies to it), and both FUSION exits emit neither. Neither can mint — the gate working — and tests pin it against the source. Fixing the prose would change bytes on calls that never asked for a receipt, so it is its own change.** New `tests/test_v1_108_183.py` (79). No tool-count/INDEX_VERSION change.
5
5
- **Prior (1.108.182):** — **A stall has a name and a ceiling (#375 REOPENED).** ⚠ **@dkiaulakis' re-run at 1.108.176 measured NO improvement on the subtree that stalled at 1.108.169 — 268s vs 240s, both unfinished. The 1.108.171 regex fix removed one known cost centre; it could not remove the SHAPE of the failure, which is that discovery and parsing were both unbounded and both reported nothing.** Three bounds: (1) `discover_providers(folder, budget_seconds=, skipped=)` runs each provider's detect+load under a wall-clock ceiling (`JCODEMUNCH_PROVIDER_BUDGET_SECONDS`, 30s, 0 disables); an overrun is skipped and NAMED via `providers_skipped` + a warning that states the CONSEQUENCE (symbols indexed, but no context from that provider and its import edges — route mounts, template renders — absent from the graph). (2) New `_route_utils.iter_source_files` prunes dependency dirs AT THE WALK: `ExpressProvider.load` ran FIVE recursive globs and dropped `node_modules` by substring AFTER enumerating it, same defect in `_scan_package_json_forced_paths`'s `rglob` — **measured 0.639s → 0.001s for the identical 41 files over a 9,600-file node_modules, warm and alternating.** (3) `parse_file_budgeted` bounds one file's parse (`JCODEMUNCH_PARSE_BUDGET_SECONDS`, 20s); raises `ParseBudgetExceeded`, which the three existing call sites' `except Exception` already turns into a by-name warning. ⚠ **Armed only at/above 128 KiB — the common path parses inline, unchanged.** ⚠ **THE LIMIT, STATED: a watchdog stops the CALLER waiting; it cannot stop the work.** Python cannot preempt a thread and tree-sitter is C, so an abandoned parse/provider keeps burning CPU. `ContextProvider.budget_expired()` is the cooperative half and only helps where a provider POLLS it — wired into the Express walk, not the other thirteen. ⚠ **Not yet reproduced on Darius' box; the walk + regex evidence says his 268s should be gone, and the strongest untested hypothesis is a stale resolved install (25+ leaked stdio instances, a :3049 server on the same db, hand-patched files an upgrade overwrote).** New `tests/test_v1_108_182.py` (23). No tool-count/INDEX_VERSION change.
6
6
- **Prior (1.108.181):** — **Uncommitted work is represented, PER SCOPE (#377 hardening item 5 — closes the last open 1.x item).** ⚠ **Git HEAD sits still while the tree holds a modified file, a brand-new UNTRACKED implementation, a deletion or a rename into/out of scope — so every freshness gate we had could report `fresh` over a corpus that never read the file the target lives in. A scan that RETURNS rows carries per-file freshness ON them; a scan that returns NOTHING has nowhere to put it.** New `subject_state.working_tree_state` gives the SCOPE its own state (`clean`/`dirty_in_scope`/`dirty_outside_scope`/`unknown`/`not_applicable`) as `verdict.working_tree`, wired into `search_symbols` / `search_text` / BOTH `get_ranked_context` exits. ⚠ **TWO restrictions are what keep it from becoming noise, and both are load-bearing: (1) work OUTSIDE the scanned scope NEVER blocks (his own distinction — a dirty file elsewhere cannot invalidate a narrow proof); (2) an edit the index has ALREADY re-read is NOT a gap — `files_not_in_index` compares each dirty in-scope path against `index.file_mtimes`, so a watcher-fresh corpus keeps proving absence. Without (2) the gate fires on EVERY developer with unsaved work in an up-to-date repo, and a signal that fires constantly is one people learn to ignore.** ⚠ **Measured for a ZERO-RESULT scan only** (a `git status` per search would tax the common path); reading is TTL-cached and SHARED with the 1.108.178 cached-negative revalidation. ⚠ **Rename handling: `R old -> new` records the NEW path — the one that exists in the tree and the one a search would have had to see;** quoted/backslashed paths normalised to compare against index paths. `working_tree` published in the verdict schema. New `tests/test_v1_108_181.py` (27). No tool-count/INDEX_VERSION change.
7
7
-**Older releases (1.108.180 and earlier):** see `CHANGELOG.md`. The 1.108.180 entry ("unknown freshness stops collapsing into fresh", #377 item 4) and 1.108.179 ("the subject has to hold still for the scan", item 6) are there in full.
0 commit comments