Conversation
Closes the ingestion half of audit H5. `optimize` now accepts multiple paths and
directories.
The architectural centerpiece could not run. `createContextBundle` produces
exactly one item, `applyCacheAwarePrefixLocking` pins everything inside the first
1,024 tokens, and `solve01Knapsack` places pinned items outside the candidate set
and always selects them — so item 0 was always pinned, `itemsPruned` was always
0, and the knapsack solver, cache-aware prefix locking, topology scoring, the
dependency graph and the git inspector could not affect any output the product
was able to produce.
Measured on `src/core` at `maxInputTokens: 4000`: 31 items, 15 pruned, 20,540
tokens saved by the planner.
`test/unit/fallback-render.test.ts` pinned the success path's `items.join('\n')`
as a latent defect and said whoever made an `emittedOutput` consumer multi-item
"should stop and read it". This is that change, so it is fixed rather than
inherited. One item renders as its content and nothing else — CLI, MCP and bench
stay byte-identical. More than one renders with a `==> path <==` header, which is
`head`/`tail`'s convention rather than a format invented here. It is not
collision-proof and nothing escapes it: the consumer is a model being given
context, legibility beats round-trip parsing, and anything needing to
machine-parse should read `finalBundle` from the trace. Fail-open is per file —
each file's original bytes, never a re-encoding — so §35 holds per item; the
stream as a whole is not byte-identical, because the headers are TokenDamper's.
Three defects this exposed, fixed here:
1. Pruning was scored as drift. `findUnwitnessedItems` had always exempted an
item absent from `after` — selection is not elision — but the ratios compared
whole bundles, so a pruned item's symbols vanished and `R_AST` read the planner
doing its job as semantic loss. The ratios now score retained items only,
guarded on ids actually corresponding: `id` is content-derived at construction
and preserved by the transforms, so a caller rebuilding its `after` bundle
independently would otherwise leave nothing to compare and report `S_k = 0` for
a gutted bundle. With no correspondence the whole bundle is compared, as
before — failing open to more measurement rather than less.
2. Whole-item elision of a symbol-bearing item is no longer attempted. Since §40,
`S_k = 1 - R_AST` for code, so destroying every symbol scores 1.0 against a
gate firing above 0.40; no threshold or flag lets it through. On a one-item
bundle that was invisible — the run fell back and emitted the input, which is
what skipping produces anyway. On a multi-item bundle two pure-`types.ts` files
were taking a 16-file batch down with them. Symbol-free items are unaffected.
3. `TD_PRESERVE:` matched its own implementation. `drift-tracker.ts` and
`cli/html-reporter.ts` each acquired a content marker they do not semantically
have; because `R_struct` is a bundle-scoped set, one phantom marker being
elided drove it to 0 and took a 16-file batch to `S_k = 0.4053` on a run whose
real symbol retention was 99.1%. Scoped to prose regions for `code` only — not
the prose types generally, because `TD_PRESERVE:` is an unambiguous token, and
the only way it appears without being a directive is as a literal inside an
expression. This also retires the `html-reporter.ts` regression §40 recorded as
left in deliberately.
Also: envelope headers were counted on the output side only, so a multi-item
fallback reported 72,973 -> 73,667 tokens — a negative reduction, the same shape
as the phantom -1.39% already diagnosed once in the Python bench harness.
Corpus, 586 rows: 1 row changed, 0 regressions. TypeScript 27.33% -> 29.55%.
Fallback counts drop sharply (prose 28 -> 9, TS stdin 57 -> 0) because doomed
elisions are no longer transformed-then-reverted; same bytes, less wasted work.
Not done, and now the binding constraint: multi-file runs still fall back on real
corpora. On the 45-file Python corpus drift is 0.0359 and AST is clean, but 26
constraint failures across 14 items revert all 45 — validation is bundle-scoped
and fallback is all-or-nothing (audit §3.1, Phase 1c, unstarted). This delivers
the mechanism; §3.1 stands between it and the outcome. Phase 1c's missing
prerequisite — attribution — now exists for constraint (§42), unwitnessed (§37)
and AST (`itemId`) failures.
Four pinned tests asserted behaviour this changes and were updated with their
findings preserved.
See DECISIONS §43.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Collaborator
Author
|
Superseded: Nothing is lost — all three verified as ancestors of |
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.
Closes the ingestion half of audit H5.
optimizenow accepts multiple paths and directories.The centerpiece could not run
ARCHITECTURE.md,README.mdand CLAUDE.md all put a "Stateless 0/1 Knapsack Planner" at the centre of the design. None of it could execute:createContextBundleproduces exactly one itemapplyCacheAwarePrefixLockingpins everything inside the first 1,024 tokenssolve01Knapsackplaces pinned items outside the candidate set and always selects themSo item 0 was always pinned,
itemsPrunedwas always 0, andplanner/knapsack.ts,planner/cache-aware.ts,topology/topology-scorer.ts,topology/dependency-graph.tsandtopology/git-inspector.tscould not affect any output the product was able to produce.Measured after the change, on
src/coreatmaxInputTokens: 4000: 31 items ingested, 15 pruned, 20,540 tokens saved by the planner alone.Output format
test/unit/fallback-render.test.tspinned the success path'sitems.join('\n')as a latent defect and said, in as many words, that whoever made anemittedOutputconsumer multi-item "should stop and read it". This is that change, so the defect is fixed rather than inherited.==> path <==header. That'shead/tail's convention, chosen because it's one a reader already knows. It is not collision-proof and nothing escapes it: the consumer is a model being given context, legibility beats round-trip parseability, and anything needing to machine-parse should readfinalBundlefrom the trace, which carries items structurally.Directory walks are sorted (order is load-bearing: prefix locking pins the first ~1,024 tokens, so it decides what bypasses the knapsack) and skip
node_modules,dist,.gitand similar.Three defects this exposed, fixed here
Each was invisible while every shipping bundle held one item.
findUnwitnessedItemsalways exempted pruned items — selection is not elision — but the ratios compared whole bundles, so a pruned item's symbols vanished andR_ASTread the planner doing its job as semantic lossS_k = 1 - R_ASTfor code, so destroying every symbol scores 1.0 against a gate firing above 0.40. No threshold or flag lets it through. Two pure-types.tsfiles (interfaces to lose, no bodies to elide) were sinking a 16-file batchTD_PRESERVE:matched its own implementationdrift-tracker.ts(the regex literal) andcli/html-reporter.ts(the highlighter for that directive) each acquired a phantom content marker.R_structis a bundle-scoped set, so one such marker being elided drove it to 0 and took a 16-file batch toS_k = 0.4053— on a run whose real symbol retention was 99.1%(3) also retires the
html-reporter.tsregression #4 recorded as "left in deliberately".Also fixed: envelope headers were counted on the output side only, so a multi-item fallback reported 72,973 → 73,667 tokens — a negative reduction, the same shape as the phantom −1.39% already diagnosed once in the Python bench harness (Issue 5).
Reviewer note — a defect I introduced and caught
Scoping drift to retained items made it silently blind when item ids don't correspond.
idis content-derived at construction and preserved by the transforms, so it's safe in the pipeline — but a caller rebuilding itsafterbundle independently would get fresh ids, leaving nothing to compare and reportingS_k = 0for a gutted bundle. That's precisely the "check that never ran" hazard invariant 10 exists for. It broke 5 tests immediately.The guard: the exemption applies only when at least one id survives, which is the evidence ids are being carried rather than regenerated. With no correspondence the whole bundle is compared, as before — failing open to more measurement rather than less. Worth an eye.
Measured
Frozen 293-file corpus, 586 rows: 1 row changed, 0 regressions. TypeScript 27.33% → 29.55%.
Fallback counts drop sharply (prose 28 → 9, TypeScript over stdin 57 → 0) because items whose elision was doomed are no longer transformed-then-reverted. Same emitted bytes, less wasted work.
Not done — §3.1 is now the binding constraint
Multi-file runs still fall back on real corpora, and not for any reason this change can fix. On the 45-file Python corpus: drift 0.0359, AST clean, 169 KB elided — and it falls back, because 26 constraint failures across 14 items revert all 45. Validation is bundle-scoped and fallback is all-or-nothing (audit §3.1, Phase 1c, unstarted).
This PR delivers the mechanism; §3.1 stands between it and the outcome.
Phase 1c's stated prerequisite was attribution, and that now exists for the classes that matter: constraint failures name their item (#6), unwitnessed items name theirs (#3), AST issues carry
itemId. Drift remains bundle-scoped and would need its own rule.Verification
npm run typecheck,npm run lint— cleannpx vitest run— 525 passing, 60 files (up from 518)ARCHITECTURE.mdis unchanged: multi-item bundles were always in the model (createBundleFromItemspredates this) and nothing about the linear pipeline moved. What changed is that a shipping adapter finally builds one.🤖 Generated with Claude Code