@dsh-cc/context-crusher 0.7.1 → 0.8.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +4 -0
  3. package/README.zh.md +4 -0
  4. package/lib/config.d.ts +9 -1
  5. package/lib/config.d.ts.map +1 -1
  6. package/lib/config.js +114 -1
  7. package/lib/config.js.map +1 -1
  8. package/lib/defer/counter.d.ts +44 -0
  9. package/lib/defer/counter.d.ts.map +1 -0
  10. package/lib/defer/counter.js +81 -0
  11. package/lib/defer/counter.js.map +1 -0
  12. package/lib/defer/gate.d.ts +41 -0
  13. package/lib/defer/gate.d.ts.map +1 -0
  14. package/lib/defer/gate.js +32 -0
  15. package/lib/defer/gate.js.map +1 -0
  16. package/lib/defer/pass.d.ts +70 -0
  17. package/lib/defer/pass.d.ts.map +1 -0
  18. package/lib/defer/pass.js +288 -0
  19. package/lib/defer/pass.js.map +1 -0
  20. package/lib/defer/residents.d.ts +126 -0
  21. package/lib/defer/residents.d.ts.map +1 -0
  22. package/lib/defer/residents.js +171 -0
  23. package/lib/defer/residents.js.map +1 -0
  24. package/lib/defer/swap.d.ts +57 -0
  25. package/lib/defer/swap.d.ts.map +1 -0
  26. package/lib/defer/swap.js +120 -0
  27. package/lib/defer/swap.js.map +1 -0
  28. package/lib/fold-counters.d.ts +29 -0
  29. package/lib/fold-counters.d.ts.map +1 -0
  30. package/lib/fold-counters.js +62 -0
  31. package/lib/fold-counters.js.map +1 -0
  32. package/lib/index.d.ts +20 -0
  33. package/lib/index.d.ts.map +1 -1
  34. package/lib/index.js +86 -15
  35. package/lib/index.js.map +1 -1
  36. package/lib/receipt.d.ts +45 -0
  37. package/lib/receipt.d.ts.map +1 -0
  38. package/lib/receipt.js +73 -0
  39. package/lib/receipt.js.map +1 -0
  40. package/lib/reducer.d.ts +52 -0
  41. package/lib/reducer.d.ts.map +1 -0
  42. package/lib/reducer.js +202 -0
  43. package/lib/reducer.js.map +1 -0
  44. package/lib/settings.d.ts.map +1 -1
  45. package/lib/settings.js +6 -3
  46. package/lib/settings.js.map +1 -1
  47. package/lib/types.d.ts +52 -1
  48. package/lib/types.d.ts.map +1 -1
  49. package/lib/verifier.d.ts +29 -0
  50. package/lib/verifier.d.ts.map +1 -0
  51. package/lib/verifier.js +57 -0
  52. package/lib/verifier.js.map +1 -0
  53. package/package.json +26 -13
