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.
- package/LICENSE +21 -0
- package/README.md +318 -2
- package/index.js +1836 -0
- package/package.json +70 -4
- package/skills/codebase-design/SKILL.md +114 -0
- package/skills/design-contract/SKILL.md +27 -0
- package/skills/design-critique/SKILL.md +19 -0
- package/skills/design-system/SKILL.md +17 -0
- package/skills/design-system/references/image-analysis.md +14 -0
- package/skills/design-system/references/tokens.md +32 -0
- package/skills/design-system/references/ui-rules.md +14 -0
- package/skills/diagnosing-bugs/SKILL.md +138 -0
- package/skills/frontend-design/SKILL.md +36 -0
- package/skills/handoff/SKILL.md +16 -0
- package/skills/improve-codebase-architecture/SKILL.md +71 -0
- package/skills/linter-negative/SKILL.md +74 -0
- package/skills/no-slop-ui/SKILL.md +26 -0
- package/skills/prototype/SKILL.md +26 -0
- package/skills/registry.json +44087 -0
- package/skills/tastemaker/SKILL.md +8 -0
- package/skills/tastemaker/references/tastemaker.md +14 -0
- package/skills/tdd/SKILL.md +38 -0
- package/skills/visual-review/SKILL.md +103 -0
- package/skills/visual-review/references/review-gates.md +42 -0
- package/skills/visual-review/references/surface-archetypes.md +37 -0
- package/skills/wizard/SKILL.md +44 -0
- package/skills/writing-for-agents/SKILL.md +81 -0
- package/src/cognition/cognition-store.js +251 -0
- package/src/cognition/cognitive-state.js +126 -0
- package/src/cognition/hypothesis-manager.js +113 -0
- package/src/cognition/observation-engine.js +112 -0
- package/src/cognition/observation-provenance.js +268 -0
- package/src/context/active-context-boundary.js +68 -0
- package/src/context/assembly.js +494 -0
- package/src/context/assessment-rules.js +55 -0
- package/src/context/capsule-staleness.js +267 -0
- package/src/context/capsules.js +2 -0
- package/src/context/compare-array.js +98 -0
- package/src/context/compare-object.js +89 -0
- package/src/context/compare-values.js +42 -0
- package/src/context/context-assembly-contract.js +71 -0
- package/src/context/context-budget-manager.js +232 -0
- package/src/context/context-builder.js +49 -0
- package/src/context/context-cache.js +55 -0
- package/src/context/context-capture.js +243 -0
- package/src/context/context-classification.js +199 -0
- package/src/context/context-compaction.js +179 -0
- package/src/context/context-decision-audit.js +267 -0
- package/src/context/context-demotion.js +173 -0
- package/src/context/context-dictionary.js +106 -0
- package/src/context/context-drift-detection.js +225 -0
- package/src/context/context-evaluation.js +374 -0
- package/src/context/context-event-ingress.js +100 -0
- package/src/context/context-event-normalization.js +224 -0
- package/src/context/context-feedback-loop.js +216 -0
- package/src/context/context-freshness.js +21 -0
- package/src/context/context-graph-builder.js +71 -0
- package/src/context/context-graph.js +451 -0
- package/src/context/context-history-relevance.js +321 -0
- package/src/context/context-interception.js +145 -0
- package/src/context/context-invalidation-events.js +154 -0
- package/src/context/context-lifecycle.js +170 -0
- package/src/context/context-loop-prevention.js +347 -0
- package/src/context/context-manager.js +340 -0
- package/src/context/context-memory-layers.js +265 -0
- package/src/context/context-minimality.js +166 -0
- package/src/context/context-normalization.js +130 -0
- package/src/context/context-optimization-metrics.js +434 -0
- package/src/context/context-output-policy.js +135 -0
- package/src/context/context-pack-authority.js +157 -0
- package/src/context/context-promotion.js +172 -0
- package/src/context/context-query-contract.js +101 -0
- package/src/context/context-ranking.js +164 -0
- package/src/context/context-rate-distortion.js +261 -0
- package/src/context/context-recovery.js +209 -0
- package/src/context/context-retrieval-integration.js +60 -0
- package/src/context/context-retrieval.js +93 -0
- package/src/context/context-router.js +457 -0
- package/src/context/context-routing-evaluation.js +331 -0
- package/src/context/context-routing-observability.js +296 -0
- package/src/context/context-routing-shadow.js +237 -0
- package/src/context/context-scope.js +181 -0
- package/src/context/context-snapshot.js +226 -0
- package/src/context/context-source-registry.js +95 -0
- package/src/context/context-sufficiency-gate.js +120 -0
- package/src/context/context-sufficiency-oracle.js +167 -0
- package/src/context/context-telemetry.js +176 -0
- package/src/context/context.js +633 -0
- package/src/context/dependency-closure.js +246 -0
- package/src/context/page-fault-tracker.js +275 -0
- package/src/context/prompt-engine.js +74 -0
- package/src/context/runtime-context-graph.js +260 -0
- package/src/context/selection.js +2 -0
- package/src/context/session.js +2 -0
- package/src/context/sufficiency-contract.js +300 -0
- package/src/context/tokenizer.js +17 -0
- package/src/context-benchmark-router.mjs +125 -0
- package/src/evidence/evidence-engine.js +161 -0
- package/src/evidence/evidence-factory.js +3 -0
- package/src/evidence/evidence-freshness.js +463 -0
- package/src/evidence/evidence-gap.js +76 -0
- package/src/evidence/evidence-lineage.js +525 -0
- package/src/evidence/evidence.js +136 -0
- package/src/evidence/freshness-validation.js +187 -0
- package/src/evidence/lineage.js +4 -0
- package/src/execution/action-evaluation.js +179 -0
- package/src/execution/circuit-breaker.js +70 -0
- package/src/execution/claim-interception.js +63 -0
- package/src/execution/execution-engine.js +258 -0
- package/src/execution/execution-intent.js +69 -0
- package/src/execution/execution-state.js +89 -0
- package/src/execution/formatting.js +60 -0
- package/src/execution/l1-sanitize.js +16 -0
- package/src/execution/l2-resource-guards.js +19 -0
- package/src/execution/l3-invariant-checker.js +5 -0
- package/src/execution/l4-containment.js +6 -0
- package/src/execution/runtime-guards.js +106 -0
- package/src/execution/strategy-identity.js +162 -0
- package/src/execution/tool-interceptor.js +7 -0
- package/src/execution/tool-result-classifier.js +63 -0
- package/src/integration/canary-deploy.js +143 -0
- package/src/integration/collector.js +59 -0
- package/src/integration/error-recovery.js +6 -0
- package/src/integration/event-bus.js +66 -0
- package/src/integration/health-checker.js +57 -0
- package/src/integration/logger.js +39 -0
- package/src/integration/message-handler.js +611 -0
- package/src/integration/opencode-db.js +153 -0
- package/src/integration/orchestration.js +419 -0
- package/src/integration/part-provenance.js +65 -0
- package/src/integration/rate-limiter.js +55 -0
- package/src/integration/release-gate.js +336 -0
- package/src/integration/rollback-manager.js +164 -0
- package/src/integration/router-adapter.js +269 -0
- package/src/integration/telemetry.js +45 -0
- package/src/integration/update-checker.js +10 -0
- package/src/persistence/event-sourcing.js +75 -0
- package/src/persistence/memory.js +501 -0
- package/src/persistence/migration-runner.js +16 -0
- package/src/persistence/persistence-manager.js +39 -0
- package/src/persistence/transaction-log.js +157 -0
- package/src/policy/action-capabilities.js +116 -0
- package/src/policy/action-policy.js +4 -0
- package/src/policy/assessment-engine.js +104 -0
- package/src/policy/assumption-tracking.js +66 -0
- package/src/policy/budget-enforcer.js +7 -0
- package/src/policy/config-validator.js +102 -0
- package/src/policy/execution-assessment.js +142 -0
- package/src/policy/failure-classification.js +5 -0
- package/src/policy/feature-flags.js +65 -0
- package/src/policy/governance-enforcement.js +118 -0
- package/src/policy/locale.js +15 -0
- package/src/policy/multi-signer.js +151 -0
- package/src/policy/policy-state-machine.js +97 -0
- package/src/policy/ponytail-constraint.js +38 -0
- package/src/policy/project-inspector.js +93 -0
- package/src/policy/query-builder.js +14 -0
- package/src/policy/request-classifier.js +79 -0
- package/src/policy/risk-engine.js +266 -0
- package/src/policy/scope-enforcement.js +140 -0
- package/src/policy/secret-scanner.js +68 -0
- package/src/policy/skill-routing.js +104 -0
- package/src/policy/strategy-capabilities.js +211 -0
- package/src/policy/strategy.js +6 -0
- package/src/policy/uncertainty.js +117 -0
- package/src/shared/container.js +44 -0
- package/src/shared/decision-types.js +24 -0
- package/src/shared/simple-test.js +160 -0
- package/src/shared/wam-events.js +52 -0
- package/src/skills/engine.js +1705 -0
- package/src/skills/skill-registry.js +19 -0
- package/src/skills/skill-routing.js +340 -0
- package/src/state/lifecycle-manager.js +113 -0
- package/src/state/operational-state.js +198 -0
- package/src/state/projections.js +47 -0
- package/src/state/replay-engine.js +5 -0
- package/src/state/requirement-ownership.js +20 -0
- package/src/state/requirement-state.js +152 -0
- package/src/state/run-event-identity.js +172 -0
- package/src/state/state-engine.js +19 -0
- package/src/state/state-machine.js +119 -0
- package/src/state/state-schema.js +318 -0
- package/src/state/state-store.js +335 -0
- package/src/state/state-transition.js +84 -0
- package/src/state/task-dependencies.js +287 -0
- package/src/state/task-execution.js +217 -0
- package/src/state/task-lifecycle.js +59 -0
- package/src/state/task-runs.js +361 -0
- package/src/state/task-state-file.js +86 -0
- package/src/state/task-state.js +41 -0
- package/src/state/task-status.js +54 -0
- package/src/state/task-store.js +48 -0
- package/src/state/wam-state.js +148 -0
- package/src/verification/completion-gate-simple.js +8 -0
- package/src/verification/completion-gate.js +183 -0
- package/src/verification/contract.js +9 -0
- package/src/verification/contradiction-resolution.js +255 -0
- package/src/verification/diagnostic-engine.js +659 -0
- package/src/verification/false-completion-prevention.js +100 -0
- package/src/verification/hook-validator.js +6 -0
- package/src/verification/requirements.js +6 -0
- package/src/verification/verification-context.js +162 -0
- package/src/verification/verification-lifecycle.js +66 -0
- package/src/verification/verification-model.js +89 -0
- package/src/verification/verification-persistence.js +129 -0
- package/src/verification/verification-policy.js +127 -0
- 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
|
+
}
|