opencode-wait-a-minute 0.0.0-stage → 1.1.0-rc.8

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 (207) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +318 -2
  3. package/index.js +1836 -0
  4. package/package.json +70 -4
  5. package/skills/codebase-design/SKILL.md +114 -0
  6. package/skills/design-contract/SKILL.md +27 -0
  7. package/skills/design-critique/SKILL.md +19 -0
  8. package/skills/design-system/SKILL.md +17 -0
  9. package/skills/design-system/references/image-analysis.md +14 -0
  10. package/skills/design-system/references/tokens.md +32 -0
  11. package/skills/design-system/references/ui-rules.md +14 -0
  12. package/skills/diagnosing-bugs/SKILL.md +138 -0
  13. package/skills/frontend-design/SKILL.md +36 -0
  14. package/skills/handoff/SKILL.md +16 -0
  15. package/skills/improve-codebase-architecture/SKILL.md +71 -0
  16. package/skills/linter-negative/SKILL.md +74 -0
  17. package/skills/no-slop-ui/SKILL.md +26 -0
  18. package/skills/prototype/SKILL.md +26 -0
  19. package/skills/registry.json +44087 -0
  20. package/skills/tastemaker/SKILL.md +8 -0
  21. package/skills/tastemaker/references/tastemaker.md +14 -0
  22. package/skills/tdd/SKILL.md +38 -0
  23. package/skills/visual-review/SKILL.md +103 -0
  24. package/skills/visual-review/references/review-gates.md +42 -0
  25. package/skills/visual-review/references/surface-archetypes.md +37 -0
  26. package/skills/wizard/SKILL.md +44 -0
  27. package/skills/writing-for-agents/SKILL.md +81 -0
  28. package/src/cognition/cognition-store.js +251 -0
  29. package/src/cognition/cognitive-state.js +126 -0
  30. package/src/cognition/hypothesis-manager.js +113 -0
  31. package/src/cognition/observation-engine.js +112 -0
  32. package/src/cognition/observation-provenance.js +268 -0
  33. package/src/context/active-context-boundary.js +68 -0
  34. package/src/context/assembly.js +494 -0
  35. package/src/context/assessment-rules.js +55 -0
  36. package/src/context/capsule-staleness.js +267 -0
  37. package/src/context/capsules.js +2 -0
  38. package/src/context/compare-array.js +98 -0
  39. package/src/context/compare-object.js +89 -0
  40. package/src/context/compare-values.js +42 -0
  41. package/src/context/context-assembly-contract.js +71 -0
  42. package/src/context/context-budget-manager.js +232 -0
  43. package/src/context/context-builder.js +49 -0
  44. package/src/context/context-cache.js +55 -0
  45. package/src/context/context-capture.js +243 -0
  46. package/src/context/context-classification.js +199 -0
  47. package/src/context/context-compaction.js +179 -0
  48. package/src/context/context-decision-audit.js +267 -0
  49. package/src/context/context-demotion.js +173 -0
  50. package/src/context/context-dictionary.js +106 -0
  51. package/src/context/context-drift-detection.js +225 -0
  52. package/src/context/context-evaluation.js +374 -0
  53. package/src/context/context-event-ingress.js +100 -0
  54. package/src/context/context-event-normalization.js +224 -0
  55. package/src/context/context-feedback-loop.js +216 -0
  56. package/src/context/context-freshness.js +21 -0
  57. package/src/context/context-graph-builder.js +71 -0
  58. package/src/context/context-graph.js +451 -0
  59. package/src/context/context-history-relevance.js +321 -0
  60. package/src/context/context-interception.js +145 -0
  61. package/src/context/context-invalidation-events.js +154 -0
  62. package/src/context/context-lifecycle.js +170 -0
  63. package/src/context/context-loop-prevention.js +347 -0
  64. package/src/context/context-manager.js +340 -0
  65. package/src/context/context-memory-layers.js +265 -0
  66. package/src/context/context-minimality.js +166 -0
  67. package/src/context/context-normalization.js +130 -0
  68. package/src/context/context-optimization-metrics.js +434 -0
  69. package/src/context/context-output-policy.js +135 -0
  70. package/src/context/context-pack-authority.js +157 -0
  71. package/src/context/context-promotion.js +172 -0
  72. package/src/context/context-query-contract.js +101 -0
  73. package/src/context/context-ranking.js +164 -0
  74. package/src/context/context-rate-distortion.js +261 -0
  75. package/src/context/context-recovery.js +209 -0
  76. package/src/context/context-retrieval-integration.js +60 -0
  77. package/src/context/context-retrieval.js +93 -0
  78. package/src/context/context-router.js +457 -0
  79. package/src/context/context-routing-evaluation.js +331 -0
  80. package/src/context/context-routing-observability.js +296 -0
  81. package/src/context/context-routing-shadow.js +237 -0
  82. package/src/context/context-scope.js +181 -0
  83. package/src/context/context-snapshot.js +226 -0
  84. package/src/context/context-source-registry.js +95 -0
  85. package/src/context/context-sufficiency-gate.js +120 -0
  86. package/src/context/context-sufficiency-oracle.js +167 -0
  87. package/src/context/context-telemetry.js +176 -0
  88. package/src/context/context.js +633 -0
  89. package/src/context/dependency-closure.js +246 -0
  90. package/src/context/page-fault-tracker.js +275 -0
  91. package/src/context/prompt-engine.js +74 -0
  92. package/src/context/runtime-context-graph.js +260 -0
  93. package/src/context/selection.js +2 -0
  94. package/src/context/session.js +2 -0
  95. package/src/context/sufficiency-contract.js +300 -0
  96. package/src/context/tokenizer.js +17 -0
  97. package/src/context-benchmark-router.mjs +125 -0
  98. package/src/evidence/evidence-engine.js +161 -0
  99. package/src/evidence/evidence-factory.js +3 -0
  100. package/src/evidence/evidence-freshness.js +463 -0
  101. package/src/evidence/evidence-gap.js +76 -0
  102. package/src/evidence/evidence-lineage.js +525 -0
  103. package/src/evidence/evidence.js +136 -0
  104. package/src/evidence/freshness-validation.js +187 -0
  105. package/src/evidence/lineage.js +4 -0
  106. package/src/execution/action-evaluation.js +179 -0
  107. package/src/execution/circuit-breaker.js +70 -0
  108. package/src/execution/claim-interception.js +63 -0
  109. package/src/execution/execution-engine.js +258 -0
  110. package/src/execution/execution-intent.js +69 -0
  111. package/src/execution/execution-state.js +89 -0
  112. package/src/execution/formatting.js +60 -0
  113. package/src/execution/l1-sanitize.js +16 -0
  114. package/src/execution/l2-resource-guards.js +19 -0
  115. package/src/execution/l3-invariant-checker.js +5 -0
  116. package/src/execution/l4-containment.js +6 -0
  117. package/src/execution/runtime-guards.js +106 -0
  118. package/src/execution/strategy-identity.js +162 -0
  119. package/src/execution/tool-interceptor.js +7 -0
  120. package/src/execution/tool-result-classifier.js +63 -0
  121. package/src/integration/canary-deploy.js +143 -0
  122. package/src/integration/collector.js +59 -0
  123. package/src/integration/error-recovery.js +6 -0
  124. package/src/integration/event-bus.js +66 -0
  125. package/src/integration/health-checker.js +57 -0
  126. package/src/integration/logger.js +39 -0
  127. package/src/integration/message-handler.js +611 -0
  128. package/src/integration/opencode-db.js +153 -0
  129. package/src/integration/orchestration.js +419 -0
  130. package/src/integration/part-provenance.js +65 -0
  131. package/src/integration/rate-limiter.js +55 -0
  132. package/src/integration/release-gate.js +336 -0
  133. package/src/integration/rollback-manager.js +164 -0
  134. package/src/integration/router-adapter.js +269 -0
  135. package/src/integration/telemetry.js +45 -0
  136. package/src/integration/update-checker.js +10 -0
  137. package/src/persistence/event-sourcing.js +75 -0
  138. package/src/persistence/memory.js +501 -0
  139. package/src/persistence/migration-runner.js +16 -0
  140. package/src/persistence/persistence-manager.js +39 -0
  141. package/src/persistence/transaction-log.js +157 -0
  142. package/src/policy/action-capabilities.js +116 -0
  143. package/src/policy/action-policy.js +4 -0
  144. package/src/policy/assessment-engine.js +104 -0
  145. package/src/policy/assumption-tracking.js +66 -0
  146. package/src/policy/budget-enforcer.js +7 -0
  147. package/src/policy/config-validator.js +102 -0
  148. package/src/policy/execution-assessment.js +142 -0
  149. package/src/policy/failure-classification.js +5 -0
  150. package/src/policy/feature-flags.js +65 -0
  151. package/src/policy/governance-enforcement.js +118 -0
  152. package/src/policy/locale.js +15 -0
  153. package/src/policy/multi-signer.js +151 -0
  154. package/src/policy/policy-state-machine.js +97 -0
  155. package/src/policy/ponytail-constraint.js +38 -0
  156. package/src/policy/project-inspector.js +93 -0
  157. package/src/policy/query-builder.js +14 -0
  158. package/src/policy/request-classifier.js +79 -0
  159. package/src/policy/risk-engine.js +266 -0
  160. package/src/policy/scope-enforcement.js +140 -0
  161. package/src/policy/secret-scanner.js +68 -0
  162. package/src/policy/skill-routing.js +104 -0
  163. package/src/policy/strategy-capabilities.js +211 -0
  164. package/src/policy/strategy.js +6 -0
  165. package/src/policy/uncertainty.js +117 -0
  166. package/src/shared/container.js +44 -0
  167. package/src/shared/decision-types.js +24 -0
  168. package/src/shared/simple-test.js +160 -0
  169. package/src/shared/wam-events.js +52 -0
  170. package/src/skills/engine.js +1705 -0
  171. package/src/skills/skill-registry.js +19 -0
  172. package/src/skills/skill-routing.js +340 -0
  173. package/src/state/lifecycle-manager.js +113 -0
  174. package/src/state/operational-state.js +198 -0
  175. package/src/state/projections.js +47 -0
  176. package/src/state/replay-engine.js +5 -0
  177. package/src/state/requirement-ownership.js +20 -0
  178. package/src/state/requirement-state.js +152 -0
  179. package/src/state/run-event-identity.js +172 -0
  180. package/src/state/state-engine.js +19 -0
  181. package/src/state/state-machine.js +119 -0
  182. package/src/state/state-schema.js +318 -0
  183. package/src/state/state-store.js +335 -0
  184. package/src/state/state-transition.js +84 -0
  185. package/src/state/task-dependencies.js +287 -0
  186. package/src/state/task-execution.js +217 -0
  187. package/src/state/task-lifecycle.js +59 -0
  188. package/src/state/task-runs.js +361 -0
  189. package/src/state/task-state-file.js +86 -0
  190. package/src/state/task-state.js +41 -0
  191. package/src/state/task-status.js +54 -0
  192. package/src/state/task-store.js +48 -0
  193. package/src/state/wam-state.js +148 -0
  194. package/src/verification/completion-gate-simple.js +8 -0
  195. package/src/verification/completion-gate.js +183 -0
  196. package/src/verification/contract.js +9 -0
  197. package/src/verification/contradiction-resolution.js +255 -0
  198. package/src/verification/diagnostic-engine.js +659 -0
  199. package/src/verification/false-completion-prevention.js +100 -0
  200. package/src/verification/hook-validator.js +6 -0
  201. package/src/verification/requirements.js +6 -0
  202. package/src/verification/verification-context.js +162 -0
  203. package/src/verification/verification-lifecycle.js +66 -0
  204. package/src/verification/verification-model.js +89 -0
  205. package/src/verification/verification-persistence.js +129 -0
  206. package/src/verification/verification-policy.js +127 -0
  207. package/src/verification/verification.js +415 -0
