agent-cache-optimizer 0.5.3 → 0.6.0

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.
@@ -0,0 +1,620 @@
1
+ import { mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs"
2
+ import { tmpdir } from "node:os"
3
+ import { join } from "node:path"
4
+ import { afterEach, describe, expect, it, vi } from "vitest"
5
+ import { hashContent } from "../core"
6
+
7
+ const stateDir = (cacheRoot: string) => join(cacheRoot, "opencode", "agent-cache-optimizer")
8
+
9
+ const model = (providerID: string, id = "deepseek-chat") =>
10
+ ({
11
+ id,
12
+ providerID,
13
+ name: id,
14
+ }) as any
15
+
16
+ async function withPlugin<T>(fn: (hooks: any, cacheRoot: string) => Promise<T>): Promise<T> {
17
+ const originalCacheHome = process.env.XDG_CACHE_HOME
18
+ const cacheRoot = mkdtempSync(join(tmpdir(), "aco-test-"))
19
+ process.env.XDG_CACHE_HOME = cacheRoot
20
+ vi.resetModules()
21
+
22
+ try {
23
+ const { CacheOptimizerPlugin } = await import("../index")
24
+ const hooks = await CacheOptimizerPlugin({} as any)
25
+ return await fn(hooks, cacheRoot)
26
+ } finally {
27
+ if (originalCacheHome === undefined) delete process.env.XDG_CACHE_HOME
28
+ else process.env.XDG_CACHE_HOME = originalCacheHome
29
+ rmSync(cacheRoot, { recursive: true, force: true })
30
+ vi.resetModules()
31
+ }
32
+ }
33
+
34
+ describe("CacheOptimizerPlugin provider/model scope", () => {
35
+ afterEach(() => {
36
+ vi.restoreAllMocks()
37
+ })
38
+
39
+ it("tracks the same model id separately for different providers", async () => {
40
+ await withPlugin(async (hooks, cacheRoot) => {
41
+ const system = [
42
+ "currentDate: 2026-06-25",
43
+ "You are a provider scoped cache optimizer. ".repeat(8),
44
+ ]
45
+
46
+ await hooks["experimental.chat.system.transform"](
47
+ { sessionID: "s1", model: model("deepseek") },
48
+ { system: [...system] },
49
+ )
50
+ await hooks["experimental.chat.system.transform"](
51
+ { sessionID: "s2", model: model("openrouter") },
52
+ { system: [...system] },
53
+ )
54
+
55
+ const files = readdirSync(stateDir(cacheRoot))
56
+ .filter((file) => file.startsWith("stability-"))
57
+ .sort()
58
+ expect(files).toEqual([
59
+ "stability-deepseek__deepseek-chat.json",
60
+ "stability-openrouter__deepseek-chat.json",
61
+ ])
62
+ })
63
+ })
64
+
65
+ it("writes loaded diagnostics once per provider/model scope", async () => {
66
+ await withPlugin(async (hooks, cacheRoot) => {
67
+ await hooks["chat.params"]({ agent: "build", model: model("deepseek") }, {})
68
+ await hooks["chat.params"]({ agent: "build", model: model("openrouter") }, {})
69
+
70
+ const log = readFileSync(join(stateDir(cacheRoot), "diag.log"), "utf-8")
71
+ expect(log).toContain("[deepseek__deepseek-chat__build] v0.6.0 loaded")
72
+ expect(log).toContain("[openrouter__deepseek-chat__build] v0.6.0 loaded")
73
+ })
74
+ })
75
+
76
+ it("does not multiply cumulative savings by the observation count twice", async () => {
77
+ await withPlugin(async (hooks, cacheRoot) => {
78
+ const stable = "You are a stable cacheable system prompt block. ".repeat(8)
79
+ const dynamic = "currentDate: 2026-06-25"
80
+
81
+ await hooks["experimental.chat.system.transform"](
82
+ { sessionID: "s1", model: model("openrouter") },
83
+ { system: [dynamic, stable] },
84
+ )
85
+ await hooks["experimental.chat.system.transform"](
86
+ { sessionID: "s2", model: model("openrouter") },
87
+ { system: [dynamic, stable] },
88
+ )
89
+
90
+ const savings = JSON.parse(readFileSync(join(stateDir(cacheRoot), "savings.json"), "utf-8"))
91
+ const expected = (Math.round(stable.length * 2 * 0.25) / 1_000_000) * 0.431
92
+ expect(savings.totalStableBytes).toBe(stable.length * 2)
93
+ expect(savings.totalObservations).toBe(2)
94
+ expect(savings.estimatedSavingsUSD).toBeCloseTo(expected, 12)
95
+ })
96
+ })
97
+
98
+ it("uses session agent context when system transform only has provider/model", async () => {
99
+ await withPlugin(async (hooks, cacheRoot) => {
100
+ const system = [
101
+ "currentDate: 2026-06-25",
102
+ "You are a stable cacheable system prompt block. ".repeat(8),
103
+ ]
104
+
105
+ await hooks["chat.params"](
106
+ { sessionID: "s-build", agent: "build", model: model("deepseek") },
107
+ {},
108
+ )
109
+ await hooks["chat.params"](
110
+ { sessionID: "s-review", agent: "review", model: model("deepseek") },
111
+ {},
112
+ )
113
+ await hooks["experimental.chat.system.transform"](
114
+ { sessionID: "s-build", model: model("deepseek") },
115
+ { system: [...system] },
116
+ )
117
+ await hooks["experimental.chat.system.transform"](
118
+ { sessionID: "s-review", model: model("deepseek") },
119
+ { system: [...system] },
120
+ )
121
+
122
+ const files = readdirSync(stateDir(cacheRoot))
123
+ .filter((file) => file.startsWith("stability-"))
124
+ .sort()
125
+ expect(files).toContain("stability-deepseek__deepseek-chat__build.json")
126
+ expect(files).toContain("stability-deepseek__deepseek-chat__review.json")
127
+ })
128
+ })
129
+
130
+ it("writes a provider-model family DB alongside agent-scoped DBs", async () => {
131
+ await withPlugin(async (hooks, cacheRoot) => {
132
+ const stable = "Stable shared prompt content for the model family. ".repeat(20)
133
+ const dynamic = "currentDate: 2026-06-25\nsession id: family-db"
134
+
135
+ await hooks["chat.params"](
136
+ { sessionID: "s-build-family", agent: "build", model: model("deepseek") },
137
+ {},
138
+ )
139
+ await hooks["experimental.chat.system.transform"](
140
+ { sessionID: "s-build-family", model: model("deepseek") },
141
+ { system: [dynamic, stable] },
142
+ )
143
+
144
+ const files = readdirSync(stateDir(cacheRoot))
145
+ .filter((file) => file.startsWith("stability-"))
146
+ .sort()
147
+
148
+ expect(files).toContain("stability-deepseek__deepseek-chat.json")
149
+ expect(files).toContain("stability-deepseek__deepseek-chat__build.json")
150
+ })
151
+ })
152
+
153
+ it("records provider cache metrics from message events without double-counting updates", async () => {
154
+ await withPlugin(async (hooks, cacheRoot) => {
155
+ await hooks["chat.params"](
156
+ { sessionID: "s-build", agent: "build", model: model("openrouter") },
157
+ {},
158
+ )
159
+
160
+ await hooks.event({
161
+ event: {
162
+ type: "message.updated",
163
+ properties: {
164
+ info: {
165
+ id: "assistant-1",
166
+ sessionID: "s-build",
167
+ role: "assistant",
168
+ providerID: "openrouter",
169
+ modelID: "deepseek-chat",
170
+ agent: "build",
171
+ cost: 0.01,
172
+ tokens: {
173
+ input: 100,
174
+ output: 20,
175
+ reasoning: 0,
176
+ cache: { read: 40, write: 10 },
177
+ },
178
+ },
179
+ },
180
+ },
181
+ })
182
+ await hooks.event({
183
+ event: {
184
+ type: "message.updated",
185
+ properties: {
186
+ info: {
187
+ id: "assistant-1",
188
+ sessionID: "s-build",
189
+ role: "assistant",
190
+ providerID: "openrouter",
191
+ modelID: "deepseek-chat",
192
+ agent: "build",
193
+ cost: 0.015,
194
+ tokens: {
195
+ input: 150,
196
+ output: 25,
197
+ reasoning: 0,
198
+ cache: { read: 70, write: 10 },
199
+ },
200
+ },
201
+ },
202
+ },
203
+ })
204
+
205
+ const metrics = JSON.parse(
206
+ readFileSync(join(stateDir(cacheRoot), "cache-metrics.json"), "utf-8"),
207
+ )
208
+ expect(metrics.total.inputTokens).toBe(150)
209
+ expect(metrics.total.outputTokens).toBe(25)
210
+ expect(metrics.total.cacheReadTokens).toBe(70)
211
+ expect(metrics.total.cacheWriteTokens).toBe(10)
212
+ expect(metrics.total.costUSD).toBeCloseTo(0.015, 12)
213
+ expect(metrics.scopes["openrouter__deepseek-chat__build"].cacheHitRate).toBeCloseTo(
214
+ 70 / (150 + 70),
215
+ 12,
216
+ )
217
+ const snapshotKeys = Object.keys(metrics.snapshots)
218
+ expect(snapshotKeys).toEqual([
219
+ `message:${hashContent("s-build")}:${hashContent("assistant-1")}`,
220
+ ])
221
+ expect(snapshotKeys[0]).not.toContain("s-build")
222
+ expect(snapshotKeys[0]).not.toContain("assistant-1")
223
+ })
224
+ })
225
+
226
+ it("skips duplicate zero-delta provider cache metric events", async () => {
227
+ await withPlugin(async (hooks, cacheRoot) => {
228
+ const update = {
229
+ event: {
230
+ type: "message.updated",
231
+ properties: {
232
+ info: {
233
+ id: "assistant-1",
234
+ sessionID: "s-build",
235
+ role: "assistant",
236
+ providerID: "openrouter",
237
+ modelID: "deepseek-chat",
238
+ agent: "build",
239
+ cost: 0.01,
240
+ tokens: {
241
+ input: 100,
242
+ output: 20,
243
+ reasoning: 0,
244
+ cache: { read: 40, write: 10 },
245
+ },
246
+ },
247
+ },
248
+ },
249
+ }
250
+
251
+ await hooks["chat.params"](
252
+ { sessionID: "s-build", agent: "build", model: model("openrouter") },
253
+ {},
254
+ )
255
+ await hooks.event(update)
256
+ await hooks.event(update)
257
+
258
+ const raw = readFileSync(join(stateDir(cacheRoot), "events.jsonl"), "utf-8")
259
+ const events = raw
260
+ .trim()
261
+ .split("\n")
262
+ .map((line) => JSON.parse(line))
263
+ const metricsEvents = events.filter((event) => event.type === "metrics")
264
+ const metrics = JSON.parse(
265
+ readFileSync(join(stateDir(cacheRoot), "cache-metrics.json"), "utf-8"),
266
+ )
267
+
268
+ expect(metricsEvents).toHaveLength(1)
269
+ expect(metrics.total.events).toBe(1)
270
+ })
271
+ })
272
+
273
+ it("migrates raw cache metric snapshot keys before applying deltas", async () => {
274
+ await withPlugin(async (hooks, cacheRoot) => {
275
+ const metricsDir = stateDir(cacheRoot)
276
+ mkdirSync(metricsDir, { recursive: true })
277
+ const existingTotals = {
278
+ events: 1,
279
+ inputTokens: 100,
280
+ outputTokens: 20,
281
+ cacheReadTokens: 40,
282
+ cacheWriteTokens: 10,
283
+ costUSD: 0.01,
284
+ cacheHitRate: 0.4,
285
+ }
286
+ writeFileSync(
287
+ join(metricsDir, "cache-metrics.json"),
288
+ JSON.stringify(
289
+ {
290
+ total: { ...existingTotals },
291
+ scopes: {
292
+ "openrouter__deepseek-chat__build": { ...existingTotals },
293
+ },
294
+ snapshots: {
295
+ "message:s-build:assistant-1": {
296
+ inputTokens: 100,
297
+ outputTokens: 20,
298
+ cacheReadTokens: 40,
299
+ cacheWriteTokens: 10,
300
+ costUSD: 0.01,
301
+ },
302
+ },
303
+ updated: 1,
304
+ },
305
+ null,
306
+ 2,
307
+ ),
308
+ )
309
+
310
+ await hooks["chat.params"](
311
+ { sessionID: "s-build", agent: "build", model: model("openrouter") },
312
+ {},
313
+ )
314
+ await hooks.event({
315
+ event: {
316
+ type: "message.updated",
317
+ properties: {
318
+ info: {
319
+ id: "assistant-1",
320
+ sessionID: "s-build",
321
+ role: "assistant",
322
+ providerID: "openrouter",
323
+ modelID: "deepseek-chat",
324
+ agent: "build",
325
+ cost: 0.015,
326
+ tokens: {
327
+ input: 150,
328
+ output: 25,
329
+ reasoning: 0,
330
+ cache: { read: 70, write: 10 },
331
+ },
332
+ },
333
+ },
334
+ },
335
+ })
336
+
337
+ const metrics = JSON.parse(
338
+ readFileSync(join(stateDir(cacheRoot), "cache-metrics.json"), "utf-8"),
339
+ )
340
+ expect(metrics.total.inputTokens).toBe(150)
341
+ expect(metrics.total.outputTokens).toBe(25)
342
+ expect(metrics.total.cacheReadTokens).toBe(70)
343
+ expect(metrics.total.cacheWriteTokens).toBe(10)
344
+ expect(metrics.total.costUSD).toBeCloseTo(0.015, 12)
345
+ expect(Object.keys(metrics.snapshots)).toEqual([
346
+ `message:${hashContent("s-build")}:${hashContent("assistant-1")}`,
347
+ ])
348
+ })
349
+ })
350
+
351
+ it("computes cache hit rate from cached plus uncached input tokens", async () => {
352
+ await withPlugin(async (hooks, cacheRoot) => {
353
+ await hooks["chat.params"](
354
+ { sessionID: "s-build", agent: "build", model: model("openrouter") },
355
+ {},
356
+ )
357
+
358
+ await hooks.event({
359
+ event: {
360
+ type: "message.updated",
361
+ properties: {
362
+ info: {
363
+ id: "assistant-1",
364
+ sessionID: "s-build",
365
+ role: "assistant",
366
+ providerID: "openrouter",
367
+ modelID: "deepseek-chat",
368
+ agent: "build",
369
+ cost: 0,
370
+ tokens: {
371
+ input: 109,
372
+ output: 4,
373
+ reasoning: 15,
374
+ cache: { read: 29952, write: 0 },
375
+ },
376
+ },
377
+ },
378
+ },
379
+ })
380
+
381
+ const metrics = JSON.parse(
382
+ readFileSync(join(stateDir(cacheRoot), "cache-metrics.json"), "utf-8"),
383
+ )
384
+ const hitRate = metrics.scopes["openrouter__deepseek-chat__build"].cacheHitRate
385
+ expect(hitRate).toBeCloseTo(29952 / (109 + 29952), 12)
386
+ expect(hitRate).toBeLessThanOrEqual(1)
387
+ })
388
+ })
389
+
390
+ it("persists scoped warm cache and promotes hashes to global after multiple scopes", async () => {
391
+ await withPlugin(async (hooks, cacheRoot) => {
392
+ const stable = "You are a stable cacheable system prompt block. ".repeat(8)
393
+ const dynamic = "currentDate: 2026-06-25"
394
+
395
+ await hooks["chat.params"](
396
+ { sessionID: "s-build", agent: "build", model: model("deepseek") },
397
+ {},
398
+ )
399
+ await hooks["chat.params"](
400
+ { sessionID: "s-review", agent: "review", model: model("deepseek") },
401
+ {},
402
+ )
403
+
404
+ for (let i = 0; i < 10; i++) {
405
+ await hooks["experimental.chat.system.transform"](
406
+ { sessionID: "s-build", model: model("deepseek") },
407
+ { system: [`${dynamic}-${i}`, stable] },
408
+ )
409
+ }
410
+ for (let i = 0; i < 10; i++) {
411
+ await hooks["experimental.chat.system.transform"](
412
+ { sessionID: "s-review", model: model("deepseek") },
413
+ { system: [`${dynamic}-${i}`, stable] },
414
+ )
415
+ }
416
+
417
+ const warm = JSON.parse(readFileSync(join(stateDir(cacheRoot), "warm-cache.json"), "utf-8"))
418
+ const stableHash = hashContent(stable)
419
+ expect(warm.version).toBe(2)
420
+ expect(warm.scopes["deepseek__deepseek-chat__build"]).toContain(stableHash)
421
+ expect(warm.scopes["deepseek__deepseek-chat__review"]).toContain(stableHash)
422
+ expect(warm.global).toContain(stableHash)
423
+ })
424
+ })
425
+
426
+ it("orders family-stable shared blocks before agent-specific stable blocks after agent switches", async () => {
427
+ await withPlugin(async (hooks, cacheRoot) => {
428
+ const sharedTools =
429
+ "Shared tool and project instructions stay identical across agents. ".repeat(30)
430
+ const buildPrompt = "You are the build agent with build-only instructions. ".repeat(30)
431
+ const reviewPrompt = "You are the review agent with review-only instructions. ".repeat(30)
432
+
433
+ await hooks["chat.params"](
434
+ { sessionID: "s-build", agent: "build", model: model("deepseek") },
435
+ {},
436
+ )
437
+ for (let i = 0; i < 3; i++) {
438
+ await hooks["experimental.chat.system.transform"](
439
+ { sessionID: "s-build", model: model("deepseek") },
440
+ { system: [`currentDate: 2026-06-25\nsession id: build-${i}`, buildPrompt, sharedTools] },
441
+ )
442
+ }
443
+
444
+ await hooks["chat.params"](
445
+ { sessionID: "s-review", agent: "review", model: model("deepseek") },
446
+ {},
447
+ )
448
+ for (let i = 0; i < 3; i++) {
449
+ await hooks["experimental.chat.system.transform"](
450
+ { sessionID: "s-review", model: model("deepseek") },
451
+ {
452
+ system: [`currentDate: 2026-06-25\nsession id: review-${i}`, reviewPrompt, sharedTools],
453
+ },
454
+ )
455
+ }
456
+
457
+ const output = {
458
+ system: ["currentDate: 2026-06-25\nsession id: review-final", reviewPrompt, sharedTools],
459
+ }
460
+ await hooks["experimental.chat.system.transform"](
461
+ { sessionID: "s-review", model: model("deepseek") },
462
+ output,
463
+ )
464
+
465
+ expect(output.system[0]).toBe(sharedTools)
466
+ expect(output.system[1]).toBe(reviewPrompt)
467
+ expect(output.system[2]).toContain("session id: review-final")
468
+
469
+ const raw = readFileSync(join(stateDir(cacheRoot), "events.jsonl"), "utf-8")
470
+ const events = raw
471
+ .trim()
472
+ .split("\n")
473
+ .map((line) => JSON.parse(line))
474
+ const transform = events.filter((event) => event.type === "transform").at(-1)
475
+
476
+ expect(transform.ranking).toMatchObject({
477
+ sharedStable: 1,
478
+ scopedStable: 1,
479
+ coldStable: 0,
480
+ })
481
+ expect(transform.ranking.sharedPrefixBytes).toBe(sharedTools.length)
482
+ })
483
+ })
484
+
485
+ it("writes structured events for statistics and debugging without raw ids", async () => {
486
+ await withPlugin(async (hooks, cacheRoot) => {
487
+ const stable = "You are a stable cacheable system prompt block. ".repeat(8)
488
+ const dynamic = "currentDate: 2026-06-25"
489
+
490
+ await hooks["chat.params"](
491
+ { sessionID: "s-sensitive-build", agent: "build", model: model("openrouter") },
492
+ {},
493
+ )
494
+ for (let i = 0; i < 10; i++) {
495
+ await hooks["experimental.chat.system.transform"](
496
+ { sessionID: "s-sensitive-build", model: model("openrouter") },
497
+ { system: [`${dynamic}-${i}`, stable] },
498
+ )
499
+ }
500
+ await hooks.event({
501
+ event: {
502
+ type: "message.updated",
503
+ properties: {
504
+ info: {
505
+ id: "msg-sensitive-1",
506
+ sessionID: "s-sensitive-build",
507
+ role: "assistant",
508
+ providerID: "openrouter",
509
+ modelID: "deepseek-chat",
510
+ agent: "build",
511
+ cost: 0.015,
512
+ tokens: {
513
+ input: 150,
514
+ output: 25,
515
+ reasoning: 0,
516
+ cache: { read: 70, write: 10 },
517
+ },
518
+ },
519
+ },
520
+ },
521
+ })
522
+
523
+ const raw = readFileSync(join(stateDir(cacheRoot), "events.jsonl"), "utf-8")
524
+ const events = raw
525
+ .trim()
526
+ .split("\n")
527
+ .map((line) => JSON.parse(line))
528
+ const types = events.map((event) => event.type)
529
+
530
+ expect(types).toContain("loaded")
531
+ expect(types).toContain("transform_seen")
532
+ expect(types).toContain("transform")
533
+ expect(types).toContain("warm_cache_update")
534
+ expect(types).toContain("metrics")
535
+ expect(raw).not.toContain("s-sensitive-build")
536
+ expect(raw).not.toContain("msg-sensitive-1")
537
+
538
+ const transform = events.find((event) => event.type === "transform")
539
+ expect(transform.sessionHash).toMatch(/^[a-f0-9]{16}$/)
540
+ expect(transform.counts).toMatchObject({ stable: 1, dynamic: 1, total: 2 })
541
+ expect(transform.classifier).toMatchObject({ unknown: 0 })
542
+
543
+ const seen = events.find((event) => event.type === "transform_seen")
544
+ expect(seen.sessionHash).toMatch(/^[a-f0-9]{16}$/)
545
+ expect(seen.rawBlockCount).toBe(2)
546
+ expect(seen.status).toBe("received")
547
+
548
+ const metrics = events.find((event) => event.type === "metrics")
549
+ expect(metrics.messageHash).toMatch(/^[a-f0-9]{16}$/)
550
+ expect(metrics.delta.cacheReadTokens).toBe(70)
551
+ expect(metrics.totals.cacheHitRate).toBeCloseTo(70 / (150 + 70), 12)
552
+
553
+ const warmUpdate = events.find((event) => event.type === "warm_cache_update")
554
+ expect(warmUpdate.scopedHashCount).toBeGreaterThan(0)
555
+ expect(warmUpdate.globalHashCount).toBeGreaterThanOrEqual(0)
556
+ })
557
+ })
558
+
559
+ it("records transform entry events for no-op system transforms", async () => {
560
+ await withPlugin(async (hooks, cacheRoot) => {
561
+ await hooks["chat.params"](
562
+ { sessionID: "s-sensitive-noop", agent: "build", model: model("openrouter") },
563
+ {},
564
+ )
565
+ await hooks["experimental.chat.system.transform"](
566
+ { sessionID: "s-sensitive-noop", model: model("openrouter") },
567
+ { system: ["single block"] },
568
+ )
569
+
570
+ const raw = readFileSync(join(stateDir(cacheRoot), "events.jsonl"), "utf-8")
571
+ const events = raw
572
+ .trim()
573
+ .split("\n")
574
+ .map((line) => JSON.parse(line))
575
+ const seen = events.find((event) => event.type === "transform_seen")
576
+
577
+ expect(seen.scope).toBe("openrouter__deepseek-chat__build")
578
+ expect(seen.sessionHash).toMatch(/^[a-f0-9]{16}$/)
579
+ expect(seen.rawBlockCount).toBe(1)
580
+ expect(seen.status).toBe("skipped")
581
+ expect(seen.reason).toBe("insufficient_system_blocks")
582
+ expect(raw).not.toContain("s-sensitive-noop")
583
+ expect(events.some((event) => event.type === "transform")).toBe(false)
584
+ })
585
+ })
586
+
587
+ it("splits a single long system block before deciding whether transform is a no-op", async () => {
588
+ await withPlugin(async (hooks, cacheRoot) => {
589
+ const dynamic = "currentDate: 2026-06-25"
590
+ const stableA = "You are stable cacheable instructions A. ".repeat(90)
591
+ const stableB = "You are stable cacheable instructions B. ".repeat(90)
592
+ const systemBlock = [dynamic, stableA, stableB].join("\n\n")
593
+ const output = { system: [systemBlock] }
594
+
595
+ await hooks["chat.params"](
596
+ { sessionID: "s-single-long", agent: "build", model: model("openrouter") },
597
+ {},
598
+ )
599
+ await hooks["experimental.chat.system.transform"](
600
+ { sessionID: "s-single-long", model: model("openrouter") },
601
+ output,
602
+ )
603
+
604
+ expect(output.system).toEqual([stableA, stableB, dynamic])
605
+
606
+ const raw = readFileSync(join(stateDir(cacheRoot), "events.jsonl"), "utf-8")
607
+ const events = raw
608
+ .trim()
609
+ .split("\n")
610
+ .map((line) => JSON.parse(line))
611
+ const seen = events.find((event) => event.type === "transform_seen")
612
+ const transform = events.find((event) => event.type === "transform")
613
+
614
+ expect(seen.rawBlockCount).toBe(1)
615
+ expect(seen.splitBlockCount).toBe(3)
616
+ expect(seen.status).toBe("received")
617
+ expect(transform.counts).toMatchObject({ stable: 2, dynamic: 1, total: 3 })
618
+ })
619
+ })
620
+ })
package/src/heuristics.ts CHANGED
@@ -38,9 +38,39 @@ export function coldStartScore(block: string, index: number, total: number): num
38
38
  const avgLineLen = block.length / Math.max(1, lines.length)
39
39
  if (lines.length > 15 && avgLineLen < 30) score = Math.min(score, 0.3)
40
40
 
41
+ const cap = volatileMetadataCap(block)
42
+ if (cap !== null) score = Math.min(score, cap)
43
+
41
44
  return score
42
45
  }
