* test(compression): TDD failing tests for hard-budget post-pass (#17)
node:test suite for applyHardBudget — targetTokens, targetRatio, no-op,
force-preserve, determinism, both-wins, techniquesUsed, integration seam.
All tests fail (module not yet created) — TDD red step.
* feat(compression): hard-budget post-pass (#17) — compress to exactly N tokens
- types.ts: add targetTokens? and targetRatio? to CompressionConfig after contextBudget
- hardBudget.ts: applyHardBudget(body, {targetTokens?,targetRatio?}) → CompressionResult;
splits prose into sentences/lines, ranks by avg scoreToken ascending, drops lowest-
saliency units until ≤ target; UNIT_PRESERVE_RE guards numbers/URLs/errors/code;
targetTokens wins when both set; techniquesUsed:["hard-budget"]
- strategySelector.ts: hard-budget post-pass in runStackedCompression +
runStackedCompressionAsync after engine loop, before finalizeStackedResult;
gated on config.targetTokens || config.targetRatio; mergeStackStep + compressed=true
* fix(compression): preserve digit-less sensitive lines in hard-budget
UNIT_PRESERVE_RE only matched digits/URLs/error-headers/code-fences, so
stack-trace at-frames, key=value credential lines, and digit-less paths
were droppable and could be cut to hit the budget. Add specific anchors
(^\s*at\s, \/[\w.-]+\/, [A-Za-z_]\w*=\S) that never match a bare
end-of-sentence period (which would re-break the feature into a no-op).
Regression tests drive each unit to target=1 (drops every non-preserved
unit) and assert the sensitive line survives; plus a guard that plain
prose ending in a period stays droppable.
* fix(compression): distribute hard-budget across messages + warn when unreachable
Two related correctness fixes in applyHardBudget:
- Aggregate target was passed verbatim to compressText for EACH message,
so an N-message body could come back ~N× over budget. Distribute the
target proportionally per message (floor(target * msgTokens/total)) so
the SUM stays <= target.
- When every unit is preserve-guarded (or a single oversized preserved
unit), the result still exceeds target with no signal. Measure the
result and push a validationWarnings entry when it remains over budget,
so callers are not silently left over the limit.
Regression tests: a 4-message body with target 200 ends with TOTAL <= 200;
an all-numeric (all-preserved) body emits the 'could not reach target'
warning.
* fix(compression): run hard-budget post-pass when targetTokens/targetRatio is 0
The seam gate used `||`, so targetTokens:0 or targetRatio:0 (both falsy)
silently skipped the post-pass in runStackedCompression and
runStackedCompressionAsync. Switch to `!= null` so an explicit 0 still
engages the pass.
Regression test: applyStackedCompression with config.targetTokens:0 must
report 'hard-budget' in techniquesUsed.
* docs(changelog): restore hard-budget bullet (#17, eaten by rebase)