fix(memory-integrity): the budget constant was 585 bytes too generous; port the phantom-tool and fleet-size legs - #2
Merged
Conversation
DEFAULT_INDEX_BUDGET_BYTES read `Math.floor(24.4 * 1024)` = 24,985 — the loader's documented "24.4KB" re-typed as KiB where the number is decimal. The one check that can catch a truncating memory index carried 585 bytes of slack it was never granted, in the only direction that matters: it let a truncated index pass. Pinned to the literal 24,400. The existing budget test asserted against `DEFAULT_INDEX_BUDGET_BYTES + 1`, so it passed just as happily on the wrong constant. The new one pins the literal and the boundary: clean at 24,400, WARN at 24,401. Catches the file up with its origin on two more legs: - phantom-tool (opt-in): an index line NAMING a tool or script that resolves to no file. The live instance was an index naming an "open-items projection" tool that never existed — a compression pass kept the name and dropped the recipe, and the line stayed unfollowable for eleven days. Extraction is pure; existence is judged by a caller-supplied `toolResolver` so the file stays fs-free. Absent resolver, the check does not run; `null` or a throw means can't-judge, which yields no finding. - classifyMemoryIndexSizes: the fleet read, whose failure the single-dir lint doesn't have — an agent whose index you could not measure. MISSING is fail-closed and a finding; a dir that never existed is declared, not swept; `sweptCount` counts only what was measured, so a run that measured nothing returns "nothing-swept" rather than "clean", and findings dominate. Deliberately NOT ported: mungeProjectPath and stripWorktreeSuffix. Both encode Claude-Code-specific path shapes (`~/.claude/projects` name munging, `.claude/worktrees` suffixes) and both are about LOCATING files, which this library does not do. 188 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CkTF1JryMkGt4Qx2A2vHEh
The bus listener writes tmp/.bus-events.jsonl in whatever repo it runs from, and those events carry portfolio internals. This is a public repo; one stray `git add -A` is all it would take. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CkTF1JryMkGt4Qx2A2vHEh
…gs, 6 as written, 1 adapted Every one reproduced against 3cfff31 before it was touched; the tests carry the numbers each returned. 1. The README quickstart measured the index with `statSync().size`, which on a CRLF working tree is +1 byte per line — the documented integration put a valid 24,400-byte index over the cap it had just been pinned to. Measure the text you read, LF-normalized. JSDoc said "on disk"; it now says what the number is. 2. `scripts\foo.py` extracted as `foo.py`, and `file:bar.py` as `bar.py`: the extractor invented tokens the index never contained, and a resolver handed the basename could "prove" absent a file that exists. Backslash is now a separator; a token preceded by `:` is skipped. 3. ADAPTED. `bytes: -1` classified OK with 24,401 bytes of headroom; 1.5 and NaN likewise. A measurement is now a non-negative safe integer; anything else is MISSING, fail-closed. Zero stays a measurement — an empty index is under the cap — and the JSDoc says how that differs from the lint's `indexRead`, which asks whether there was anything to lint. 4. `dirExists: false` alongside a real `bytes` discarded the measurement and hid an over-budget index. The measurement wins; it is what was observed. 5. Two rows named `a` counted as two agents; an empty name became a clean "(unnamed agent)"; a null row manufactured a MISSING finding whose message asserted a directory exists. Rows with no identity, and duplicates, are now `malformedCount` — skipped, never measured, never a finding. 6. `TOOL_REF_RE` was quadratic on a long line with no match: 30,000 hyphens took 2.5 s, on an input the size of the budget this library polices. Segments are bounded and a linear pre-check skips lines with no candidate extension. 50,000 hyphens: 0 ms. 7. The README claimed the lint finds phantom tools while its quickstart passed no resolver, so the check never ran; and an async resolver returned a Promise, never `false`, so it silently never fired. The row now says opt-in and synchronous, the quickstart shows a resolver, and a thenable throws a TypeError — a caller error, not a data ambiguity. 194 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CkTF1JryMkGt4Qx2A2vHEh
Owner
Author
Codex adversarial round — 7 findings, 6 as written, 1 adaptedAll seven reproduced against the first commit before anything was touched. Folded in as one commit; each test names the number the old code returned.
194 tests pass. The |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
lib/memory-integrity.mjshad drifted behind the internal version it was extracted from. This closes the gap.The bug
DEFAULT_INDEX_BUDGET_BYTESreadMath.floor(24.4 * 1024)= 24,985. The loader's documented "24.4KB" is 24,400 decimal bytes; re-typing it as KiB gave the check 585 bytes of slack it was never granted — in the only direction that matters. An index between 24,401 and 24,985 bytes is silently truncated by the loader and passes this lint clean.The existing test asserted
DEFAULT_INDEX_BUDGET_BYTES + 1, which passes just as happily on the wrong constant. That is why it never caught it. The replacement pins the literal and both sides of the boundary.Also ported from origin
phantom-tool(opt-in via a newtoolResolveroption) — an index line that NAMES a tool or script which resolves to no file. The live instance: an index naming an "open-items projection" tool that never existed, because a compression pass kept the name and dropped the inline recipe. Unfollowable for eleven days, and every reader saw a capability the fleet didn't have. Extraction (extractToolReferences) is pure; existence is judged by a caller-supplied resolver, so the file stays fs-free. No resolver → the check doesn't run. Resolver returnsnullor throws → can't judge → no finding.classifyMemoryIndexSizes+MEMORY_SIZE_STATES— the fleet-wide read, which has a failure the single-dir lint does not: an agent whose index you could not measure.MISSINGis fail-closed and a finding (the dir exists, so sessions run there with no index at all); a dir that never existed is declared, not swept, and not a finding;sweptCountcounts only measured agents, so a run that measured nothing returns"nothing-swept"rather than"clean". Findings dominate — a one-agent fleet whose index is missing has zero measured agents, and calling that "nothing-swept" would bury the loudest instance of the exact failure the classifier exists to catch.LOADER_TRUNCATION_SENTENCE— one sentence, used by both budget messages, so every surface says the same thing about why the byte count matters: nothing errors, nothing warns, the tail is just gone.Deliberately not ported
mungeProjectPathandstripWorktreeSuffix. Both encode Claude-Code-specific path shapes (~/.claude/projectsname munging,.claude/worktreessuffixes), and both are about locating files — which this library does not do.Checks
188 tests pass, 0 skipped. Five new: the pinned budget boundary,
extractToolReferencesdedup/URL/placeholder handling, the phantom-tool resolver contract across all five resolver outcomes, and the twoclassifyMemoryIndexSizesverdict cases.Independent of #1 — branched from
main.🤖 Generated with Claude Code