@compaction/cli 0.1.2 → 0.2.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/README.md +11 -1
- package/dist/cli/commands/compact.js +15 -0
- package/dist/cli/commands/context.d.ts +2 -0
- package/dist/cli/commands/context.js +130 -0
- package/dist/cli/commands/import.js +3 -1
- package/dist/cli/commands/init.js +184 -157
- package/dist/cli/commands/run.js +13 -1
- package/dist/cli/index.js +4 -1
- package/dist/cli/onboarding/InitTui.d.ts +52 -0
- package/dist/cli/onboarding/InitTui.js +153 -0
- package/dist/cli/onboarding/model.d.ts +39 -0
- package/dist/cli/onboarding/model.js +77 -0
- package/dist/cli/onboarding/should-use-tui.d.ts +25 -0
- package/dist/cli/onboarding/should-use-tui.js +35 -0
- package/dist/cli/onboarding/wordmark.d.ts +28 -0
- package/dist/cli/onboarding/wordmark.js +72 -0
- package/dist/core/aggregate-format.d.ts +3 -1
- package/dist/core/aggregate-format.js +18 -0
- package/dist/core/context-store-eval-cases.d.ts +6 -0
- package/dist/core/context-store-eval-cases.js +331 -0
- package/dist/core/context-store-eval.d.ts +140 -0
- package/dist/core/context-store-eval.js +138 -0
- package/dist/core/context-store-fs.d.ts +73 -0
- package/dist/core/context-store-fs.js +157 -0
- package/dist/core/context-store-sufficiency.d.ts +98 -0
- package/dist/core/context-store-sufficiency.js +135 -0
- package/dist/core/context-store.d.ts +155 -0
- package/dist/core/context-store.js +228 -0
- package/dist/core/report-generator.d.ts +32 -1
- package/dist/core/report-generator.js +67 -0
- package/dist/core/run-aggregator.d.ts +64 -0
- package/dist/core/run-aggregator.js +132 -3
- package/dist/core/session-aggregate.d.ts +43 -0
- package/dist/core/session-aggregate.js +89 -7
- package/dist/core/trace-adapters.d.ts +8 -0
- package/dist/core/trace-adapters.js +4 -0
- package/dist/core/types.d.ts +55 -0
- package/package.json +5 -1
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { makeContextItem } from "./context-store.js";
|
|
2
|
+
/** Label a fixture `sufficiency` — gated against the sufficiency thresholds. */
|
|
3
|
+
function sufficiency(fixture) {
|
|
4
|
+
return { ...fixture, role: "sufficiency" };
|
|
5
|
+
}
|
|
6
|
+
/** Label a fixture `adversarial` — NOT gated; a regression anchor with an expected outcome. */
|
|
7
|
+
function adversarial(fixture, expected) {
|
|
8
|
+
return { ...fixture, role: "adversarial", expected };
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Canonical fixtures for the V0.4 context-store eval harness (rung-1).
|
|
12
|
+
*
|
|
13
|
+
* These are SYNTHETIC fixtures — hand-authored stores + queries with known task-critical
|
|
14
|
+
* items — used to MEASURE deterministic retrieval/assembly behavior. They are not captured
|
|
15
|
+
* sessions and carry no real-world claim. `created_at` values are fixed so the suite is
|
|
16
|
+
* fully deterministic (apart from observed latency).
|
|
17
|
+
*/
|
|
18
|
+
const T0 = "2026-06-20T09:00:00.000Z";
|
|
19
|
+
const T1 = "2026-06-21T09:00:00.000Z";
|
|
20
|
+
const T2 = "2026-06-22T09:00:00.000Z";
|
|
21
|
+
function fact(content, source_pointer, created_at) {
|
|
22
|
+
return makeContextItem({
|
|
23
|
+
content,
|
|
24
|
+
source_pointer,
|
|
25
|
+
source_trace_id: source_pointer.split(" ")[0]?.replace("trace=", ""),
|
|
26
|
+
recoverability: true,
|
|
27
|
+
created_at
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/** Fixture A: the task-critical item is lexically obvious and fits the budget. */
|
|
31
|
+
const lexicalRecall = {
|
|
32
|
+
name: "lexical-recall",
|
|
33
|
+
description: "Task-critical item is lexically related to the query and fits the budget.",
|
|
34
|
+
store: [
|
|
35
|
+
fact("the auth token must be refreshed before every batch upload", "trace=A message=m1", T0),
|
|
36
|
+
fact("the user prefers a restrained blue palette in the TUI", "trace=A message=m2", T1),
|
|
37
|
+
fact("retry the upload with exponential backoff on 503", "trace=A message=m3", T2)
|
|
38
|
+
],
|
|
39
|
+
query: { text: "how do we handle the auth token before an upload batch" },
|
|
40
|
+
tokenBudget: 1000,
|
|
41
|
+
goldItems: ["the auth token must be refreshed before every batch upload"],
|
|
42
|
+
requiredSourcePointers: ["trace=A message=m1"],
|
|
43
|
+
continuationRequirements: ["auth token", "batch upload"]
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Fixture B: the source-pointer boost is the DECIDING factor. The task-critical item
|
|
47
|
+
* (trace B) has weak lexical overlap with the query, while a non-gold distractor (trace C)
|
|
48
|
+
* has high lexical overlap. The budget fits only one item — so without the trace-id boost
|
|
49
|
+
* the distractor would win the slot and the task-critical item would be lost. The boost is
|
|
50
|
+
* what makes the right item survive.
|
|
51
|
+
*/
|
|
52
|
+
const goldDecision = "decision: keep the public surface at eight commands for rung-1";
|
|
53
|
+
const sourcePointerRecall = {
|
|
54
|
+
name: "source-pointer-recall",
|
|
55
|
+
description: "Under budget pressure, the exact trace-id hint is the deciding factor that keeps a lexically-weak task-critical item.",
|
|
56
|
+
store: [
|
|
57
|
+
fact(goldDecision, "trace=B message=m1", T0),
|
|
58
|
+
// Distractor: high lexical overlap with the query, but NOT task-critical (trace C, unboosted).
|
|
59
|
+
fact("what did we decide about the command surface here today", "trace=C message=m1", T1)
|
|
60
|
+
],
|
|
61
|
+
query: { text: "what did we decide about the command surface", trace_ids: ["B"] },
|
|
62
|
+
// Budget fits exactly one item — forces the boost to decide which survives.
|
|
63
|
+
tokenBudget: makeContextItem({ content: goldDecision, created_at: T0 }).estimated_tokens,
|
|
64
|
+
goldItems: [goldDecision],
|
|
65
|
+
requiredSourcePointers: ["trace=B message=m1"],
|
|
66
|
+
continuationRequirements: ["eight commands"]
|
|
67
|
+
};
|
|
68
|
+
/** Fixture C: the budget forces a drop — the higher-ranked task-critical item must win. */
|
|
69
|
+
const budgetPressure = {
|
|
70
|
+
name: "budget-pressure",
|
|
71
|
+
description: "Under a tight budget the task-critical (higher-ranked) item must survive.",
|
|
72
|
+
store: [
|
|
73
|
+
fact("critical: the migration must run before the API deploy", "trace=D message=m1", T0),
|
|
74
|
+
fact("aside: someone mentioned the office coffee machine is broken", "trace=D message=m2", T1)
|
|
75
|
+
],
|
|
76
|
+
query: { text: "what must happen before the api deploy migration" },
|
|
77
|
+
// Budget large enough for one short item only.
|
|
78
|
+
tokenBudget: makeContextItem({ content: "critical: the migration must run before the API deploy", created_at: T0 })
|
|
79
|
+
.estimated_tokens,
|
|
80
|
+
goldItems: ["critical: the migration must run before the API deploy"],
|
|
81
|
+
requiredSourcePointers: ["trace=D message=m1"],
|
|
82
|
+
continuationRequirements: ["migration", "api deploy"]
|
|
83
|
+
};
|
|
84
|
+
export const CONTEXT_STORE_EVAL_FIXTURES = [
|
|
85
|
+
sufficiency(lexicalRecall),
|
|
86
|
+
sufficiency(sourcePointerRecall),
|
|
87
|
+
sufficiency(budgetPressure)
|
|
88
|
+
];
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
// Richer / adversarial fixtures (V0.4 rung-1, 2026-06-24).
|
|
91
|
+
//
|
|
92
|
+
// PURPOSE: make the eval harness HARDER TO GAME before any threshold is set. Unlike the
|
|
93
|
+
// healthy baseline above (every axis passes), several of these fixtures are designed so a
|
|
94
|
+
// SPECIFIC axis legitimately FAILS — proving the harness reports honest sub-1.0 metrics and
|
|
95
|
+
// cannot be satisfied by a degenerate retriever. Expected per-axis outcomes live next to the
|
|
96
|
+
// assertions in `tests/core/context-store-eval-richer.test.ts`. No thresholds, no claim.
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
/** Flexible item builder — source pointer / recoverability are optional (for the negative cases). */
|
|
99
|
+
function mk(content, opts = {}) {
|
|
100
|
+
return makeContextItem({
|
|
101
|
+
content,
|
|
102
|
+
source_pointer: opts.pointer,
|
|
103
|
+
source_trace_id: opts.trace ?? (opts.pointer ? opts.pointer.split(" ")[0]?.replace("trace=", "") : undefined),
|
|
104
|
+
recoverability: opts.recoverable ?? true,
|
|
105
|
+
created_at: opts.created_at ?? T0
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
function tokensOf(content) {
|
|
109
|
+
return makeContextItem({ content, created_at: T0 }).estimated_tokens;
|
|
110
|
+
}
|
|
111
|
+
/** R1 — task-critical item survives budget pressure against several genuinely-retrieved competitors. */
|
|
112
|
+
const budgetPressureMulti = {
|
|
113
|
+
name: "budget-pressure-multi",
|
|
114
|
+
description: "Gold outranks several genuinely-retrieved competitors and wins the only budget slot.",
|
|
115
|
+
store: [
|
|
116
|
+
mk("critical: rotate the signing key before the release cutover", { pointer: "trace=E message=m1" }),
|
|
117
|
+
mk("rotate the release plan before cutover today", { pointer: "trace=E message=m2", created_at: T1 }),
|
|
118
|
+
mk("the signing key was discussed", { pointer: "trace=E message=m3", created_at: T2 })
|
|
119
|
+
],
|
|
120
|
+
query: { text: "rotate the signing key before release cutover" },
|
|
121
|
+
tokenBudget: tokensOf("critical: rotate the signing key before the release cutover"),
|
|
122
|
+
goldItems: ["critical: rotate the signing key before the release cutover"],
|
|
123
|
+
requiredSourcePointers: ["trace=E message=m1"],
|
|
124
|
+
continuationRequirements: ["signing key", "release cutover"]
|
|
125
|
+
};
|
|
126
|
+
/** R2 — source coverage FAILS: a second required source is dropped under budget, gold still survives. */
|
|
127
|
+
const sourceCoverageMiss = {
|
|
128
|
+
name: "source-coverage-miss",
|
|
129
|
+
description: "Two sources are required but the budget fits only the gold item; the other required source is missing.",
|
|
130
|
+
store: [
|
|
131
|
+
mk("the api key rotation runbook lives in the wiki", { pointer: "trace=A message=m1" }),
|
|
132
|
+
mk("the staging database snapshot policy", { pointer: "trace=B message=m1", created_at: T1 })
|
|
133
|
+
],
|
|
134
|
+
query: { text: "where is the api key rotation runbook" },
|
|
135
|
+
tokenBudget: tokensOf("the api key rotation runbook lives in the wiki"),
|
|
136
|
+
goldItems: ["the api key rotation runbook lives in the wiki"],
|
|
137
|
+
requiredSourcePointers: ["trace=A message=m1", "trace=B message=m1"],
|
|
138
|
+
continuationRequirements: ["rotation runbook"]
|
|
139
|
+
};
|
|
140
|
+
/** R3 — continuation success FAILS: one required marker is absent from the assembled text. */
|
|
141
|
+
const continuationMarkerAbsent = {
|
|
142
|
+
name: "continuation-marker-absent",
|
|
143
|
+
description: "The gold item is assembled but a required continuation marker is not present in it.",
|
|
144
|
+
store: [mk("deploy step: run the smoke test then promote the build", { pointer: "trace=F message=m1" })],
|
|
145
|
+
query: { text: "what is the deploy step for promote" },
|
|
146
|
+
tokenBudget: 1000,
|
|
147
|
+
goldItems: ["deploy step: run the smoke test then promote the build"],
|
|
148
|
+
requiredSourcePointers: ["trace=F message=m1"],
|
|
149
|
+
// "smoke test" is present; "rollback plan" is absent -> continuation_success = 0.5.
|
|
150
|
+
continuationRequirements: ["smoke test", "rollback plan"]
|
|
151
|
+
};
|
|
152
|
+
// R4/R5 share a store: a high-lexical-overlap distractor (trace C) vs a lexically-weak gold (trace B).
|
|
153
|
+
const trapGold = "rollback procedure: restore the retained original artifact";
|
|
154
|
+
const trapDistractor = "which note covers the deploy step we discussed";
|
|
155
|
+
const lexicalTrapStore = [
|
|
156
|
+
mk(trapGold, { pointer: "trace=B message=m1" }),
|
|
157
|
+
mk(trapDistractor, { pointer: "trace=C message=m1", created_at: T1 })
|
|
158
|
+
];
|
|
159
|
+
/** R4 — lexical trap, source pointer SAVES it: the trace-id boost lifts the gold over the distractor. */
|
|
160
|
+
const lexicalTrapPointerSaves = {
|
|
161
|
+
name: "lexical-trap-pointer-saves",
|
|
162
|
+
description: "An irrelevant high-overlap distractor would win, but the trace-id hint lifts the gold into the slot.",
|
|
163
|
+
store: lexicalTrapStore,
|
|
164
|
+
query: { text: "which note covers the deploy rollback step", trace_ids: ["B"] },
|
|
165
|
+
tokenBudget: tokensOf(trapGold),
|
|
166
|
+
goldItems: [trapGold],
|
|
167
|
+
requiredSourcePointers: ["trace=B message=m1"],
|
|
168
|
+
continuationRequirements: ["retained original"]
|
|
169
|
+
};
|
|
170
|
+
/** R5 — lexical trap, NO pointer: the irrelevant distractor wins the slot, gold is lost (recall 0). */
|
|
171
|
+
const lexicalTrapNoPointer = {
|
|
172
|
+
name: "lexical-trap-no-pointer",
|
|
173
|
+
description: "Without a source-pointer hint the high-overlap distractor wins the slot and the gold is legitimately lost.",
|
|
174
|
+
store: lexicalTrapStore,
|
|
175
|
+
query: { text: "which note covers the deploy step" },
|
|
176
|
+
tokenBudget: tokensOf(trapDistractor),
|
|
177
|
+
goldItems: [trapGold],
|
|
178
|
+
requiredSourcePointers: ["trace=B message=m1"],
|
|
179
|
+
continuationRequirements: ["retained original"]
|
|
180
|
+
};
|
|
181
|
+
/** R6 — duplicate/redundant context must NOT inflate success: 3 byte-identical golds collapse to one. */
|
|
182
|
+
const goldCanary = "the canary must pass before full rollout";
|
|
183
|
+
const duplicateNoInflation = {
|
|
184
|
+
name: "duplicate-no-inflation",
|
|
185
|
+
description: "Three byte-identical copies of the gold plus one filler assemble to two distinct items, not four.",
|
|
186
|
+
store: [
|
|
187
|
+
mk(goldCanary, { pointer: "trace=G message=m1" }),
|
|
188
|
+
mk(goldCanary, { pointer: "trace=G message=m1", created_at: T1 }),
|
|
189
|
+
mk(goldCanary, { pointer: "trace=G message=m1", created_at: T2 }),
|
|
190
|
+
mk("full rollout schedule for the canary release", { pointer: "trace=H message=m1", created_at: T2 })
|
|
191
|
+
],
|
|
192
|
+
query: { text: "when does the canary pass before rollout" },
|
|
193
|
+
tokenBudget: 1000,
|
|
194
|
+
goldItems: [goldCanary],
|
|
195
|
+
requiredSourcePointers: ["trace=G message=m1"],
|
|
196
|
+
continuationRequirements: ["canary"]
|
|
197
|
+
};
|
|
198
|
+
/** R7 — recoverability DROPS when an assembled item lacks a source pointer. */
|
|
199
|
+
const recoverabilityMissingPointers = {
|
|
200
|
+
name: "recoverability-missing-pointers",
|
|
201
|
+
description: "Two task-critical items are assembled; one carries no source pointer, so recoverability is 0.5.",
|
|
202
|
+
store: [
|
|
203
|
+
mk("retain the original artifact for rollback", { pointer: "trace=A message=m1" }),
|
|
204
|
+
// No source pointer: flagged recoverable but cannot actually be recovered (no pointer to recover to).
|
|
205
|
+
mk("the diff summary should be reviewed before apply", { recoverable: true, created_at: T1 })
|
|
206
|
+
],
|
|
207
|
+
query: { text: "what to retain and review before apply rollback" },
|
|
208
|
+
tokenBudget: 1000,
|
|
209
|
+
goldItems: ["retain the original artifact for rollback", "the diff summary should be reviewed before apply"],
|
|
210
|
+
requiredSourcePointers: ["trace=A message=m1"],
|
|
211
|
+
continuationRequirements: ["retain", "review"]
|
|
212
|
+
};
|
|
213
|
+
export const CONTEXT_STORE_EVAL_RICHER_FIXTURES = [
|
|
214
|
+
sufficiency(budgetPressureMulti),
|
|
215
|
+
adversarial(sourceCoverageMiss, { recall: 1, source_coverage: 0.5, continuation_success: 1, recoverability: 1 }),
|
|
216
|
+
adversarial(continuationMarkerAbsent, { recall: 1, source_coverage: 1, continuation_success: 0.5, recoverability: 1 }),
|
|
217
|
+
sufficiency(lexicalTrapPointerSaves),
|
|
218
|
+
adversarial(lexicalTrapNoPointer, { recall: 0, source_coverage: 0, continuation_success: 0, recoverability: 1 }),
|
|
219
|
+
sufficiency(duplicateNoInflation),
|
|
220
|
+
adversarial(recoverabilityMissingPointers, { recall: 1, source_coverage: 1, continuation_success: 1, recoverability: 0.5 })
|
|
221
|
+
];
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
// Edge-class fixtures (V0.4 rung-1, 2026-06-24) — the missing classes the accepted
|
|
224
|
+
// threshold note (docs/design/v0.4-context-store-thresholds.md) asked for BEFORE any
|
|
225
|
+
// threshold is implemented:
|
|
226
|
+
// - multi-gold -> makes recall/coverage FRACTIONAL (today they are binary), so a
|
|
227
|
+
// future bar can be calibrated against real partials.
|
|
228
|
+
// - empty store -> makes zero-context behavior explicit and honest.
|
|
229
|
+
// - budget / limit -> recall + coverage degrade for the RIGHT, distinguishable reason
|
|
230
|
+
// (token budget vs retrieval `limit`).
|
|
231
|
+
// - adversarial anchor -> a low score is the CORRECT behavior (the info is not in the store).
|
|
232
|
+
// Fixtures/tests only. No thresholds, no claim, no command.
|
|
233
|
+
// ---------------------------------------------------------------------------
|
|
234
|
+
// Three equally-relevant task-critical items (each fully contains the query terms, so they
|
|
235
|
+
// tie on lexical overlap and order deterministically by recency). Used by the multi-gold and
|
|
236
|
+
// budget/limit fixtures so exactly which items survive is deterministic.
|
|
237
|
+
const mgQuery = "rollout checklist: migration, signing, oncall";
|
|
238
|
+
const mgA = "rollout checklist migration signing oncall — run the database migration first";
|
|
239
|
+
const mgB = "rollout checklist migration signing oncall — rotate the signing key next";
|
|
240
|
+
const mgC = "rollout checklist migration signing oncall — page the oncall engineer last";
|
|
241
|
+
/** F1 — multi-gold, PARTIAL recall: budget fits two of three task-critical items. */
|
|
242
|
+
const multiGoldPartialRecall = {
|
|
243
|
+
name: "multi-gold-partial-recall",
|
|
244
|
+
description: "Three equally-relevant gold items, budget fits two -> recall is a real fraction (not 0/1).",
|
|
245
|
+
store: [
|
|
246
|
+
mk(mgA, { pointer: "trace=MG message=a", created_at: T0 }),
|
|
247
|
+
mk(mgB, { pointer: "trace=MG message=b", created_at: T1 }),
|
|
248
|
+
mk(mgC, { pointer: "trace=MG message=c", created_at: T2 })
|
|
249
|
+
],
|
|
250
|
+
query: { text: mgQuery },
|
|
251
|
+
// Room for the two newest (mgC, mgB by recency tie-break); mgA is dropped.
|
|
252
|
+
tokenBudget: tokensOf(mgC) + tokensOf(mgB),
|
|
253
|
+
goldItems: [mgA, mgB, mgC],
|
|
254
|
+
// No required sources / markers here: isolate PARTIAL RECALL as the signal.
|
|
255
|
+
requiredSourcePointers: [],
|
|
256
|
+
continuationRequirements: []
|
|
257
|
+
};
|
|
258
|
+
/** F2 — empty store: zero-context behavior is explicit and honest (no crash, no inflation). */
|
|
259
|
+
const emptyStore = {
|
|
260
|
+
name: "empty-store",
|
|
261
|
+
description: "Nothing has been accumulated yet; retrieval/assembly must report honest zeros.",
|
|
262
|
+
store: [],
|
|
263
|
+
query: { text: "what did we decide about the migration", trace_ids: ["Z"] },
|
|
264
|
+
tokenBudget: 1000,
|
|
265
|
+
goldItems: ["a task-critical fact that is not in the store"],
|
|
266
|
+
requiredSourcePointers: ["trace=Z message=m1"],
|
|
267
|
+
continuationRequirements: ["migration decision"]
|
|
268
|
+
};
|
|
269
|
+
/** F3 — budget pressure: recall AND coverage degrade together, for the budget reason. */
|
|
270
|
+
const budgetDegradesRecallAndCoverage = {
|
|
271
|
+
name: "budget-degrades-recall-and-coverage",
|
|
272
|
+
description: "All three sources are required but the budget fits two -> recall and coverage both drop.",
|
|
273
|
+
store: [
|
|
274
|
+
mk(mgA, { pointer: "trace=MG message=a", created_at: T0 }),
|
|
275
|
+
mk(mgB, { pointer: "trace=MG message=b", created_at: T1 }),
|
|
276
|
+
mk(mgC, { pointer: "trace=MG message=c", created_at: T2 })
|
|
277
|
+
],
|
|
278
|
+
query: { text: mgQuery },
|
|
279
|
+
tokenBudget: tokensOf(mgC) + tokensOf(mgB),
|
|
280
|
+
goldItems: [mgA, mgB, mgC],
|
|
281
|
+
// All three required -> the dropped item's source is uncovered (coverage degrades WITH recall).
|
|
282
|
+
requiredSourcePointers: ["trace=MG message=a", "trace=MG message=b", "trace=MG message=c"],
|
|
283
|
+
continuationRequirements: []
|
|
284
|
+
};
|
|
285
|
+
/** F4 — retrieval `limit` omits one gold: recall degrades because of the LIMIT, not the budget. */
|
|
286
|
+
const limitOmitsGold = {
|
|
287
|
+
name: "limit-omits-gold",
|
|
288
|
+
description: "Budget is generous but retrieval limit=2 caps the candidate set -> one gold never reaches assembly.",
|
|
289
|
+
store: [
|
|
290
|
+
mk(mgA, { pointer: "trace=MG message=a", created_at: T0 }),
|
|
291
|
+
mk(mgB, { pointer: "trace=MG message=b", created_at: T1 }),
|
|
292
|
+
mk(mgC, { pointer: "trace=MG message=c", created_at: T2 })
|
|
293
|
+
],
|
|
294
|
+
query: { text: mgQuery },
|
|
295
|
+
tokenBudget: 1000, // generous: nothing is dropped BY BUDGET
|
|
296
|
+
retrievalOptions: { limit: 2 },
|
|
297
|
+
goldItems: [mgA, mgB, mgC],
|
|
298
|
+
requiredSourcePointers: [],
|
|
299
|
+
continuationRequirements: []
|
|
300
|
+
};
|
|
301
|
+
/** F5 — adversarial anchor: a low score is the CORRECT behavior because the info isn't stored. */
|
|
302
|
+
const adversarialCorrectLowScore = {
|
|
303
|
+
name: "adversarial-correct-low-score",
|
|
304
|
+
description: "The store holds only unrelated items; the task-critical fact is absent, so recall 0 is correct (not a retriever failure).",
|
|
305
|
+
store: [
|
|
306
|
+
mk("the office wifi password was rotated on tuesday", { pointer: "trace=N message=m1", created_at: T0 }),
|
|
307
|
+
mk("lunch is catered on fridays this quarter", { pointer: "trace=N message=m2", created_at: T1 })
|
|
308
|
+
],
|
|
309
|
+
query: { text: "what is the database migration rollback procedure" },
|
|
310
|
+
tokenBudget: 1000,
|
|
311
|
+
// The required fact was never captured into the store -> a faithful retriever cannot surface it.
|
|
312
|
+
goldItems: ["the database migration rollback procedure is to restore the retained original"],
|
|
313
|
+
requiredSourcePointers: ["trace=DB message=m1"],
|
|
314
|
+
continuationRequirements: ["rollback procedure"]
|
|
315
|
+
};
|
|
316
|
+
export const CONTEXT_STORE_EVAL_EDGE_FIXTURES = [
|
|
317
|
+
// All edge fixtures are adversarial anchors: their partial/zero scores are the CORRECT
|
|
318
|
+
// behavior (budget / limit / empty / absent-info), not a sufficiency failure.
|
|
319
|
+
adversarial(multiGoldPartialRecall, { recall: 2 / 3, source_coverage: 1, continuation_success: 1, recoverability: 1 }),
|
|
320
|
+
adversarial(emptyStore, { recall: 0, source_coverage: 0, continuation_success: 0, recoverability: 1 }),
|
|
321
|
+
adversarial(budgetDegradesRecallAndCoverage, { recall: 2 / 3, source_coverage: 2 / 3, continuation_success: 1, recoverability: 1 }),
|
|
322
|
+
adversarial(limitOmitsGold, { recall: 2 / 3, source_coverage: 1, continuation_success: 1, recoverability: 1 }),
|
|
323
|
+
adversarial(adversarialCorrectLowScore, { recall: 0, source_coverage: 0, continuation_success: 0, recoverability: 1 })
|
|
324
|
+
];
|
|
325
|
+
/** Every fixture (healthy + richer + edge), for the internal sufficiency report. */
|
|
326
|
+
export const CONTEXT_STORE_ALL_EVAL_FIXTURES = [
|
|
327
|
+
...CONTEXT_STORE_EVAL_FIXTURES,
|
|
328
|
+
...CONTEXT_STORE_EVAL_RICHER_FIXTURES,
|
|
329
|
+
...CONTEXT_STORE_EVAL_EDGE_FIXTURES
|
|
330
|
+
];
|
|
331
|
+
//# sourceMappingURL=context-store-eval-cases.js.map
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { type ContextStoreItem, type RetrievalOptions, type RetrievalQuery } from "./context-store.js";
|
|
2
|
+
/**
|
|
3
|
+
* V0.4 context-store eval harness — rung-1 Increment 2 (local, deterministic, fixture-based).
|
|
4
|
+
*
|
|
5
|
+
* PURPOSE: MEASURE whether deterministic retrieval + assembly surfaces the *sufficient*
|
|
6
|
+
* active context on fixtures, along four axes from the accepted design doc
|
|
7
|
+
* (`docs/design/v0.4-context-store-retrieval.md`):
|
|
8
|
+
* - recall — did the task-critical source-pointed items survive into the
|
|
9
|
+
* assembled context?
|
|
10
|
+
* - source coverage — are the required sources represented in the assembled context?
|
|
11
|
+
* - latency — observed retrieval + assembly wall time (a measurement, not a
|
|
12
|
+
* gate; it varies by machine).
|
|
13
|
+
* - continuation success — can a fresh agent continue from the assembled context?
|
|
14
|
+
* **FIXTURE-BASED** (required markers present), NOT live model
|
|
15
|
+
* replay (true model replay is a future, gated axis — mirrors the
|
|
16
|
+
* engine harness's `task_check` honesty note).
|
|
17
|
+
* Plus a local recoverability signal (fraction of assembled items carrying a recoverable
|
|
18
|
+
* source pointer — the substrate invariant).
|
|
19
|
+
*
|
|
20
|
+
* WHAT THIS IS NOT (load-bearing):
|
|
21
|
+
* - This harness MAKES NO CLAIM. It reports per-fixture + aggregate metrics; it does NOT
|
|
22
|
+
* decide "sufficient" and bakes in NO pass/fail thresholds. The thresholds that would gate
|
|
23
|
+
* a rung-6 large-memory claim are an explicit OPEN founder decision (design doc open
|
|
24
|
+
* question 4) — deliberately not encoded here.
|
|
25
|
+
* - NO model, NO embeddings, NO network. Continuation success is a deterministic substring
|
|
26
|
+
* check against the assembled text, NOT model re-execution.
|
|
27
|
+
* - NO 300M / large-memory / retrieval-correctness claim is produced by running this.
|
|
28
|
+
*/
|
|
29
|
+
export declare const CONTEXT_STORE_EVAL_VERSION = "context-store-eval-v1";
|
|
30
|
+
/** A monotonic clock returning milliseconds. Injected so latency is testable/deterministic. */
|
|
31
|
+
export type Clock = () => number;
|
|
32
|
+
/** The four quality axes a fixture is scored on (latency is a separate measurement). */
|
|
33
|
+
export interface AxisScores {
|
|
34
|
+
recall: number;
|
|
35
|
+
source_coverage: number;
|
|
36
|
+
continuation_success: number;
|
|
37
|
+
recoverability: number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Threshold role of a fixture:
|
|
41
|
+
* - `sufficiency` — the task-critical context SHOULD be surfaced; the fixture is GATED against
|
|
42
|
+
* the sufficiency thresholds (see context-store-sufficiency.ts).
|
|
43
|
+
* - `adversarial` — a low/partial score is the CORRECT behavior; the fixture is NOT gated, it is
|
|
44
|
+
* a regression ANCHOR (its measured scores must keep matching `expected`).
|
|
45
|
+
* Unlabeled fixtures default to `sufficiency`.
|
|
46
|
+
*/
|
|
47
|
+
export type ContextStoreFixtureRole = "sufficiency" | "adversarial";
|
|
48
|
+
export interface ContextStoreEvalFixture {
|
|
49
|
+
/** Stable fixture name. */
|
|
50
|
+
name: string;
|
|
51
|
+
/** Optional human description of what the fixture exercises. */
|
|
52
|
+
description?: string;
|
|
53
|
+
/** Threshold role (default `sufficiency`). Metadata only — `evaluate` ignores it. */
|
|
54
|
+
role?: ContextStoreFixtureRole;
|
|
55
|
+
/** For `adversarial` fixtures: the expected (often low/partial) per-axis anchor outcome. */
|
|
56
|
+
expected?: Partial<AxisScores>;
|
|
57
|
+
/** The store under evaluation (the accumulated items). */
|
|
58
|
+
store: ContextStoreItem[];
|
|
59
|
+
/** The retrieval query (current step's context + optional source hints). */
|
|
60
|
+
query: RetrievalQuery;
|
|
61
|
+
/** Token budget for assembly (local estimate). */
|
|
62
|
+
tokenBudget: number;
|
|
63
|
+
/** Optional retrieval options (weights, limit). */
|
|
64
|
+
retrievalOptions?: RetrievalOptions;
|
|
65
|
+
/**
|
|
66
|
+
* Task-critical item CONTENT that MUST survive into the assembled context. Compared by
|
|
67
|
+
* normalized content hash (so authors write the text, not the hash).
|
|
68
|
+
*/
|
|
69
|
+
goldItems: string[];
|
|
70
|
+
/** Source pointers that MUST be represented among the assembled items (optional). */
|
|
71
|
+
requiredSourcePointers?: string[];
|
|
72
|
+
/**
|
|
73
|
+
* FIXTURE-BASED continuation requirements: markers/substrings that MUST appear in the
|
|
74
|
+
* assembled context text for a fresh agent to continue. NOT live model replay.
|
|
75
|
+
*/
|
|
76
|
+
continuationRequirements: string[];
|
|
77
|
+
}
|
|
78
|
+
export interface ContextStoreEvalResult {
|
|
79
|
+
version: string;
|
|
80
|
+
name: string;
|
|
81
|
+
/** [0,1] — fraction of gold (task-critical) items present in the assembled context. */
|
|
82
|
+
recall: number;
|
|
83
|
+
/** [0,1] — fraction of required sources represented (1 when none are required). */
|
|
84
|
+
source_coverage: number;
|
|
85
|
+
/** [0,1] — fraction of FIXTURE-BASED continuation markers present in the assembled text. */
|
|
86
|
+
continuation_success: number;
|
|
87
|
+
/**
|
|
88
|
+
* [0,1] — fraction of assembled items that are recoverable, i.e. carry BOTH a recoverable
|
|
89
|
+
* flag AND a source pointer to recover to (1 if nothing was assembled — vacuously true).
|
|
90
|
+
*/
|
|
91
|
+
recoverability: number;
|
|
92
|
+
/** Observed retrieval + assembly time in ms (measurement, machine-dependent). */
|
|
93
|
+
latency_ms: number;
|
|
94
|
+
/** Local token estimate of the assembled context. */
|
|
95
|
+
assembled_token_estimate: number;
|
|
96
|
+
/** Whether all retrieved items fit under the budget. */
|
|
97
|
+
within_budget: boolean;
|
|
98
|
+
/** Raw counts behind the ratios (for transparency). */
|
|
99
|
+
detail: {
|
|
100
|
+
gold_total: number;
|
|
101
|
+
gold_present: number;
|
|
102
|
+
required_sources_total: number;
|
|
103
|
+
required_sources_covered: number;
|
|
104
|
+
continuation_total: number;
|
|
105
|
+
continuation_met: number;
|
|
106
|
+
assembled_item_count: number;
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Evaluate a single fixture. Deterministic apart from `latency_ms` (wall time). Pure with
|
|
111
|
+
* respect to the inputs — no model, no network.
|
|
112
|
+
*/
|
|
113
|
+
export declare function evaluateContextStoreFixture(fixture: ContextStoreEvalFixture, options?: {
|
|
114
|
+
clock?: Clock;
|
|
115
|
+
}): ContextStoreEvalResult;
|
|
116
|
+
export interface ContextStoreEvalAggregate {
|
|
117
|
+
version: string;
|
|
118
|
+
fixture_count: number;
|
|
119
|
+
/** Mean across fixtures (each in [0,1]). */
|
|
120
|
+
mean_recall: number;
|
|
121
|
+
mean_source_coverage: number;
|
|
122
|
+
mean_continuation_success: number;
|
|
123
|
+
mean_recoverability: number;
|
|
124
|
+
/** Mean observed latency (ms) — measurement, not a gate. */
|
|
125
|
+
mean_latency_ms: number;
|
|
126
|
+
results: ContextStoreEvalResult[];
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Evaluate a suite of fixtures and roll up the per-axis means. REPORTS metrics only — no
|
|
130
|
+
* pass/fail verdict, no thresholds (those are a gated founder decision, by design).
|
|
131
|
+
*/
|
|
132
|
+
export declare function evaluateContextStoreFixtures(fixtures: ContextStoreEvalFixture[], options?: {
|
|
133
|
+
clock?: Clock;
|
|
134
|
+
}): ContextStoreEvalAggregate;
|
|
135
|
+
/**
|
|
136
|
+
* Markdown report for a fixture suite. States the four axes, the honest non-claim, and the
|
|
137
|
+
* fixture-based-continuation caveat explicitly — so a reader cannot mistake these metrics for
|
|
138
|
+
* a large-memory / retrieval-correctness claim.
|
|
139
|
+
*/
|
|
140
|
+
export declare function formatContextStoreEvalMarkdown(aggregate: ContextStoreEvalAggregate): string;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { contentHash } from "./content-hash.js";
|
|
2
|
+
import { assembleContext, retrieveContextItems } from "./context-store.js";
|
|
3
|
+
/**
|
|
4
|
+
* V0.4 context-store eval harness — rung-1 Increment 2 (local, deterministic, fixture-based).
|
|
5
|
+
*
|
|
6
|
+
* PURPOSE: MEASURE whether deterministic retrieval + assembly surfaces the *sufficient*
|
|
7
|
+
* active context on fixtures, along four axes from the accepted design doc
|
|
8
|
+
* (`docs/design/v0.4-context-store-retrieval.md`):
|
|
9
|
+
* - recall — did the task-critical source-pointed items survive into the
|
|
10
|
+
* assembled context?
|
|
11
|
+
* - source coverage — are the required sources represented in the assembled context?
|
|
12
|
+
* - latency — observed retrieval + assembly wall time (a measurement, not a
|
|
13
|
+
* gate; it varies by machine).
|
|
14
|
+
* - continuation success — can a fresh agent continue from the assembled context?
|
|
15
|
+
* **FIXTURE-BASED** (required markers present), NOT live model
|
|
16
|
+
* replay (true model replay is a future, gated axis — mirrors the
|
|
17
|
+
* engine harness's `task_check` honesty note).
|
|
18
|
+
* Plus a local recoverability signal (fraction of assembled items carrying a recoverable
|
|
19
|
+
* source pointer — the substrate invariant).
|
|
20
|
+
*
|
|
21
|
+
* WHAT THIS IS NOT (load-bearing):
|
|
22
|
+
* - This harness MAKES NO CLAIM. It reports per-fixture + aggregate metrics; it does NOT
|
|
23
|
+
* decide "sufficient" and bakes in NO pass/fail thresholds. The thresholds that would gate
|
|
24
|
+
* a rung-6 large-memory claim are an explicit OPEN founder decision (design doc open
|
|
25
|
+
* question 4) — deliberately not encoded here.
|
|
26
|
+
* - NO model, NO embeddings, NO network. Continuation success is a deterministic substring
|
|
27
|
+
* check against the assembled text, NOT model re-execution.
|
|
28
|
+
* - NO 300M / large-memory / retrieval-correctness claim is produced by running this.
|
|
29
|
+
*/
|
|
30
|
+
export const CONTEXT_STORE_EVAL_VERSION = "context-store-eval-v1";
|
|
31
|
+
function defaultClock() {
|
|
32
|
+
return globalThis.performance.now();
|
|
33
|
+
}
|
|
34
|
+
function normalizeContent(content) {
|
|
35
|
+
return content.trim().replace(/\s+/g, " ");
|
|
36
|
+
}
|
|
37
|
+
function ratio(numerator, denominator, whenEmpty) {
|
|
38
|
+
return denominator === 0 ? whenEmpty : numerator / denominator;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Evaluate a single fixture. Deterministic apart from `latency_ms` (wall time). Pure with
|
|
42
|
+
* respect to the inputs — no model, no network.
|
|
43
|
+
*/
|
|
44
|
+
export function evaluateContextStoreFixture(fixture, options = {}) {
|
|
45
|
+
const clock = options.clock ?? defaultClock;
|
|
46
|
+
const start = clock();
|
|
47
|
+
const ranked = retrieveContextItems(fixture.store, fixture.query, fixture.retrievalOptions);
|
|
48
|
+
const assembled = assembleContext(ranked, { tokenBudget: fixture.tokenBudget });
|
|
49
|
+
const latency_ms = clock() - start;
|
|
50
|
+
const includedHashes = new Set(assembled.included.map((item) => item.content_sha256));
|
|
51
|
+
const includedPointers = new Set(assembled.included.map((item) => item.source_pointer).filter((p) => p !== undefined));
|
|
52
|
+
const goldHashes = fixture.goldItems.map((content) => contentHash(normalizeContent(content)));
|
|
53
|
+
const goldPresent = goldHashes.filter((hash) => includedHashes.has(hash)).length;
|
|
54
|
+
const requiredSources = fixture.requiredSourcePointers ?? [];
|
|
55
|
+
const sourcesCovered = requiredSources.filter((pointer) => includedPointers.has(pointer)).length;
|
|
56
|
+
const assembledLower = assembled.text.toLowerCase();
|
|
57
|
+
const continuationMet = fixture.continuationRequirements.filter((marker) => assembledLower.includes(marker.toLowerCase())).length;
|
|
58
|
+
// Recoverable requires BOTH a recoverable flag AND a source pointer to recover *to* — an
|
|
59
|
+
// item flagged recoverable but carrying no source pointer cannot actually be recovered.
|
|
60
|
+
const recoverableCount = assembled.included.filter((item) => item.recoverability === true && item.source_pointer !== undefined).length;
|
|
61
|
+
return {
|
|
62
|
+
version: CONTEXT_STORE_EVAL_VERSION,
|
|
63
|
+
name: fixture.name,
|
|
64
|
+
recall: ratio(goldPresent, goldHashes.length, 1),
|
|
65
|
+
source_coverage: ratio(sourcesCovered, requiredSources.length, 1),
|
|
66
|
+
continuation_success: ratio(continuationMet, fixture.continuationRequirements.length, 1),
|
|
67
|
+
recoverability: ratio(recoverableCount, assembled.included.length, 1),
|
|
68
|
+
latency_ms,
|
|
69
|
+
assembled_token_estimate: assembled.estimated_tokens,
|
|
70
|
+
within_budget: assembled.within_budget,
|
|
71
|
+
detail: {
|
|
72
|
+
gold_total: goldHashes.length,
|
|
73
|
+
gold_present: goldPresent,
|
|
74
|
+
required_sources_total: requiredSources.length,
|
|
75
|
+
required_sources_covered: sourcesCovered,
|
|
76
|
+
continuation_total: fixture.continuationRequirements.length,
|
|
77
|
+
continuation_met: continuationMet,
|
|
78
|
+
assembled_item_count: assembled.included.length
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function mean(values) {
|
|
83
|
+
return values.length === 0 ? 0 : values.reduce((sum, v) => sum + v, 0) / values.length;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Evaluate a suite of fixtures and roll up the per-axis means. REPORTS metrics only — no
|
|
87
|
+
* pass/fail verdict, no thresholds (those are a gated founder decision, by design).
|
|
88
|
+
*/
|
|
89
|
+
export function evaluateContextStoreFixtures(fixtures, options = {}) {
|
|
90
|
+
const results = fixtures.map((fixture) => evaluateContextStoreFixture(fixture, options));
|
|
91
|
+
return {
|
|
92
|
+
version: CONTEXT_STORE_EVAL_VERSION,
|
|
93
|
+
fixture_count: results.length,
|
|
94
|
+
mean_recall: mean(results.map((r) => r.recall)),
|
|
95
|
+
mean_source_coverage: mean(results.map((r) => r.source_coverage)),
|
|
96
|
+
mean_continuation_success: mean(results.map((r) => r.continuation_success)),
|
|
97
|
+
mean_recoverability: mean(results.map((r) => r.recoverability)),
|
|
98
|
+
mean_latency_ms: mean(results.map((r) => r.latency_ms)),
|
|
99
|
+
results
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function pct(value) {
|
|
103
|
+
return `${(value * 100).toFixed(1)}%`;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Markdown report for a fixture suite. States the four axes, the honest non-claim, and the
|
|
107
|
+
* fixture-based-continuation caveat explicitly — so a reader cannot mistake these metrics for
|
|
108
|
+
* a large-memory / retrieval-correctness claim.
|
|
109
|
+
*/
|
|
110
|
+
export function formatContextStoreEvalMarkdown(aggregate) {
|
|
111
|
+
const lines = [
|
|
112
|
+
"## Context-store eval (rung-1, local-deterministic, fixture-based)",
|
|
113
|
+
"",
|
|
114
|
+
`Fixtures: ${aggregate.fixture_count}`,
|
|
115
|
+
"",
|
|
116
|
+
"| Axis | Mean |",
|
|
117
|
+
"|---|---|",
|
|
118
|
+
`| Recall | ${pct(aggregate.mean_recall)} |`,
|
|
119
|
+
`| Source coverage | ${pct(aggregate.mean_source_coverage)} |`,
|
|
120
|
+
`| Continuation success (fixture-based) | ${pct(aggregate.mean_continuation_success)} |`,
|
|
121
|
+
`| Recoverability | ${pct(aggregate.mean_recoverability)} |`,
|
|
122
|
+
`| Latency (mean, ms) | ${aggregate.mean_latency_ms.toFixed(2)} |`,
|
|
123
|
+
"",
|
|
124
|
+
"### Per fixture",
|
|
125
|
+
"| Fixture | Recall | Source cov. | Continuation | Recoverability | Latency (ms) | Within budget |",
|
|
126
|
+
"|---|---|---|---|---|---|---|",
|
|
127
|
+
...aggregate.results.map((r) => `| ${r.name} | ${pct(r.recall)} | ${pct(r.source_coverage)} | ${pct(r.continuation_success)} | ` +
|
|
128
|
+
`${pct(r.recoverability)} | ${r.latency_ms.toFixed(2)} | ${r.within_budget ? "yes" : "no"} |`),
|
|
129
|
+
"",
|
|
130
|
+
"**What these numbers are NOT:** this harness reports fixture metrics only. It bakes in NO",
|
|
131
|
+
"pass/fail thresholds and makes NO large-memory / 300M / retrieval-correctness claim — the",
|
|
132
|
+
"thresholds that would gate such a claim are an open, gated decision. Continuation success is",
|
|
133
|
+
"FIXTURE-BASED (required markers present in the assembled text), NOT live model re-execution.",
|
|
134
|
+
"Latency is an observed measurement and varies by machine."
|
|
135
|
+
];
|
|
136
|
+
return lines.join("\n");
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=context-store-eval.js.map
|