@@ -0,0 +1,434 @@
1
+ /**
2
+ * WAM Context Optimization Metrics — C06
3
+ *
4
+ * Records and computes the metrics defined for context optimization:
5
+ * - CRR (Context Reduction Rate) : how aggressively we shrink context
6
+ * - SPR (Sufficiency Preservation Rate) : how much of the required context survived
7
+ * - COR (Critical Omission Rate) : whether a critical node was dropped
8
+ * - CWR (Context Waste Rate) : how much kept context was actually used
9
+ * - PFR (Page Fault Rate) : how often we had to re-fetch after selection
10
+ * - RPC (Reacquisition Cost) : total tokens spent reacquiring missed context
11
+ * - TTC (Total Context Cost) : initialContextTokens + reacquiredTokens
12
+ * + retrievalOverhead (informational)
13
+ *
14
+ * Status as of TASK-09:
15
+ * - RPC and TTC are recorded on every metric and aggregated in
16
+ * summarize(), but they are INFORMATIONAL only — they are NOT
17
+ * part of the hard optimization gates. No TTC_MAX / RPC_MAX is
18
+ * defined yet because there is no empirical baseline.
19
+ *
20
+ * The module is intentionally framework-free: the recording primitives
21
+ * (`record`, `metrics`) can be invoked by any selector (router, builder,
22
+ * retrieval, promotion) so we get end-to-end observability without coupling.
23
+ */
24
+
25
+ // -- Types --
26
+ // (informative JSDoc only; runtime is plain JS)
27
+ //
28
+ // @typedef {Object} ContextMetricInput
29
+ // @property {string} strategy selector name (e.g. "full", "semantic-top-k", "wam-router")
30
+ // @property {number} requiredTokens tokens for the *required* ids (SPR/CWR denominator)
31
+ // @property {number} selectedTokens tokens actually selected
32
+ // @property {number} [fullTokens] tokens for the entire candidate graph (CRR denominator).
33
+ // Defaults to `requiredTokens` when not supplied.
34
+ // @property {string[]} requiredIds ids the task actually needed
35
+ // @property {string[]} selectedIds ids actually selected
36
+ // @property {string[]} criticalIds subset of requiredIds that MUST be present (COR)
37
+ // @property {string[]} usedIds ids the consumer actually read/used downstream
38
+ // @property {number} pageFaults number of additional fetches triggered by misses
39
+ // @property {number} reacquiredTokens tokens spent on those additional fetches
40
+ // @property {boolean} [taskSuccess] did the downstream task succeed? 1 for TSR numerator
41
+ // @property {boolean} [verified] did an independent oracle verify sufficiency? 1 for VSR numerator
42
+ // @property {number} [retrievalOverhead] tokens spent on retrieval/lookup overhead (TTC component)
43
+ // @property {string[]} [oracleMissing] ids the oracle declared missing (independent ground truth)
44
+
45
+ /**
46
+ * @typedef {Object} ContextMetrics
47
+ * @property {string} strategy
48
+ * @property {number} CRR Context Reduction Rate (0..1, higher = more reduction)
49
+ * @property {number} SPR Sufficiency Preservation Rate (0..1, higher = better)
50
+ * @property {number} COR Critical Omission Rate (0..1, lower is better)
51
+ * @property {number} CWR Context Waste Rate (0..1, lower = better)
52
+ * @property {number} PFR Page Fault Rate (0..1, lower = better)
53
+ * @property {number} RPC Reacquisition Cost (tokens) (>=0, lower is better; informational — not a gate)
54
+ * @property {number} selectedTokens
55
+ * @property {number} requiredTokens
56
+ * @property {number} usedTokens
57
+ * @property {number} pageFaults
58
+ * @property {number} reacquiredTokens
59
+ * @property {number} initialContextTokens (TTC component; defaults to selectedTokens)
60
+ * @property {number} retrievalOverhead (TTC component; defaults to 0)
61
+ * @property {number} TTC (Total Context Cost; informational — not a gate)
62
+ */
63
+
64
+ // -- Token estimation --
65
+
66
+ /**
67
+ * Cheap, deterministic token estimator (≈ length / 4).
68
+ * @param {string|undefined|null} text
69
+ * @returns {number}
70
+ */
71
+ export function estimateTokens(text) {
72
+ return Math.ceil((text || "").length / 4);
73
+ }
74
+
75
+ /**
76
+ * Sum tokens across a set of nodes (any object with a `content` field).
77
+ * @param {Iterable<{content: string}>} nodes
78
+ * @returns {number}
79
+ */
80
+ export function nodesTokens(nodes) {
81
+ let total = 0;
82
+ for (const n of nodes) total += estimateTokens(n?.content) + 20;
83
+ return total;
84
+ }
85
+
86
+ // -- Recording primitives --
87
+
88
+ /**
89
+ * Compute all six metrics from a single selection event.
90
+ * Pure function: no side effects, no I/O.
91
+ *
92
+ * @param {ContextMetricInput} input
93
+ * @returns {ContextMetrics}
94
+ */
95
+ export function record(input) {
96
+ const strategy = String(input?.strategy ?? "unknown");
97
+ const requiredTokens = Math.max(0, Number(input?.requiredTokens ?? 0));
98
+ const selectedTokens = Math.max(0, Number(input?.selectedTokens ?? 0));
99
+ const fullTokens = Math.max(
100
+ 0,
101
+ Number(input?.fullTokens ?? Math.max(requiredTokens, selectedTokens))
102
+ );
103
+ const requiredIds = Array.isArray(input?.requiredIds) ? input.requiredIds : [];
104
+ const selectedIds = Array.isArray(input?.selectedIds) ? input.selectedIds : [];
105
+ const criticalIds = Array.isArray(input?.criticalIds) ? input.criticalIds : [];
106
+ const usedIds = Array.isArray(input?.usedIds) ? input.usedIds : [];
107
+ const pageFaults = Math.max(0, Number(input?.pageFaults ?? 0));
108
+ const reacquiredTokens = Math.max(0, Number(input?.reacquiredTokens ?? 0));
109
+
110
+ const requiredSet = new Set(requiredIds);
111
+ const selectedSet = new Set(selectedIds);
112
+ const criticalSet = new Set(criticalIds);
113
+
114
+ // CRR: how much we shrunk relative to the FULL candidate graph
115
+ // (i.e. shipping everything). This is the metric that justifies
116
+ // the selector's existence; measured against required it floors
117
+ // to 0 the moment we add a single distractor.
118
+ // If full is empty, we report 0 (no reduction happened).
119
+ const CRR =
120
+ fullTokens > 0
121
+ ? clamp01(1 - selectedTokens / fullTokens)
122
+ : 0;
123
+
124
+ // SPR: fraction of required ids we still have available.
125
+ // 1.0 when there is nothing required (vacuous truth).
126
+ let preserved = 0;
127
+ for (const id of requiredSet) if (selectedSet.has(id)) preserved++;
128
+ const SPR = requiredSet.size === 0 ? 1 : clamp01(preserved / requiredSet.size);
129
+
130
+ // COR: fraction of critical ids that were dropped.
131
+ let missingCritical = 0;
132
+ for (const id of criticalSet) if (!selectedSet.has(id)) missingCritical++;
133
+ const COR = criticalSet.size === 0 ? 0 : clamp01(missingCritical / criticalSet.size);
134
+
135
+ // CWR: fraction of selected tokens that ended up unused.
136
+ // When no explicit usedIds or usedTokens are provided, CWR must be null (no evidence).
137
+ let usedTokens = 0;
138
+ let CWR = null;
139
+ if (typeof input?.usedTokens === "number") {
140
+ usedTokens = Math.max(0, Math.min(selectedTokens, Number(input.usedTokens)));
141
+ CWR = selectedTokens > 0 ? clamp01(1 - usedTokens / selectedTokens) : 0;
142
+ } else if (Array.isArray(input?.usedIds)) {
143
+ const usedSet = new Set(input.usedIds);
144
+ let usedInSelection = 0;
145
+ for (const id of usedSet) if (selectedSet.has(id)) usedInSelection++;
146
+ usedTokens = selectedIds.length > 0 ? (usedInSelection / selectedIds.length) * selectedTokens : 0;
147
+ CWR = selectedTokens > 0 ? clamp01(1 - usedTokens / selectedTokens) : 0;
148
+ } else {
149
+ CWR = null;
150
+ }
151
+
152
+ // PFR: page faults normalized by total selected ids (avoids div-by-zero).
153
+ const PFR = selectedIds.length > 0 ? clamp01(pageFaults / selectedIds.length) : 0;
154
+
155
+ // RPC: raw reacquired tokens. Not normalized; it's a cost, not a rate.
156
+ const RPC = reacquiredTokens;
157
+
158
+ // -- Extended metrics (P1) --
159
+ //
160
+ // TSR (Task Success Rate): whether the downstream task succeeded.
161
+ // Computed as 1 when `taskSuccess` is true, 0 when false.
162
+ // NaN/undefined -> omitted from output to avoid poisoning summaries.
163
+ // Boolean per-record; aggregated in summarize() by mean.
164
+ const TSR =
165
+ input?.taskSuccess === true ? 1 : input?.taskSuccess === false ? 0 : null;
166
+
167
+ // VSR (Verification/Oracle Success Rate): did an independent oracle
168
+ // verify the selection as sufficient? Independent of the selector's
169
+ // own requiredIds — uses oracleMissing.length === 0 as truth.
170
+ // Defaults to null when no oracle verdict was supplied.
171
+ const oracleMissing = Array.isArray(input?.oracleMissing)
172
+ ? input.oracleMissing
173
+ : null;
174
+ const VSR = oracleMissing ? (oracleMissing.length === 0 ? 1 : 0) : null;
175
+
176
+ // TTC (Total Context Cost) = initialContextTokens + reacquiredTokens +
177
+ // retrievalOverhead. This is the real cost the system paid to
178
+ // deliver context, not just the bytes we shipped.
179
+ //
180
+ // Formula (per spec — TASK-09):
181
+ // TTC = initialContextTokens
182
+ // + reacquiredTokens
183
+ // + retrievalOverhead
184
+ //
185
+ // When `initialContextTokens` is not supplied it defaults to
186
+ // `selectedTokens` (the tokens we initially shipped before any
187
+ // re-fetches). When `retrievalOverhead` is not supplied it
188
+ // defaults to 0 explicitly so the cost decomposition stays
189
+ // auditable.
190
+ //
191
+ // NOTE: TTC_MAX is intentionally NOT defined here. There is no
192
+ // empirical baseline yet; setting a numeric cap now would be
193
+ // arbitrary and could mask regressions before we have data.
194
+ const initialContextTokens =
195
+ typeof input?.initialContextTokens === "number" &&
196
+ Number.isFinite(input.initialContextTokens)
197
+ ? Math.max(0, input.initialContextTokens)
198
+ : Math.max(0, selectedTokens);
199
+ const retrievalOverhead =
200
+ typeof input?.retrievalOverhead === "number" &&
201
+ Number.isFinite(input.retrievalOverhead)
202
+ ? Math.max(0, input.retrievalOverhead)
203
+ : 0;
204
+ const TTC = Math.round(
205
+ initialContextTokens + Math.max(0, reacquiredTokens) + retrievalOverhead
206
+ );
207
+
208
+ const out = {
209
+ strategy: input.strategy,
210
+ CRR: round4(CRR),
211
+ SPR: round4(SPR),
212
+ COR: round4(COR),
213
+ CWR: round4(CWR),
214
+ PFR: round4(PFR),
215
+ RPC: Math.round(RPC),
216
+ selectedTokens: Math.round(selectedTokens),
217
+ requiredTokens: Math.round(requiredTokens),
218
+ usedTokens: Math.round(usedTokens),
219
+ pageFaults,
220
+ reacquiredTokens: Math.round(reacquiredTokens),
221
+ initialContextTokens: Math.round(initialContextTokens),
222
+ TTC,
223
+ retrievalOverhead: Math.round(retrievalOverhead),
224
+ };
225
+ if (TSR !== null) out.TSR = round4(TSR);
226
+ if (VSR !== null) out.VSR = round4(VSR);
227
+ if (oracleMissing) out.oracleMissing = [...oracleMissing];
228
+ return out;
229
+ }
230
+
231
+ // -- Aggregate / summary --
232
+
233
+ /**
234
+ * Aggregate multiple metric records into a per-strategy summary
235
+ * (mean for rates, sum for RPC/PFR counts).
236
+ *
237
+ * @param {ContextMetrics[]} records
238
+ * @returns {Object<string, ContextMetrics & {samples: number}>}
239
+ */
240
+ export function summarize(records) {
241
+ const buckets = new Map();
242
+ for (const r of records || []) {
243
+ const key = r.strategy || "unknown";
244
+ if (!buckets.has(key)) buckets.set(key, []);
245
+ buckets.get(key).push(r);
246
+ }
247
+ const out = {};
248
+ for (const [strategy, items] of buckets) {
249
+ const n = items.length || 1;
250
+ const sum = (k) => items.reduce((acc, x) => acc + (x[k] || 0), 0);
251
+ const mean = (k) => sum(k) / n;
252
+ // TSR / VSR are per-record booleans coerced to 0/1; aggregate by mean
253
+ // over only the records that reported them. Records that did not
254
+ // report a verdict must not be counted as failures.
255
+ const tsrRecords = items.filter((x) => typeof x.TSR === "number");
256
+ const vsrRecords = items.filter((x) => typeof x.VSR === "number");
257
+ const tsrMean =
258
+ tsrRecords.length === 0
259
+ ? null
260
+ : tsrRecords.reduce((acc, x) => acc + x.TSR, 0) / tsrRecords.length;
261
+ const vsrMean =
262
+ vsrRecords.length === 0
263
+ ? null
264
+ : vsrRecords.reduce((acc, x) => acc + x.VSR, 0) / vsrRecords.length;
265
+ const agg = {
266
+ strategy,
267
+ CRR: round4(mean("CRR")),
268
+ SPR: round4(mean("SPR")),
269
+ COR: round4(mean("COR")),
270
+ CWR: round4(mean("CWR")),
271
+ PFR: round4(mean("PFR")),
272
+ RPC: Math.round(sum("RPC")),
273
+ selectedTokens: Math.round(sum("selectedTokens")),
274
+ requiredTokens: Math.round(sum("requiredTokens")),
275
+ usedTokens: Math.round(sum("usedTokens")),
276
+ pageFaults: Math.round(sum("pageFaults")),
277
+ reacquiredTokens: Math.round(sum("reacquiredTokens")),
278
+ initialContextTokens: Math.round(sum("initialContextTokens")),
279
+ TTC: Math.round(sum("TTC")),
280
+ retrievalOverhead: Math.round(sum("retrievalOverhead")),
281
+ samples: n,
282
+ };
283
+ if (tsrMean !== null) agg.TSR = round4(tsrMean);
284
+ if (vsrMean !== null) agg.VSR = round4(vsrMean);
285
+ agg.tsrReported = tsrRecords.length;
286
+ agg.vsrReported = vsrRecords.length;
287
+ out[strategy] = agg;
288
+ }
289
+ return out;
290
+ }
291
+
292
+ // -- Falsifiable gates --
293
+
294
+ /**
295
+ * Default thresholds for the falsifiable gates (C10).
296
+ *
297
+ * Gates are split into two explicit categories (P1):
298
+ *
299
+ * - SAFETY gates: a selector that fails these is fundamentally unsafe;
300
+ * they cannot be relaxed for "performance" reasons.
301
+ * SPR_MIN (>=0.99) required ids must mostly survive
302
+ * COR_MAX (=0.0) critical ids must NEVER be dropped
303
+ * TSR_MIN (>=0.99) downstream task must mostly succeed
304
+ * VSR_MIN (>=0.99) independent oracle must mostly verify
305
+ *
306
+ * - OPTIMIZATION gates: a selector that fails these is wasteful but
307
+ * not unsafe. They are tunable per-corpus.
308
+ * CWR_MAX (0.5) kept context was actually used
309
+ * PFR_MAX (0.05) additional fetches triggered by misses
310
+ * CRR_MIN (0.0) any reduction is fine
311
+ *
312
+ * RPC (Reacquisition Cost) and TTC (Total Context Cost) are
313
+ * INFORMATIONAL only as of TASK-09. They are still recorded on
314
+ * every metric and surfaced by summarize(), but they are NOT part
315
+ * of the hard optimization gates. Rationale: there is no
316
+ * empirical baseline yet, so any numeric cap (RPC_MAX / TTC_MAX)
317
+ * would be arbitrary and could mask real regressions before we have
318
+ * data. Once a baseline exists we can promote them back to
319
+ * gates with defensible thresholds.
320
+ *
321
+ * `evaluateGates()` evaluates BOTH sets and returns both verdicts.
322
+ * `evaluateSafetyGates()` and `evaluateOptimizationGates()` evaluate
323
+ * one set at a time so callers can reason about each independently.
324
+ */
325
+ export const SAFETY_GATES = Object.freeze({
326
+ SPR_MIN: 0.99,
327
+ COR_MAX: 0.0,
328
+ TSR_MIN: 0.99,
329
+ VSR_MIN: 0.99,
330
+ });
331
+
332
+ export const OPTIMIZATION_GATES = Object.freeze({
333
+ CWR_MAX: 0.5,
334
+ PFR_MAX: 0.05,
335
+ CRR_MIN: 0.0,
336
+ });
337
+
338
+ // Back-compat alias: the historical "DEFAULT_GATES" is the union of both
339
+ // sets, with the same numeric thresholds as before. New code should
340
+ // prefer SAFETY_GATES / OPTIMIZATION_GATES directly.
341
+ export const DEFAULT_GATES = Object.freeze({
342
+ ...SAFETY_GATES,
343
+ ...OPTIMIZATION_GATES,
344
+ });
345
+
346
+ /**
347
+ * Evaluate ONLY safety gates against a metric record.
348
+ *
349
+ * @param {ContextMetrics} m
350
+ * @param {Partial<typeof SAFETY_GATES>} [overrides]
351
+ * @returns {{pass: boolean, failures: string[]}}
352
+ */
353
+ export function evaluateSafetyGates(m, overrides = {}) {
354
+ const g = { ...SAFETY_GATES, ...(overrides || {}) };
355
+ const failures = [];
356
+ if (typeof m.SPR === "number" && m.SPR < g.SPR_MIN) {
357
+ failures.push(`SPR ${m.SPR} < ${g.SPR_MIN}`);
358
+ }
359
+ if (typeof m.COR === "number" && m.COR > g.COR_MAX) {
360
+ failures.push(`COR ${m.COR} > ${g.COR_MAX}`);
361
+ }
362
+ // TSR/VSR may be absent on records that did not report a verdict.
363
+ // We only fail when a verdict WAS reported and it falls below floor.
364
+ if (typeof m.TSR === "number" && m.TSR < g.TSR_MIN) {
365
+ failures.push(`TSR ${m.TSR} < ${g.TSR_MIN}`);
366
+ }
367
+ if (typeof m.VSR === "number" && m.VSR < g.VSR_MIN) {
368
+ failures.push(`VSR ${m.VSR} < ${g.VSR_MIN}`);
369
+ }
370
+ return { pass: failures.length === 0, failures };
371
+ }
372
+
373
+ /**
374
+ * Evaluate ONLY optimization gates against a metric record.
375
+ *
376
+ * @param {ContextMetrics} m
377
+ * @param {Partial<typeof OPTIMIZATION_GATES>} [overrides]
378
+ * @returns {{pass: boolean, failures: string[]}}
379
+ */
380
+ export function evaluateOptimizationGates(m, overrides = {}) {
381
+ const g = { ...OPTIMIZATION_GATES, ...(overrides || {}) };
382
+ const failures = [];
383
+ if (typeof m.CWR === "number" && m.CWR > g.CWR_MAX) {
384
+ failures.push(`CWR ${m.CWR} > ${g.CWR_MAX}`);
385
+ }
386
+ if (typeof m.PFR === "number" && m.PFR > g.PFR_MAX) {
387
+ failures.push(`PFR ${m.PFR} > ${g.PFR_MAX}`);
388
+ }
389
+ // NOTE: RPC_MAX was removed from the hard gates as of TASK-09. RPC
390
+ // (and TTC) remain recorded on every metric and surfaced via
391
+ // summarize(), but they are informational until we have an
392
+ // empirical baseline to set a defensible cap.
393
+ // CRR_MIN: if there is reduction at all, fine. We don't push for more.
394
+ if (typeof m.CRR === "number" && m.CRR < g.CRR_MIN) {
395
+ failures.push(`CRR ${m.CRR} < ${g.CRR_MIN}`);
396
+ }
397
+ return { pass: failures.length === 0, failures };
398
+ }
399
+
400
+ /**
401
+ * Evaluate a metric against the full falsifiable gate set.
402
+ *
403
+ * Returned object splits safety vs optimization verdicts so callers
404
+ * (benchmark, ablation, CI) can act on each independently. The legacy
405
+ * `pass` and `failures` fields remain the AND of both sets so existing
406
+ * callers keep working unchanged.
407
+ *
408
+ * @param {ContextMetrics} m
409
+ * @param {Partial<typeof DEFAULT_GATES>} [overrides]
410
+ * @returns {{pass: boolean, failures: string[], safety: {pass: boolean, failures: string[]}, optimization: {pass: boolean, failures: string[]}}}
411
+ */
412
+ export function evaluateGates(m, overrides = {}) {
413
+ const safety = evaluateSafetyGates(m, overrides);
414
+ const optimization = evaluateOptimizationGates(m, overrides);
415
+ return {
416
+ pass: safety.pass && optimization.pass,
417
+ failures: [...safety.failures, ...optimization.failures],
418
+ safety,
419
+ optimization,
420
+ };
421
+ }
422
+
423
+ // -- Helpers --
424
+
425
+ function clamp01(n) {
426
+ if (!Number.isFinite(n)) return 0;
427
+ if (n < 0) return 0;
428
+ if (n > 1) return 1;
429
+ return n;
430
+ }
431
+
432
+ function round4(n) {
433
+ return Math.round(n * 10000) / 10000;
434
+ }
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Context Output Policy — MUST/SHOULD/DO_NOT_INCLUDE policies for LLM context.
3
+ *
4
+ * Change 76: Policy-based filtering of what context is included,
5
+ * should be included, or must NOT be included when building LLM prompts.
6
+ *
7
+ * Policies:
8
+ * MUST — Always included, never filtered out
9
+ * SHOULD — Include unless over budget
10
+ * DO_NOT_INCLUDE — Never included
11
+ * UNCLASSIFIED — Default, treated as SHOULD
12
+ */
13
+
14
+ /** @typedef {"MUST"|"SHOULD"|"DO_NOT_INCLUDE"|"UNCLASSIFIED"} PolicyLevel */
15
+
16
+ /**
17
+ * @typedef {Object} PolicyRule
18
+ * @property {string} id - Rule identifier
19
+ * @property {PolicyLevel} policy - The policy level
20
+ * @property {(item: Object) => boolean} match - Predicate to match items
21
+ * @property {string} reason - Human-readable reason
22
+ */
23
+
24
+ /**
25
+ * @typedef {Object} PolicyResult
26
+ * @property {Object[]} must - Items classified MUST
27
+ * @property {Object[]} should - Items classified SHOULD
28
+ * @property {Object[]} excluded - Items classified DO_NOT_INCLUDE
29
+ * @property {Object[]} unclassified - Items without policy match
30
+ * @property {Set<string>} includedIds - IDs of all items included (must + should)
31
+ */
32
+
33
+ const DEFAULT_POLICY = "UNCLASSIFIED";
34
+
35
+ /**
36
+ * Change 76 — applyOutputPolicy
37
+ *
38
+ * Categorizes items into MUST / SHOULD / DO_NOT_INCLUDE / UNCLASSIFIED
39
+ * based on a list of policy rules.
40
+ *
41
+ * @param {Object[]} items - Context items to categorize
42
+ * @param {PolicyRule[]} rules - Policy rules to apply
43
+ * @returns {PolicyResult}
44
+ */
45
+ export function applyOutputPolicy(items, rules) {
46
+ if (!Array.isArray(items)) {
47
+ throw new TypeError("items must be an array");
48
+ }
49
+ if (!Array.isArray(rules)) {
50
+ throw new TypeError("rules must be an array");
51
+ }
52
+
53
+ const must = [];
54
+ const should = [];
55
+ const excluded = [];
56
+ const unclassified = [];
57
+
58
+ for (const item of items) {
59
+ if (!item || !item.id) continue;
60
+
61
+ let matched = false;
62
+ for (const rule of rules) {
63
+ if (!rule.match || !rule.match(item)) continue;
64
+ matched = true;
65
+ switch (rule.policy) {
66
+ case "MUST":
67
+ must.push(item);
68
+ break;
69
+ case "SHOULD":
70
+ should.push(item);
71
+ break;
72
+ case "DO_NOT_INCLUDE":
73
+ excluded.push(item);
74
+ break;
75
+ default:
76
+ unclassified.push(item);
77
+ }
78
+ break; // First matching rule wins
79
+ }
80
+ if (!matched) {
81
+ unclassified.push(item);
82
+ }
83
+ }
84
+
85
+ const includedIds = new Set([...must, ...should].map((i) => i.id));
86
+
87
+ return { must, should, excluded, unclassified, includedIds };
88
+ }
89
+
90
+ /**
91
+ * Filter items to only those allowed by policy (MUST + SHOULD).
92
+ * DO_NOT_INCLUDE items are removed.
93
+ *
94
+ * @param {Object[]} items
95
+ * @param {PolicyRule[]} rules
96
+ * @returns {Object[]}
97
+ */
98
+ export function filterByPolicy(items, rules) {
99
+ const result = applyOutputPolicy(items, rules);
100
+ return [...result.must, ...result.should];
101
+ }
102
+
103
+ /**
104
+ * Create a simple type-based policy rule.
105
+ * @param {string} id
106
+ * @param {PolicyLevel} policy
107
+ * @param {string[]} types
108
+ * @param {string} reason
109
+ * @returns {PolicyRule}
110
+ */
111
+ export function typePolicyRule(id, policy, types, reason) {
112
+ return {
113
+ id,
114
+ policy,
115
+ match: (item) => item && types.includes(item.type),
116
+ reason,
117
+ };
118
+ }
119
+
120
+ /**
121
+ * Create a simple scope-based policy rule.
122
+ * @param {string} id
123
+ * @param {PolicyLevel} policy
124
+ * @param {string[]} scopes
125
+ * @param {string} reason
126
+ * @returns {PolicyRule}
127
+ */
128
+ export function scopePolicyRule(id, policy, scopes, reason) {
129
+ return {
130
+ id,
131
+ policy,
132
+ match: (item) => item && scopes.includes(item.scope),
133
+ reason,
134
+ };
135
+ }