43
46
 
47
+ /**
48
+ * Dynamic meta-info / structural patterns.
49
+ *
50
+ * Run this as a final cap so structured boosts cannot move volatile metadata
51
+ * back into the stable prefix.
52
+ */
53
+ function volatileMetadataCap(block: string): number | null {
54
+ const dynamicPatterns = [
55
+ { re: /(^|\n)\s*["']?(currentDate|current date)["']?\s*[:=]/i, cap: 0.15 },
56
+ { re: /["'](currentDate|current date)["']\s*[:=]/i, cap: 0.15 },
57
+ { re: /(^|\n)\s*today is\b/i, cap: 0.15 },
58
+ {
59
+ re: /(^|\n)\s*["']?(session\s*id|session|timestamp|last updated|iso timestamp)["']?\s*[:=]/i,
60
+ cap: 0.25,
61
+ },
62
+ {
63
+ re: /["'](session\s*id|session|timestamp|last updated|iso timestamp)["']\s*[:=]/i,
64
+ cap: 0.25,
65
+ },
66
+ ]
67
+ let cap: number | null = null
68
+ for (const { re, cap: nextCap } of dynamicPatterns) {
69
+ if (re.test(block)) cap = cap === null ? nextCap : Math.min(cap, nextCap)
70
+ }
71
+ return cap
72
+ }
73
+
44
74
  // ── Classification ───────────────────────────────────────────────────
45
75
 
46
76
  /**
@@ -79,11 +109,18 @@ export function classify(
79
109
  // Priority 2: content-addressed score (primary)
80
110
  const contentScore = lookupContentScore(db, hash)
81
111
  if (contentScore !== null && db.contentObservations >= 2) {
82
- if (contentScore >= 0.7) { result.stable.push(item); continue }
83
- if (contentScore <= 0.2) { result.dynamic.push(item); continue }
112
+ if (contentScore >= 0.7) {
113
+ result.stable.push(item)
114
+ continue
115
+ }
116
+ if (contentScore <= 0.2) {
117
+ result.dynamic.push(item)
118
+ continue
119
+ }
120
+ // Middle range (0.2–0.7): fall through to cold-start for tiered classification
84
121
  }
85
122
 
86
- // Priority 3: position-based score (fallback)
123
+ // Priority 3: position-based score (fallback) or cold-start heuristic
87
124
  const known = lookupScore(db, hash)
88
125
  let score: number
89
126
  if (known !== null && warm) {
@@ -92,9 +129,9 @@ export function classify(
92
129
  score = coldStartScore(item, i, total)
93
130
  }
94
131
 
95
- if (score >= 0.7) result.stable.push(item)
96
- else if (score <= 0.3) result.dynamic.push(item)
97
- else result.unknown.push(item)
132
+ // Tiered classification: 0.5 threshold reduces unknown to near-empty
133
+ if (score >= 0.5) result.stable.push(item)
134
+ else result.dynamic.push(item)
98
135
  }
99
136
 
100
137
  return result