package/lib/receipt.js ADDED
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Receipt schema and canonical renderer for the evidence-preserving reducer.
3
+ * @module @dsh-cc/context-crusher/receipt
4
+ */
5
+ import z from '@deepseek-ai/schemastery';
6
+ /** schemastery has no `.optional()` method — union with `undefined` instead. */
7
+ const opt = (t) => z.union([t, z.const(undefined)]);
8
+ /** schemastery schema for the receipt shape (types only; see parseReceipt for key-set checks). */
9
+ export const ReceiptSchema = z.object({
10
+ v: z.const(1),
11
+ cmd: z.string(),
12
+ exit: z.object({ ok: z.boolean(), code: opt(z.number().step(1)) }),
13
+ failures: z.array(z.object({ name: z.string(), evidence: z.string() })),
14
+ key_output: z.array(z.string()).max(8),
15
+ counts: opt(z.object({ pass: z.number(), fail: z.number(), skip: z.number() })),
16
+ });
17
+ /** Exact allowed key sets per object level (unknown keys are rejected, not stripped). */
18
+ const ROOT_KEYS = ['v', 'cmd', 'exit', 'failures', 'key_output'];
19
+ const ROOT_OPTIONAL_KEYS = ['counts'];
20
+ const EXIT_KEYS = ['ok'];
21
+ const EXIT_OPTIONAL_KEYS = ['code'];
22
+ const FAILURE_KEYS = ['name', 'evidence'];
23
+ const COUNTS_KEYS = ['pass', 'fail', 'skip'];
24
+ /** Every present key must be in `allowed ∪ optional`; absent optional keys are fine. */
25
+ function exactKeys(obj, allowed, optional = []) {
26
+ return Object.keys(obj).every((k) => allowed.includes(k) || optional.includes(k));
27
+ }
28
+ /**
29
+ * Parse and validate a raw receipt. Returns the receipt, or `null` when any
30
+ * check fails: schema types, the 8-entry `key_output` cap, or the explicit
31
+ * exact-key-set check at every object level (schemastery `z.object` keeps
32
+ * unknown keys rather than stripping them, so rejection is explicit here).
33
+ */
34
+ export function parseReceipt(raw) {
35
+ if (raw === null || typeof raw !== 'object')
36
+ return null;
37
+ const r = raw;
38
+ if (!exactKeys(r, ROOT_KEYS, ROOT_OPTIONAL_KEYS))
39
+ return null;
40
+ try {
41
+ ReceiptSchema(r);
42
+ }
43
+ catch {
44
+ return null;
45
+ }
46
+ const receipt = r;
47
+ if (!exactKeys(receipt.exit, EXIT_KEYS, EXIT_OPTIONAL_KEYS))
48
+ return null;
49
+ for (const f of receipt.failures) {
50
+ if (!exactKeys(f, FAILURE_KEYS))
51
+ return null;
52
+ }
53
+ if (receipt.counts !== undefined && !exactKeys(receipt.counts, COUNTS_KEYS))
54
+ return null;
55
+ return receipt;
56
+ }
57
+ /**
58
+ * Canonical few-line rendering (plan §3.5): deterministic, receipt body only.
59
+ * The caller appends the pinned `buildMarker(...)` line — never included here.
60
+ */
61
+ export function renderReceipt(r) {
62
+ const lines = [`cmd: ${r.cmd}`];
63
+ const code = r.exit.code === undefined ? '' : ` code ${r.exit.code}`;
64
+ lines.push(`exit: ${r.exit.ok ? 'ok' : 'failed'}${code}`);
65
+ for (const f of r.failures)
66
+ lines.push(`FAIL ${f.name}: ${f.evidence}`);
67
+ for (const q of r.key_output)
68
+ lines.push(`key: ${q}`);
69
+ if (r.counts)
70
+ lines.push(`counts: pass=${r.counts.pass} fail=${r.counts.fail} skip=${r.counts.skip}`);
71
+ return lines.join('\n');
72
+ }
73
+ //# sourceMappingURL=receipt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"receipt.js","sourceRoot":"","sources":["../src/receipt.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Evidence-preserving reducer (plan §3.1-§3.3, §3.7): the escalation stage
3
+ * between the deterministic route and the passthrough decision. One cheap-lane
4
+ * side query extracts a receipt from the (possibly truncated) tool output
5
+ * view; a deterministic verifier (verifier.ts) gates the replacement; ANY
6
+ * failure — eligibility, lane provenance, timeout, malformed JSON, failed
7
+ * verification — returns `undefined` so the caller passes the ORIGINAL bytes
8
+ * through untouched (never the deterministic route's sub-threshold candidate).
9
+ *
10
+ * Fail-soft is the hard invariant: this stage never throws (the caller's
11
+ * waterfall try/catch is the backstop, and the stage also guards internally).
12
+ *
13
+ * @module @dsh-cc/context-crusher/reducer
14
+ */
15
+ import type { Context } from '@deepseek-ai/cordis';
16
+ import type { ToolExecution } from '@dsh-cc/tools';
17
+ import type { CrusherMode, LedgerRow, ResolvedConfig } from './types.ts';
18
+ /** Tools whose invocation command line is reducer-eligible (§3.2). */
19
+ export declare const REDUCER_COMMAND_TOOLS: readonly string[];
20
+ /** Executed command line of a tool invocation, when it is a string (§3.2). */
21
+ export declare function invocationCommand(exec: ToolExecution): string | undefined;
22
+ /** §3.2 eligibility: command surface, configured patterns, flag. */
23
+ export declare function isReducerEligible(cfg: ResolvedConfig, exec: ToolExecution): boolean;
24
+ /**
25
+ * Head/tail truncation for oversized sources (§3.3): the view budget is the
26
+ * `reducerMaxInputTokens` cap, split ~20% head / ~80% tail of lines (build
27
+ * and test failures concentrate at the tail).
28
+ */
29
+ export declare function truncateView(text: string, maxTokens: number, estimate: (text: string) => number): string;
30
+ export interface ReducerStageDeps {
31
+ ctx: Context;
32
+ cfg: ResolvedConfig;
33
+ exec: ToolExecution;
34
+ originalText: string;
35
+ tokensBefore: number;
36
+ isError: boolean;
37
+ estimate: (text: string) => number;
38
+ projectKey: string | undefined;
39
+ mode: CrusherMode;
40
+ /** Store write for the full original text (only called in `on` mode). */
41
+ put: (projectKey: string, text: string) => Promise<string>;
42
+ ledgerRow: (row: Omit<LedgerRow, 'ts' | 'sessionId' | 'tool'>) => Promise<void>;
43
+ }
44
+ /**
45
+ * Run one reducer attempt. Returns the replacement text
46
+ * (`renderReceipt` + the verbatim pinned `buildMarker` line as the LAST
47
+ * line), or `undefined` = passthrough the original bytes. Every rejection is
48
+ * ledgered `applied:false` with a reason (§3.4, §3.7); the deterministic
49
+ * route's sub-threshold candidate is never mixed in.
50
+ */
51
+ export declare function reduceToolOutput(deps: ReducerStageDeps): Promise<string | undefined>;
52
+ //# sourceMappingURL=reducer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reducer.d.ts","sourceRoot":"","sources":["../src/reducer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAMlD,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAExE,sEAAsE;AACtE,eAAO,MAAM,qBAAqB,EAAE,SAAS,MAAM,EAAa,CAAA;AAoBhE,8EAA8E;AAC9E,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,GAAG,SAAS,CAIzE;AAED,oEAAoE;AACpE,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAMnF;AAED;;;;GAIG;AAGH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CASxG;AAYD,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,OAAO,CAAA;IACZ,GAAG,EAAE,cAAc,CAAA;IACnB,IAAI,EAAE,aAAa,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IAClC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAA;IAC9B,IAAI,EAAE,WAAW,CAAA;IACjB,yEAAyE;IACzE,GAAG,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAC1D,SAAS,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,GAAG,WAAW,GAAG,MAAM,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAChF;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAsH1F"}
package/lib/reducer.js ADDED
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Evidence-preserving reducer (plan §3.1-§3.3, §3.7): the escalation stage
3
+ * between the deterministic route and the passthrough decision. One cheap-lane
4
+ * side query extracts a receipt from the (possibly truncated) tool output
5
+ * view; a deterministic verifier (verifier.ts) gates the replacement; ANY
6
+ * failure — eligibility, lane provenance, timeout, malformed JSON, failed
7
+ * verification — returns `undefined` so the caller passes the ORIGINAL bytes
8
+ * through untouched (never the deterministic route's sub-threshold candidate).
9
+ *
10
+ * Fail-soft is the hard invariant: this stage never throws (the caller's
11
+ * waterfall try/catch is the backstop, and the stage also guards internally).
12
+ *
13
+ * @module @dsh-cc/context-crusher/reducer
14
+ */
15
+ import { resolveAlias } from '@dsh-cc/model-aliases';
16
+ import { runSideQuery } from '@dsh-cc/side-query';
17
+ import { buildMarker } from "./marker.js";
18
+ import { parseReceipt, renderReceipt } from "./receipt.js";
19
+ import { verifyReceipt } from "./verifier.js";
20
+ /** Tools whose invocation command line is reducer-eligible (§3.2). */
21
+ export const REDUCER_COMMAND_TOOLS = ['bash'];
22
+ /**
23
+ * System prompt for the extraction side query (§3.3, §3.7): pure extraction,
24
+ * no inference, no advice, strict JSON only. The prompt NEVER embeds the
25
+ * user's task or conversation history — only the single tool output view.
26
+ */
27
+ const REDUCER_SYSTEM = [
28
+ 'You extract evidence from tool output. Pure extraction only: never infer,',
29
+ 'never diagnose, never advise. Every "evidence" and "key_output" string MUST',
30
+ 'be copied verbatim from the tool output (no paraphrase, no trimming).',
31
+ 'Output ONLY strict JSON — no prose, no code fences — matching exactly:',
32
+ '{"v":1,"cmd":"first line of the invocation","exit":{"ok":boolean,',
33
+ '"code":number only when the output states an exit code},"failures":',
34
+ '[{"name":"test/file identifier","evidence":"exact quote from the output"}],',
35
+ '"key_output":["exact quote", up to 8 entries],"counts":',
36
+ '{"pass":number,"fail":number,"skip":number} only when the counts are stated}.',
37
+ '"failures" is empty on success; omit "counts" when unknowable.',
38
+ ].join('\n');
39
+ /** Executed command line of a tool invocation, when it is a string (§3.2). */
40
+ export function invocationCommand(exec) {
41
+ const args = exec.arguments;
42
+ const command = args === null || typeof args !== 'object' ? undefined : args.command;
43
+ return typeof command === 'string' && command.length > 0 ? command : undefined;
44
+ }
45
+ /** §3.2 eligibility: command surface, configured patterns, flag. */
46
+ export function isReducerEligible(cfg, exec) {
47
+ if (!cfg.reducerEnabled)
48
+ return false;
49
+ if (!REDUCER_COMMAND_TOOLS.includes(exec.name))
50
+ return false;
51
+ const command = invocationCommand(exec);
52
+ if (command === undefined)
53
+ return false;
54
+ return cfg.reducerCommands.some((pattern) => pattern.test(command));
55
+ }
56
+ /**
57
+ * Head/tail truncation for oversized sources (§3.3): the view budget is the
58
+ * `reducerMaxInputTokens` cap, split ~20% head / ~80% tail of lines (build
59
+ * and test failures concentrate at the tail).
60
+ */
61
+ // ponytail: per-line token cost is a linear average, not per-line estimates;
62
+ // if real logs skew the split, switch to incremental estimation.
63
+ export function truncateView(text, maxTokens, estimate) {
64
+ if (estimate(text) <= maxTokens)
65
+ return text;
66
+ const lines = text.split('\n');
67
+ if (lines.length < 2)
68
+ return text;
69
+ const tokensPerLine = Math.max(estimate(text) / lines.length, 0.01);
70
+ const headCount = Math.max(1, Math.floor((0.2 * maxTokens) / tokensPerLine));
71
+ const tailCount = Math.max(1, Math.floor((0.8 * maxTokens) / tokensPerLine));
72
+ if (headCount + tailCount >= lines.length)
73
+ return text;
74
+ return [...lines.slice(0, headCount), ...lines.slice(lines.length - tailCount)].join('\n');
75
+ }
76
+ /** Side-query failure reason → ledger reason (§3.7 fail-mode: passthrough). */
77
+ function laneReason(reason) {
78
+ switch (reason) {
79
+ case 'unrouted': return 'lane-missing';
80
+ case 'timeout': return 'lane-timeout';
81
+ case 'empty': return 'malformed';
82
+ case 'error': return 'lane-error';
83
+ }
84
+ }
85
+ /**
86
+ * Run one reducer attempt. Returns the replacement text
87
+ * (`renderReceipt` + the verbatim pinned `buildMarker` line as the LAST
88
+ * line), or `undefined` = passthrough the original bytes. Every rejection is
89
+ * ledgered `applied:false` with a reason (§3.4, §3.7); the deterministic
90
+ * route's sub-threshold candidate is never mixed in.
91
+ */
92
+ export async function reduceToolOutput(deps) {
93
+ try {
94
+ if (!isReducerEligible(deps.cfg, deps.exec))
95
+ return undefined;
96
+ const { originalText, tokensBefore } = deps;
97
+ const ledger = async (applied, tokensAfter, reason, hash, charsAfter) => {
98
+ await deps.ledgerRow({
99
+ kind: 'receipt',
100
+ charsBefore: originalText.length,
101
+ charsAfter: charsAfter ?? receiptBodyRef.length,
102
+ tokensBefore,
103
+ tokensAfter,
104
+ applied,
105
+ ...(reason === undefined ? {} : { reason }),
106
+ ...(hash === undefined ? {} : { hash }),
107
+ });
108
+ };
109
+ // The extraction view: the full text under the input cap, else head/tail.
110
+ // Verifier quotes are checked against this SAME view (§3.3).
111
+ const view = truncateView(originalText, deps.cfg.reducerMaxInputTokens, deps.estimate);
112
+ let receiptBodyRef = '';
113
+ // Lane provenance pre-check: resolve the reducer alias ourselves so the
114
+ // string-form (model-only) alias hole — `toOneShotRoute` fills the
115
+ // provider from the parent request — never bills the parent route (§3.3).
116
+ const agent = deps.exec.agent;
117
+ if (agent === undefined) {
118
+ await ledger(false, 0, 'lane-missing');
119
+ return undefined;
120
+ }
121
+ const route = resolveAlias(deps.ctx, deps.cfg.reducerAlias);
122
+ if (route === undefined) {
123
+ // Unconfigured alias: the side query would return 'unrouted' anyway;
124
+ // decline here so no model call is attempted at all (§4 test b).
125
+ await ledger(false, 0, 'lane-missing');
126
+ return undefined;
127
+ }
128
+ if (route.provider === undefined || route.provider.length === 0) {
129
+ await ledger(false, 0, 'lane-inherited');
130
+ return undefined;
131
+ }
132
+ const side = await runSideQuery(deps.ctx, {
133
+ agent,
134
+ alias: deps.cfg.reducerAlias,
135
+ onUnrouted: 'skip', // mandatory: an unrouted alias never bills the parent route
136
+ rejectToolCalls: true, // llm.stream-class: no tools, no seeds (§3.7)
137
+ system: REDUCER_SYSTEM,
138
+ prompt: [
139
+ 'Reduce the following tool output into the receipt JSON described in the system prompt.',
140
+ 'The output is the complete and only source; quote from it verbatim.',
141
+ '--- tool output start ---',
142
+ view,
143
+ '--- tool output end ---',
144
+ ].join('\n'),
145
+ maxTokens: deps.cfg.reducerMaxTokens,
146
+ timeoutMs: deps.cfg.reducerTimeoutMs,
147
+ });
148
+ if (!side.ok) {
149
+ await ledger(false, 0, laneReason(side.reason));
150
+ return undefined;
151
+ }
152
+ // Defense in depth for the inherited-route hole: side-query provenance.
153
+ if (side.inheritedRoute) {
154
+ await ledger(false, 0, 'lane-inherited');
155
+ return undefined;
156
+ }
157
+ let raw;
158
+ try {
159
+ raw = JSON.parse(side.text);
160
+ }
161
+ catch {
162
+ await ledger(false, 0, 'malformed');
163
+ return undefined;
164
+ }
165
+ const receipt = parseReceipt(raw);
166
+ if (receipt === null) {
167
+ await ledger(false, 0, 'malformed');
168
+ return undefined;
169
+ }
170
+ receiptBodyRef = renderReceipt(receipt);
171
+ // Verify against the SAME view used for extraction (§3.4).
172
+ const verdict = verifyReceipt(view, receipt, deps.isError, deps.estimate, {
173
+ minSavingsRatio: deps.cfg.reducerMinSavingsRatio,
174
+ });
175
+ if (!verdict.ok) {
176
+ const tokensAfter = deps.estimate(receiptBodyRef);
177
+ await ledger(false, tokensAfter, `verify:${verdict.reason}`, undefined, receiptBodyRef.length);
178
+ return undefined;
179
+ }
180
+ const tokensAfter = deps.estimate(receiptBodyRef);
181
+ if (deps.mode !== 'on') {
182
+ // Dry-run (§3.6): produce + verify only — no store.put, no substitution.
183
+ await ledger(false, tokensAfter, undefined, undefined, receiptBodyRef.length);
184
+ return undefined;
185
+ }
186
+ if (deps.projectKey === undefined)
187
+ return undefined;
188
+ // The FULL original text goes to the store, never the truncated view.
189
+ const hash = await deps.put(deps.projectKey, originalText);
190
+ // The LAST line is the verbatim pinned marker contract (§3.5): TUS
191
+ // isCrusherStub and the microcompact guard recognize it unchanged.
192
+ const replacementText = `${receiptBodyRef}\n${buildMarker(tokensBefore, tokensAfter, hash)}`;
193
+ await ledger(true, tokensAfter, undefined, hash, replacementText.length);
194
+ return replacementText;
195
+ }
196
+ catch (error) {
197
+ // Fail-soft (§3.7): any internal throw degrades to unchanged passthrough.
198
+ deps.ctx.logger.warn(`context-crusher: reducer stage degraded to passthrough: ${String(error)}`);
199
+ return undefined;
200
+ }
201
+ }
202
+ //# sourceMappingURL=reducer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reducer.js","sourceRoot":"","sources":["../src/reducer.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,iBAAiB,EAAoB,MAAM,2BAA2B,CAAA;AAEpF,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAE/D,gEAAgE;AAChE,eAAO,MAAM,kBAAkB,EAA+B,iBAAiB,CAAA;AAE/E,mFAAmF;AACnF,MAAM,MAAM,cAAc,GAAG,MAAM,aAAa,GAAG,SAAS,CAAA;AAE5D;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,GAAG,cAAc,CAWnF"}
1
+ {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAGlE,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAE/D,gEAAgE;AAChE,eAAO,MAAM,kBAAkB,EAA+B,iBAAiB,CAAA;AAE/E,mFAAmF;AACnF,MAAM,MAAM,cAAc,GAAG,MAAM,aAAa,GAAG,SAAS,CAAA;AAE5D;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,GAAG,cAAc,CAanF"}
package/lib/settings.js CHANGED
@@ -4,6 +4,7 @@
4
4
  * scope is re-read on EVERY use so a settings write applies immediately.
5
5
  * @module @dsh-cc/context-crusher/settings
6
6
  */
7
+ import { registerNamespaceSafe } from '@dsh-cc/settings-ns';
7
8
  import { Config, SettingsSchema } from "./config.js";
8
9
  /** The settings namespace carrying the live crusher overlay. */
9
10
  export const SETTINGS_NAMESPACE = 'cc-context-compression';
@@ -16,8 +17,7 @@ export const SETTINGS_NAMESPACE = 'cc-context-compression';
16
17
  * @returns the live scope reader.
17
18
  */
18
19
  export function registerSettings(ctx, base) {
19
- const settings = ctx.get('settings');
20
- const scope = settings?.register(SETTINGS_NAMESPACE, SettingsSchema, {
20
+ const read = registerNamespaceSafe(ctx, SETTINGS_NAMESPACE, SettingsSchema, {
21
21
  validate: (value) => {
22
22
  // Re-run the same cross-field validation as the config layer so a
23
23
  // half-written scope is rejected at write time.
@@ -25,6 +25,9 @@ export function registerSettings(ctx, base) {
25
25
  Config(merged);
26
26
  },
27
27
  });
28
- return () => scope?.get?.();
28
+ return () => {
29
+ const value = read();
30
+ return value === undefined ? undefined : { ...base, ...value };
31
+ };
29
32
  }
30
33
  //# sourceMappingURL=settings.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"settings.js","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAGpD,gEAAgE;AAChE,MAAM,CAAC,MAAM,kBAAkB,GAAG,wBAA6C,CAAA;AAK/E;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAY,EAAE,IAAoB;IACjE,MAAM,QAAQ,GAAG,GAAG,CAAC,GAAG,CAAC,UAAU,CAAiC,CAAA;IACpE,MAAM,KAAK,GAAG,QAAQ,EAAE,QAAQ,CAAC,kBAAkB,EAAE,cAAc,EAAE;QACnE,QAAQ,EAAE,CAAC,KAAoB,EAAE,EAAE;YACjC,kEAAkE;YAClE,gDAAgD;YAChD,MAAM,MAAM,GAAG,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,EAAmB,CAAA;YACrD,MAAM,CAAC,MAAM,CAAC,CAAA;QAChB,CAAC;KACF,CAAC,CAAA;IACF,OAAO,GAAG,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,EAA+B,CAAA;AAC1D,CAAC"}
1
+ {"version":3,"file":"settings.js","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAC3D,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAGpD,gEAAgE;AAChE,MAAM,CAAC,MAAM,kBAAkB,GAAG,wBAA6C,CAAA;AAK/E;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAY,EAAE,IAAoB;IACjE,MAAM,IAAI,GAAG,qBAAqB,CAAgB,GAAG,EAAE,kBAAkB,EAAE,cAAc,EAAE;QACzF,QAAQ,EAAE,CAAC,KAAoB,EAAE,EAAE;YACjC,kEAAkE;YAClE,gDAAgD;YAChD,MAAM,MAAM,GAAG,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,EAAmB,CAAA;YACrD,MAAM,CAAC,MAAM,CAAC,CAAA;QAChB,CAAC;KACF,CAAC,CAAA;IACF,OAAO,GAAG,EAAE;QACV,MAAM,KAAK,GAAG,IAAI,EAAE,CAAA;QACpB,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,EAAE,CAAA;IAChE,CAAC,CAAA;AACH,CAAC"}
package/lib/types.d.ts CHANGED
@@ -28,6 +28,39 @@ export interface CrusherConfig {
28
28
  * explicitly set list replaces the defaults entirely (not a union).
29
29
  */
30
30
  'protected-tools'?: string[];
31
+ /** Evidence-preserving reducer master flag (§3.2). Defaults to `false`. */
32
+ 'reducer-enabled'?: boolean;
33
+ /** Regex sources matched against the invocation command line (§3.2). */
34
+ 'reducer-commands'?: string[];
35
+ /** Head/tail truncation threshold in tokens (§3.3). Defaults to 30000. */
36
+ 'reducer-max-input-tokens'?: number;
37
+ /** Verifier size-gain ratio (§3.4.4). Defaults to 0.5. */
38
+ 'reducer-min-savings-ratio'?: number;
39
+ /** Side-query output token cap (§3.3). Defaults to 1024. */
40
+ 'reducer-max-tokens'?: number;
41
+ /** Side-query wall-clock budget in ms (§3.3). Defaults to 10000. */
42
+ 'reducer-timeout-ms'?: number;
43
+ /** Cheap-lane alias for the side query (§3.3). Defaults to 'haiku'. */
44
+ 'reducer-alias'?: string;
45
+ /**
46
+ * Deferred externalization residency (design §3.6): eligible results are
47
+ * stored immediately but enter the session UNMODIFIED, and are swapped to
48
+ * the standard stub after this many counted main-loop sends
49
+ * (`2` = paper parity). `0` (default) disables deferral — behavior is
50
+ * byte-identical to immediate mode.
51
+ */
52
+ 'defer-requests'?: number;
53
+ /** Suffix-cost gate multiplier for the deferred swap. Defaults to `1.5`. */
54
+ 'defer-margin'?: number;
55
+ /** Sweep age (ms) for residents that never swapped. Defaults to `1_800_000`. */
56
+ 'defer-max-age-ms'?: number;
57
+ /**
58
+ * Window-pressure override distance in tokens: when the session estimate
59
+ * comes within this distance of the model window, residents swap
60
+ * regardless of the gate. Unset = inactive (also inactive without a
61
+ * window source).
62
+ */
63
+ 'defer-urgency-tokens'?: number;
31
64
  }
32
65
  /** Validated, detached, deeply immutable configuration. */
33
66
  export interface ResolvedConfig {
@@ -36,6 +69,18 @@ export interface ResolvedConfig {
36
69
  readonly minBytes: number;
37
70
  readonly minSavingsRatio: number;
38
71
  readonly protectedTools: readonly string[];
72
+ /** Compiled command patterns (§3.2); invalid user regexes are dropped at resolve time. */
73
+ readonly reducerEnabled: boolean;
74
+ readonly reducerCommands: readonly RegExp[];
75
+ readonly reducerMaxInputTokens: number;
76
+ readonly reducerMinSavingsRatio: number;
77
+ readonly reducerMaxTokens: number;
78
+ readonly reducerTimeoutMs: number;
79
+ readonly reducerAlias: string;
80
+ readonly deferRequests: number;
81
+ readonly deferMargin: number;
82
+ readonly deferMaxAgeMs: number;
83
+ readonly deferUrgencyTokens?: number;
39
84
  }
40
85
  /** One append-only savings-ledger row (`savings.jsonl`). */
41
86
  export interface LedgerRow {
@@ -43,7 +88,13 @@ export interface LedgerRow {
43
88
  readonly sessionId: string;
44
89
  readonly tool: string;
45
90
  /** Router kind that fired, or the reason a gate passed through. */
46
- readonly kind: 'search' | 'log';
91
+ readonly kind: 'search' | 'log' | 'receipt';
92
+ /**
93
+ * Reducer-only detail: why the attempt did not apply (§3.4) —
94
+ * `lane-missing`, `lane-inherited`, `lane-timeout`, `lane-error`,
95
+ * `malformed`, or `verify:<check>`. Absent on plain router rows.
96
+ */
97
+ readonly reason?: string;
47
98
  readonly charsBefore: number;
48
99
  readonly charsAfter: number;
49
100
  readonly tokensBefore: number;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,6EAA6E;AAC7E,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,IAAI,CAAA;AAE1C;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,gEAAgE;IAChE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,wEAAwE;IACxE,IAAI,CAAC,EAAE,WAAW,CAAA;IAClB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,wEAAwE;IACxE,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAA;CAC7B;AAED,2DAA2D;AAC3D,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;IAChC,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAA;CAC3C;AAED,4DAA4D;AAC5D,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,mEAAmE;IACnE,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,KAAK,CAAA;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,iEAAiE;IACjE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,oDAAoD;AACpD,MAAM,MAAM,aAAa,GAAG,cAAc,GAAG,SAAS,GAAG,SAAS,CAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Deterministic receipt verifier for the evidence-preserving reducer.
3
+ * @module @dsh-cc/context-crusher/verifier
4
+ */
5
+ /** Rejection reasons are ledgered as `verify:<reason>`. */
6
+ export type VerifyResult = {
7
+ ok: true;
8
+ } | {
9
+ ok: false;
10
+ reason: string;
11
+ };
12
+ /**
13
+ * Pure verifier (plan §3.4) — no I/O, no service imports. `estimate` is
14
+ * injected (the call site binds the tokenMeter estimator; Phase 0 tests use a
15
+ * deterministic stub). Checks run in order, first failure wins:
16
+ *
17
+ * 1. `schema` — parse + explicit unknown-key rejection;
18
+ * 2. `quote` — every evidence/key_output string (≥ 8 chars, NFC-normalized)
19
+ * is a verbatim substring of the view;
20
+ * 3. `exit` — `exit.ok === !isError`, and an `exit.code` is present only when
21
+ * the view's tail (last 64 lines) verifiably carries that exact code;
22
+ * 4. `size` — `estimate(rendered)` < `minSavingsRatio` (default 0.5) ×
23
+ * `estimate(view)`;
24
+ * 5. `counts` — when `counts` present, `fail` must equal `failures.length`.
25
+ */
26
+ export declare function verifyReceipt(view: string, receipt: unknown, isError: boolean, estimate: (text: string) => number, opts?: {
27
+ minSavingsRatio?: number;
28
+ }): VerifyResult;
29
+ //# sourceMappingURL=verifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verifier.d.ts","sourceRoot":"","sources":["../src/verifier.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,2DAA2D;AAC3D,MAAM,MAAM,YAAY,GAAG;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAWvE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,EAClC,IAAI,CAAC,EAAE;IAAE,eAAe,CAAC,EAAE,MAAM,CAAA;CAAE,GAClC,YAAY,CA4Bd"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Deterministic receipt verifier for the evidence-preserving reducer.
3
+ * @module @dsh-cc/context-crusher/verifier
4
+ */
5
+ import { parseReceipt, renderReceipt } from "./receipt.js";
6
+ /** Exit-code patterns the view's tail may carry (plan §3.4.3b, conservative set). */
7
+ const EXIT_CODE_PATTERNS = [/exit(?:ed with)? code (\d+)/i, /make.*Error (\d+)/];
8
+ /** Number of trailing lines considered for exit-code verification. */
9
+ const TAIL_LINES = 64;
10
+ /** Minimum quote length for evidence / key_output entries (stops vacuous matches). */
11
+ const MIN_QUOTE_LENGTH = 8;
12
+ /**
13
+ * Pure verifier (plan §3.4) — no I/O, no service imports. `estimate` is
14
+ * injected (the call site binds the tokenMeter estimator; Phase 0 tests use a
15
+ * deterministic stub). Checks run in order, first failure wins:
16
+ *
17
+ * 1. `schema` — parse + explicit unknown-key rejection;
18
+ * 2. `quote` — every evidence/key_output string (≥ 8 chars, NFC-normalized)
19
+ * is a verbatim substring of the view;
20
+ * 3. `exit` — `exit.ok === !isError`, and an `exit.code` is present only when
21
+ * the view's tail (last 64 lines) verifiably carries that exact code;
22
+ * 4. `size` — `estimate(rendered)` < `minSavingsRatio` (default 0.5) ×
23
+ * `estimate(view)`;
24
+ * 5. `counts` — when `counts` present, `fail` must equal `failures.length`.
25
+ */
26
+ export function verifyReceipt(view, receipt, isError, estimate, opts) {
27
+ // 1. schema (types + exact key sets at every object level).
28
+ const r = parseReceipt(receipt);
29
+ if (r === null)
30
+ return { ok: false, reason: 'schema' };
31
+ // 2. exact quotes, NFC-normalized on both sides.
32
+ const normView = view.normalize('NFC');
33
+ for (const q of [...r.failures.map((f) => f.evidence), ...r.key_output]) {
34
+ if (q.length < MIN_QUOTE_LENGTH)
35
+ return { ok: false, reason: 'quote' };
36
+ if (!normView.includes(q.normalize('NFC')))
37
+ return { ok: false, reason: 'quote' };
38
+ }
39
+ // 3. exit consistency: exactly two decidable rules.
40
+ if (r.exit.ok !== !isError)
41
+ return { ok: false, reason: 'exit' };
42
+ if (r.exit.code !== undefined) {
43
+ const tail = normView.split('\n').slice(-TAIL_LINES).join('\n');
44
+ const match = EXIT_CODE_PATTERNS.map((p) => p.exec(tail)).find((m) => m !== null);
45
+ if (match === undefined || Number(match[1]) !== r.exit.code)
46
+ return { ok: false, reason: 'exit' };
47
+ }
48
+ // 4. size gain against the view (the same text the quotes verified against).
49
+ const ratio = opts?.minSavingsRatio ?? 0.5;
50
+ if (estimate(renderReceipt(r)) >= ratio * estimate(view))
51
+ return { ok: false, reason: 'size' };
52
+ // 5. count consistency.
53
+ if (r.counts && r.counts.fail !== r.failures.length)
54
+ return { ok: false, reason: 'counts' };
55
+ return { ok: true };
56
+ }
57
+ //# sourceMappingURL=verifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verifier.js","sourceRoot":"","sources":["../src/verifier.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAM1D,qFAAqF;AACrF,MAAM,kBAAkB,GAAG,CAAC,8BAA8B,EAAE,mBAAmB,CAAC,CAAA;AAEhF,sEAAsE;AACtE,MAAM,UAAU,GAAG,EAAE,CAAA;AAErB,sFAAsF;AACtF,MAAM,gBAAgB,GAAG,CAAC,CAAA;AAE1B;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,aAAa,CAC3B,IAAY,EACZ,OAAgB,EAChB,OAAgB,EAChB,QAAkC,EAClC,IAAmC;IAEnC,4DAA4D;IAC5D,MAAM,CAAC,GAAmB,YAAY,CAAC,OAAO,CAAC,CAAA;IAC/C,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAA;IAEtD,iDAAiD;IACjD,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;IACtC,KAAK,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,CAAC,UAAU,CAAC,EAAE,CAAC;QACxE,IAAI,CAAC,CAAC,MAAM,GAAG,gBAAgB;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAA;QACtE,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAA;IACnF,CAAC;IAED,oDAAoD;IACpD,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,OAAO;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAA;IAChE,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC9B,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC/D,MAAM,KAAK,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAA;QACjF,IAAI,KAAK,KAAK,SAAS,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAA;IACnG,CAAC;IAED,6EAA6E;IAC7E,MAAM,KAAK,GAAG,IAAI,EAAE,eAAe,IAAI,GAAG,CAAA;IAC1C,IAAI,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAA;IAE9F,wBAAwB;IACxB,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,CAAC,QAAQ,CAAC,MAAM;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAA;IAE3F,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAA;AACrB,CAAC"}