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.
- package/CHANGELOG.md +85 -22
- package/README.md +55 -33
- package/bin/aco +61 -6
- package/docs/superpowers/plans/2026-06-25-cross-agent-cache-sharing.md +549 -0
- package/docs/superpowers/specs/2026-06-25-cross-agent-cache-hit-design.md +102 -0
- package/package.json +1 -1
- package/src/__tests__/heuristics-splitting.test.ts +287 -0
- package/src/__tests__/plugin.test.ts +620 -0
- package/src/heuristics.ts +43 -6
- package/src/index.ts +724 -48
- package/src/splitting.ts +155 -15
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest"
|
|
2
|
+
import { coldStartScore, classify } from "../heuristics"
|
|
3
|
+
import { splitBlock } from "../splitting"
|
|
4
|
+
import { emptyDB } from "../core"
|
|
5
|
+
|
|
6
|
+
// ── coldStartScore ─────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
describe("coldStartScore", () => {
|
|
9
|
+
it("returns <=0.15 for currentDate-like line", () => {
|
|
10
|
+
const score = coldStartScore("currentDate: 2025-06-25", 5, 10)
|
|
11
|
+
expect(score).toBeLessThanOrEqual(0.15)
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it("returns <=0.25 for 'Current date:' line", () => {
|
|
15
|
+
const score = coldStartScore("Current date: Tuesday, June 25th 2026", 3, 10)
|
|
16
|
+
expect(score).toBeLessThanOrEqual(0.25)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it("returns <=0.25 for 'Today is' line", () => {
|
|
20
|
+
const score = coldStartScore("Today is a great day to build", 4, 10)
|
|
21
|
+
expect(score).toBeLessThanOrEqual(0.25)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it("returns <=0.25 for 'Session:' line", () => {
|
|
25
|
+
const score = coldStartScore("Session: abc-123-def", 2, 10)
|
|
26
|
+
expect(score).toBeLessThanOrEqual(0.25)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it("returns <=0.25 for 'session id' line", () => {
|
|
30
|
+
const score = coldStartScore("session id: xyz-987", 6, 10)
|
|
31
|
+
expect(score).toBeLessThanOrEqual(0.25)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it("returns <=0.25 for 'timestamp' line", () => {
|
|
35
|
+
const score = coldStartScore("timestamp: 1719300000", 3, 10)
|
|
36
|
+
expect(score).toBeLessThanOrEqual(0.25)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it("returns <=0.25 for 'Last updated' line", () => {
|
|
40
|
+
const score = coldStartScore("Last updated: 2026-06-25 13:00:00", 5, 10)
|
|
41
|
+
expect(score).toBeLessThanOrEqual(0.25)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it("returns <=0.25 for 'ISO timestamp' line", () => {
|
|
45
|
+
const score = coldStartScore("ISO timestamp: 2026-06-25T13:00:00Z", 4, 10)
|
|
46
|
+
expect(score).toBeLessThanOrEqual(0.25)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it("matches dynamic metadata labels case-insensitively", () => {
|
|
50
|
+
const filler = " stable-looking content".repeat(20)
|
|
51
|
+
expect(coldStartScore(`Timestamp: 1719300000${filler}`, 3, 10)).toBeLessThanOrEqual(0.25)
|
|
52
|
+
expect(coldStartScore(`Session ID: xyz-987${filler}`, 3, 10)).toBeLessThanOrEqual(0.25)
|
|
53
|
+
expect(coldStartScore(`LAST UPDATED: 2026-06-25${filler}`, 3, 10)).toBeLessThanOrEqual(0.25)
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
// ── classify unknown tiering ───────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
describe("classify", () => {
|
|
60
|
+
it("routes coldStart=0.5 blocks to stable instead of unknown", () => {
|
|
61
|
+
// 150-200 char block at index 2 with total ~8 → baseline score 0.5
|
|
62
|
+
// With new 0.5 threshold → stable (was unknown with old 0.7)
|
|
63
|
+
const db = emptyDB()
|
|
64
|
+
const block =
|
|
65
|
+
"A moderate length prompt block that does not trigger any special heuristic in coldStartScore and gets the baseline of 0.5 which is now our stable threshold."
|
|
66
|
+
const blocks = ["a", "b", block, "c", "d", "e", "f", "g"]
|
|
67
|
+
const result = classify(blocks, db, { warmThreshold: 0 })
|
|
68
|
+
expect(result.stable).toContain(block)
|
|
69
|
+
expect(result.unknown).toHaveLength(0)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it("routes short/metadata blocks to dynamic instead of unknown", () => {
|
|
73
|
+
const db = emptyDB()
|
|
74
|
+
const block = "Short meta block" // <100 chars → score ≤0.2 → dynamic
|
|
75
|
+
const result = classify(["prefix", block, "suffix"], db, { warmThreshold: 0 })
|
|
76
|
+
expect(result.dynamic).toContain(block)
|
|
77
|
+
expect(result.unknown).toHaveLength(0)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it("reduces unknown bucket size with tiered classification", () => {
|
|
81
|
+
const db = emptyDB()
|
|
82
|
+
const blocks = [
|
|
83
|
+
'{"role": "system", "content": "You are a helpful assistant."}', // structured, ~0.8 → stable
|
|
84
|
+
"currentDate: 2026-06-25", // dynamic pattern, ≤0.15 → dynamic
|
|
85
|
+
"50 char block that sits right in our heuristics", // short (< 100) → ≤0.2 → dynamic
|
|
86
|
+
]
|
|
87
|
+
const result = classify(blocks, db, { warmThreshold: 0 })
|
|
88
|
+
expect(result.unknown.length).toBeLessThan(blocks.length)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it("lets contentScore 0.2-0.7 range fall through to coldStartScore tiering", () => {
|
|
92
|
+
// When contentScore is in the middle range, it should fall through
|
|
93
|
+
// to coldStartScore which uses the new thresholds
|
|
94
|
+
const db = emptyDB()
|
|
95
|
+
const block = "A short-ish block that would have mid content score"
|
|
96
|
+
const result = classify(["p1", block, "p2"], db, { warmThreshold: 0 })
|
|
97
|
+
// All 3 blocks should be classified (none stuck in unknown limbo)
|
|
98
|
+
expect(result.dynamic.length + result.stable.length).toBe(3)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it("passes through structured stable blocks correctly", () => {
|
|
102
|
+
const db = emptyDB()
|
|
103
|
+
// JSON block at index 0 with total=3 → structured boost max(0.15, 0.8) = 0.8
|
|
104
|
+
const stable = '{"key": "value", "nested": {"a": 1}}'
|
|
105
|
+
const result = classify([stable, "p1", "p2"], db, { warmThreshold: 0 })
|
|
106
|
+
expect(result.stable).toContain(stable)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it("keeps structured currentDate metadata dynamic after structural boosts", () => {
|
|
110
|
+
const db = emptyDB()
|
|
111
|
+
const volatile = '{"currentDate": "2026-06-25", "instructions": "You are stable."}'
|
|
112
|
+
const result = classify([volatile, "p1", "p2"], db, { warmThreshold: 0 })
|
|
113
|
+
expect(result.dynamic).toContain(volatile)
|
|
114
|
+
expect(result.stable).not.toContain(volatile)
|
|
115
|
+
})
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
// ── splitBlock Markdown ### ────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
describe("splitBlock Markdown ###", () => {
|
|
121
|
+
it("splits long blocks at top-level # section headers", () => {
|
|
122
|
+
const block = `# Setup
|
|
123
|
+
Install the package
|
|
124
|
+
|
|
125
|
+
# Usage
|
|
126
|
+
Run the command
|
|
127
|
+
|
|
128
|
+
# Troubleshooting
|
|
129
|
+
Inspect logs`
|
|
130
|
+
const result = splitBlock(block, 50)
|
|
131
|
+
expect(result).toHaveLength(3)
|
|
132
|
+
expect(result[0]).toContain("# Setup")
|
|
133
|
+
expect(result[1]).toContain("# Usage")
|
|
134
|
+
expect(result[2]).toContain("# Troubleshooting")
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it("splits long blocks at ### section headers", () => {
|
|
138
|
+
const block = `# Overview
|
|
139
|
+
Some intro text
|
|
140
|
+
|
|
141
|
+
### Installation
|
|
142
|
+
Install via npm
|
|
143
|
+
|
|
144
|
+
### Configuration
|
|
145
|
+
Configure the tool
|
|
146
|
+
|
|
147
|
+
### Usage
|
|
148
|
+
Run the command`
|
|
149
|
+
const result = splitBlock(block, 50) // low threshold forces splitting
|
|
150
|
+
expect(result.length).toBeGreaterThanOrEqual(3)
|
|
151
|
+
expect(result[0]).toContain("# Overview")
|
|
152
|
+
expect(result[1]).toContain("### Installation")
|
|
153
|
+
expect(result[2]).toContain("### Configuration")
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it("does not split at ### when block is small enough", () => {
|
|
157
|
+
const block = `### Small
|
|
158
|
+
Content`
|
|
159
|
+
const result = splitBlock(block, 4000)
|
|
160
|
+
expect(result).toEqual([block])
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it("splits at ### when no ## exist and block exceeds threshold", () => {
|
|
164
|
+
// Has ### but no ## (exactly two hashes)
|
|
165
|
+
const block = `# Title
|
|
166
|
+
Some text
|
|
167
|
+
|
|
168
|
+
### Section One
|
|
169
|
+
Content here
|
|
170
|
+
|
|
171
|
+
### Section Two
|
|
172
|
+
More content here`
|
|
173
|
+
const result = splitBlock(block, 20)
|
|
174
|
+
expect(result.length).toBeGreaterThanOrEqual(2)
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
it("does not split headings inside fenced code blocks", () => {
|
|
178
|
+
const block = `# Real Section
|
|
179
|
+
Content
|
|
180
|
+
|
|
181
|
+
\`\`\`md
|
|
182
|
+
# Not A Section
|
|
183
|
+
## Still Code
|
|
184
|
+
\`\`\`
|
|
185
|
+
|
|
186
|
+
# Next Section
|
|
187
|
+
More content`
|
|
188
|
+
const result = splitBlock(block, 50)
|
|
189
|
+
expect(result).toHaveLength(2)
|
|
190
|
+
expect(result[0]).toContain("# Not A Section")
|
|
191
|
+
expect(result[1]).toContain("# Next Section")
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it("splits long markdown lists by top-level list items", () => {
|
|
195
|
+
const block = `- Alpha item
|
|
196
|
+
continued alpha
|
|
197
|
+
- Beta item
|
|
198
|
+
continued beta
|
|
199
|
+
- Gamma item
|
|
200
|
+
continued gamma`
|
|
201
|
+
const result = splitBlock(block, 30)
|
|
202
|
+
expect(result).toEqual([
|
|
203
|
+
"- Alpha item\n continued alpha",
|
|
204
|
+
"- Beta item\n continued beta",
|
|
205
|
+
"- Gamma item\n continued gamma",
|
|
206
|
+
])
|
|
207
|
+
})
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
// ── splitBlock JSON tool schema ────────────────────────────────────────
|
|
211
|
+
|
|
212
|
+
describe("splitBlock JSON arrays", () => {
|
|
213
|
+
it("splits a JSON array of tool definitions", () => {
|
|
214
|
+
const block = JSON.stringify(
|
|
215
|
+
[
|
|
216
|
+
{ name: "tool_a", description: "Tool A", parameters: { type: "object", properties: {} } },
|
|
217
|
+
{ name: "tool_b", description: "Tool B", parameters: { type: "object", properties: {} } },
|
|
218
|
+
{ name: "tool_c", description: "Tool C", parameters: { type: "object", properties: {} } },
|
|
219
|
+
],
|
|
220
|
+
null,
|
|
221
|
+
2,
|
|
222
|
+
)
|
|
223
|
+
const result = splitBlock(block, 100)
|
|
224
|
+
expect(result.length).toBeGreaterThanOrEqual(3)
|
|
225
|
+
expect(result[0]).toContain("tool_a")
|
|
226
|
+
expect(result[1]).toContain("tool_b")
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
it("splits consecutive JSON objects not in an array", () => {
|
|
230
|
+
const block = `{"name": "tool_a", "description": "First tool"}
|
|
231
|
+
{"name": "tool_b", "description": "Second tool"}
|
|
232
|
+
{"name": "tool_c", "description": "Third tool"}`
|
|
233
|
+
const result = splitBlock(block, 50)
|
|
234
|
+
expect(result.length).toBeGreaterThanOrEqual(3)
|
|
235
|
+
expect(result[0]).toContain("tool_a")
|
|
236
|
+
expect(result[1]).toContain("tool_b")
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
it("splits deeply nested JSON objects in an array", () => {
|
|
240
|
+
const block = JSON.stringify(
|
|
241
|
+
[
|
|
242
|
+
{
|
|
243
|
+
name: "complex_tool",
|
|
244
|
+
parameters: {
|
|
245
|
+
type: "object",
|
|
246
|
+
properties: {
|
|
247
|
+
field1: { type: "string", description: "A field" },
|
|
248
|
+
field2: { type: "number", description: "Another field" },
|
|
249
|
+
},
|
|
250
|
+
required: ["field1"],
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
name: "another_tool",
|
|
255
|
+
parameters: {
|
|
256
|
+
type: "object",
|
|
257
|
+
properties: {
|
|
258
|
+
param_a: { type: "string" },
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
null,
|
|
264
|
+
2,
|
|
265
|
+
)
|
|
266
|
+
const result = splitBlock(block, 200)
|
|
267
|
+
expect(result.length).toBeGreaterThanOrEqual(2)
|
|
268
|
+
expect(result[0]).toContain("complex_tool")
|
|
269
|
+
expect(result[1]).toContain("another_tool")
|
|
270
|
+
})
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
// ── splitBlock XML siblings ───────────────────────────────────────────
|
|
274
|
+
|
|
275
|
+
describe("splitBlock XML siblings", () => {
|
|
276
|
+
it("splits top-level XML sibling elements", () => {
|
|
277
|
+
const block = `<tool><name>a</name><description>first</description></tool>
|
|
278
|
+
<tool><name>b</name><description>second</description></tool>
|
|
279
|
+
<tool><name>c</name><description>third</description></tool>`
|
|
280
|
+
const result = splitBlock(block, 50)
|
|
281
|
+
expect(result).toEqual([
|
|
282
|
+
"<tool><name>a</name><description>first</description></tool>",
|
|
283
|
+
"<tool><name>b</name><description>second</description></tool>",
|
|
284
|
+
"<tool><name>c</name><description>third</description></tool>",
|
|
285
|
+
])
|
|
286
|
+
})
|
|
287
|
+
})
|