opencode-wait-a-minute 0.0.0-stage → 1.1.0-rc.10
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,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capsule Staleness Detection — proactive detection and auto-staling.
|
|
3
|
+
*
|
|
4
|
+
* Detects stale capsules proactively and auto-stales them.
|
|
5
|
+
* Estimates max staleness (MSL) per session from activity patterns.
|
|
6
|
+
*
|
|
7
|
+
* MSL = max gap between consecutive selections + safety margin.
|
|
8
|
+
* If no selections yet, default = 24h.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import fs from "node:fs";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
|
|
14
|
+
/** Default TTL in days */
|
|
15
|
+
const DEFAULT_TTL_DAYS = 30;
|
|
16
|
+
|
|
17
|
+
/** Safety margin multiplier for MSL */
|
|
18
|
+
const SAFETY_MARGIN = 1.5;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {Object} StalenessConfig
|
|
22
|
+
* @property {number} ttl_days - Default TTL in days
|
|
23
|
+
* @property {number} msl_hours - Max staleness limit in hours
|
|
24
|
+
* @property {boolean} auto_stale - Whether to auto-mark stale capsules
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @typedef {Object} StalenessResult
|
|
29
|
+
* @property {string} contextId
|
|
30
|
+
* @property {boolean} stale
|
|
31
|
+
* @property {string} reason
|
|
32
|
+
* @property {number} age_days
|
|
33
|
+
* @property {number} freshness
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Default staleness configuration.
|
|
38
|
+
*/
|
|
39
|
+
export const DEFAULT_CONFIG = {
|
|
40
|
+
ttl_days: DEFAULT_TTL_DAYS,
|
|
41
|
+
msl_hours: 24,
|
|
42
|
+
auto_stale: true,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Calculate capsule freshness based on age.
|
|
47
|
+
*
|
|
48
|
+
* @param {Object} capsule
|
|
49
|
+
* @param {number} now - Current timestamp (ms)
|
|
50
|
+
* @returns {number} Freshness score 0-1
|
|
51
|
+
*/
|
|
52
|
+
export function freshness(capsule, now = Date.now()) {
|
|
53
|
+
const updated = new Date(capsule.updated_at || capsule.created_at).getTime();
|
|
54
|
+
const days = Math.max(0, (now - updated) / 86400000);
|
|
55
|
+
return Math.max(0.1, 1 - days / DEFAULT_TTL_DAYS);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Check if a capsule is stale based on TTL and MSL.
|
|
60
|
+
*
|
|
61
|
+
* @param {Object} capsule
|
|
62
|
+
* @param {StalenessConfig} config
|
|
63
|
+
* @param {number} now - Current timestamp (ms)
|
|
64
|
+
* @returns {StalenessResult}
|
|
65
|
+
*/
|
|
66
|
+
export function checkStaleness(capsule, config = DEFAULT_CONFIG, now = Date.now()) {
|
|
67
|
+
const created = new Date(capsule.created_at).getTime();
|
|
68
|
+
const updated = new Date(capsule.updated_at || capsule.created_at).getTime();
|
|
69
|
+
const ageDays = (now - created) / 86400000;
|
|
70
|
+
const lastUpdateDays = (now - updated) / 86400000;
|
|
71
|
+
|
|
72
|
+
// Check TTL expiration
|
|
73
|
+
if (ageDays > config.ttl_days) {
|
|
74
|
+
return {
|
|
75
|
+
contextId: capsule.context_id,
|
|
76
|
+
stale: true,
|
|
77
|
+
reason: `ttl_exceeded: age ${ageDays.toFixed(1)}d > TTL ${config.ttl_days}d`,
|
|
78
|
+
age_days: ageDays,
|
|
79
|
+
freshness: freshness(capsule, now),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Check MSL (max staleness limit)
|
|
84
|
+
const mslDays = config.msl_hours / 24;
|
|
85
|
+
if (lastUpdateDays > mslDays) {
|
|
86
|
+
return {
|
|
87
|
+
contextId: capsule.context_id,
|
|
88
|
+
stale: true,
|
|
89
|
+
reason: `msl_exceeded: last update ${lastUpdateDays.toFixed(1)}d > MSL ${mslDays.toFixed(1)}d`,
|
|
90
|
+
age_days: ageDays,
|
|
91
|
+
freshness: freshness(capsule, now),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Check if lifecycle is already stale
|
|
96
|
+
if (capsule.lifecycle === "stale") {
|
|
97
|
+
return {
|
|
98
|
+
contextId: capsule.context_id,
|
|
99
|
+
stale: true,
|
|
100
|
+
reason: "already_stale",
|
|
101
|
+
age_days: ageDays,
|
|
102
|
+
freshness: freshness(capsule, now),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
contextId: capsule.context_id,
|
|
108
|
+
stale: false,
|
|
109
|
+
reason: "fresh",
|
|
110
|
+
age_days: ageDays,
|
|
111
|
+
freshness: freshness(capsule, now),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Estimate MSL (max staleness limit) from session activity patterns.
|
|
117
|
+
*
|
|
118
|
+
* @param {string} root - Project root
|
|
119
|
+
* @param {string} sessionId - Session ID
|
|
120
|
+
* @returns {number} MSL in hours
|
|
121
|
+
*/
|
|
122
|
+
export function estimateMSL(root, sessionId) {
|
|
123
|
+
const tracesDir = path.join(root, ".wam", "traces");
|
|
124
|
+
const selectionLog = path.join(tracesDir, "selection-log.jsonl");
|
|
125
|
+
|
|
126
|
+
if (!fs.existsSync(selectionLog)) {
|
|
127
|
+
return DEFAULT_CONFIG.msl_hours;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const content = fs.readFileSync(selectionLog, "utf-8");
|
|
132
|
+
const lines = content.trim().split("\n").filter(Boolean);
|
|
133
|
+
|
|
134
|
+
// Get timestamps for this session
|
|
135
|
+
const timestamps = [];
|
|
136
|
+
for (const line of lines) {
|
|
137
|
+
try {
|
|
138
|
+
const entry = JSON.parse(line);
|
|
139
|
+
if (entry.session_id === sessionId || !entry.session_id) {
|
|
140
|
+
timestamps.push(new Date(entry.timestamp).getTime());
|
|
141
|
+
}
|
|
142
|
+
} catch {}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (timestamps.length < 2) {
|
|
146
|
+
return DEFAULT_CONFIG.msl_hours;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Calculate gaps between consecutive selections
|
|
150
|
+
timestamps.sort((a, b) => a - b);
|
|
151
|
+
const gaps = [];
|
|
152
|
+
for (let i = 1; i < timestamps.length; i++) {
|
|
153
|
+
gaps.push(timestamps[i] - timestamps[i - 1]);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// MSL = max gap + safety margin
|
|
157
|
+
const maxGap = Math.max(...gaps);
|
|
158
|
+
const mslMs = maxGap * SAFETY_MARGIN;
|
|
159
|
+
const mslHours = Math.max(1, mslMs / 3600000);
|
|
160
|
+
|
|
161
|
+
// Clamp to reasonable bounds
|
|
162
|
+
return Math.min(72, Math.max(1, mslHours));
|
|
163
|
+
} catch {
|
|
164
|
+
return DEFAULT_CONFIG.msl_hours;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Scan all active capsules and return staleness results.
|
|
170
|
+
*
|
|
171
|
+
* @param {string} root - Project root
|
|
172
|
+
* @param {StalenessConfig} config
|
|
173
|
+
* @returns {StalenessResult[]}
|
|
174
|
+
*/
|
|
175
|
+
export function scanForStale(root, config = DEFAULT_CONFIG) {
|
|
176
|
+
const capsulesDir = path.join(root, ".wam", "capsules");
|
|
177
|
+
if (!fs.existsSync(capsulesDir)) {
|
|
178
|
+
return [];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const results = [];
|
|
182
|
+
const files = fs.readdirSync(capsulesDir).filter((f) => f.endsWith(".json"));
|
|
183
|
+
|
|
184
|
+
for (const file of files) {
|
|
185
|
+
try {
|
|
186
|
+
const content = fs.readFileSync(path.join(capsulesDir, file), "utf-8");
|
|
187
|
+
const capsule = JSON.parse(content);
|
|
188
|
+
|
|
189
|
+
if (capsule.lifecycle === "active") {
|
|
190
|
+
results.push(checkStaleness(capsule, config));
|
|
191
|
+
}
|
|
192
|
+
} catch {}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return results;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Auto-mark stale capsules in a directory.
|
|
200
|
+
*
|
|
201
|
+
* @param {string} root - Project root
|
|
202
|
+
* @param {StalenessConfig} config
|
|
203
|
+
* @returns {string[]} List of capsule IDs that were marked stale
|
|
204
|
+
*/
|
|
205
|
+
export function autoMarkStale(root, config = DEFAULT_CONFIG) {
|
|
206
|
+
if (!config.auto_stale) {
|
|
207
|
+
return [];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const capsulesDir = path.join(root, ".wam", "capsules");
|
|
211
|
+
if (!fs.existsSync(capsulesDir)) {
|
|
212
|
+
return [];
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const marked = [];
|
|
216
|
+
const files = fs.readdirSync(capsulesDir).filter((f) => f.endsWith(".json"));
|
|
217
|
+
|
|
218
|
+
for (const file of files) {
|
|
219
|
+
try {
|
|
220
|
+
const filePath = path.join(capsulesDir, file);
|
|
221
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
222
|
+
const capsule = JSON.parse(content);
|
|
223
|
+
|
|
224
|
+
if (capsule.lifecycle === "active") {
|
|
225
|
+
const result = checkStaleness(capsule, config);
|
|
226
|
+
if (result.stale) {
|
|
227
|
+
capsule.lifecycle = "stale";
|
|
228
|
+
capsule.updated_at = new Date().toISOString();
|
|
229
|
+
fs.writeFileSync(filePath, JSON.stringify(capsule, null, 2) + "\n");
|
|
230
|
+
marked.push(capsule.context_id);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
} catch {}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return marked;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Get staleness report for a project.
|
|
241
|
+
*
|
|
242
|
+
* @param {string} root - Project root
|
|
243
|
+
* @param {StalenessConfig} config
|
|
244
|
+
* @returns {Object} Staleness report
|
|
245
|
+
*/
|
|
246
|
+
export function getStalenessReport(root, config = DEFAULT_CONFIG) {
|
|
247
|
+
const results = scanForStale(root, config);
|
|
248
|
+
const stale = results.filter((r) => r.stale);
|
|
249
|
+
const fresh = results.filter((r) => !r.stale);
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
total: results.length,
|
|
253
|
+
stale: stale.length,
|
|
254
|
+
fresh: fresh.length,
|
|
255
|
+
staleCapsules: stale.map((r) => ({
|
|
256
|
+
id: r.contextId,
|
|
257
|
+
reason: r.reason,
|
|
258
|
+
age_days: r.age_days,
|
|
259
|
+
})),
|
|
260
|
+
freshCapsules: fresh.map((r) => ({
|
|
261
|
+
id: r.contextId,
|
|
262
|
+
freshness: r.freshness,
|
|
263
|
+
age_days: r.age_days,
|
|
264
|
+
})),
|
|
265
|
+
config,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Array comparison — element-wise with placeholder tolerance.
|
|
3
|
+
*
|
|
4
|
+
* Rules:
|
|
5
|
+
* - both empty → SUPPORTED
|
|
6
|
+
* - expected empty, actual non-empty → INCONCLUSIVE (allowed extras)
|
|
7
|
+
* - actual shorter than expected → CONTRADICTED for every missing expected element
|
|
8
|
+
* - actual longer than expected → INCONCLUSIVE for extras
|
|
9
|
+
* - element-wise:
|
|
10
|
+
* nested array → recurse
|
|
11
|
+
* nested object → compareObject
|
|
12
|
+
* scalar → compareValues
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { AssessmentResult } from "./assessment-rules.js";
|
|
16
|
+
import { compareValues } from "./compare-values.js";
|
|
17
|
+
import { compareObject } from "./compare-object.js";
|
|
18
|
+
|
|
19
|
+
export function compareArray(expected, actual, path = "") {
|
|
20
|
+
const findings = [];
|
|
21
|
+
|
|
22
|
+
if (!Array.isArray(expected) || !Array.isArray(actual)) {
|
|
23
|
+
findings.push({
|
|
24
|
+
result: AssessmentResult.INCONCLUSIVE,
|
|
25
|
+
path,
|
|
26
|
+
expected,
|
|
27
|
+
actual,
|
|
28
|
+
reason: "expected/actual is not an array",
|
|
29
|
+
});
|
|
30
|
+
return findings;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (expected.length === 0 && actual.length === 0) {
|
|
34
|
+
findings.push({
|
|
35
|
+
result: AssessmentResult.SUPPORTED,
|
|
36
|
+
path,
|
|
37
|
+
expected,
|
|
38
|
+
actual,
|
|
39
|
+
reason: "both arrays empty",
|
|
40
|
+
});
|
|
41
|
+
return findings;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (expected.length === 0) {
|
|
45
|
+
findings.push({
|
|
46
|
+
result: AssessmentResult.INCONCLUSIVE,
|
|
47
|
+
path,
|
|
48
|
+
expected,
|
|
49
|
+
actual,
|
|
50
|
+
reason: "expected empty, actual has extras",
|
|
51
|
+
});
|
|
52
|
+
return findings;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
for (let i = 0; i < expected.length; i++) {
|
|
56
|
+
const childPath = `${path}[${i}]`;
|
|
57
|
+
if (i >= actual.length) {
|
|
58
|
+
findings.push({
|
|
59
|
+
result: AssessmentResult.CONTRADICTED,
|
|
60
|
+
path: childPath,
|
|
61
|
+
expected: expected[i],
|
|
62
|
+
actual: undefined,
|
|
63
|
+
reason: "missing array element",
|
|
64
|
+
});
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const expEl = expected[i];
|
|
69
|
+
const actEl = actual[i];
|
|
70
|
+
|
|
71
|
+
if (Array.isArray(expEl) && Array.isArray(actEl)) {
|
|
72
|
+
findings.push(...compareArray(expEl, actEl, childPath));
|
|
73
|
+
} else if (
|
|
74
|
+
expEl !== null &&
|
|
75
|
+
typeof expEl === "object" &&
|
|
76
|
+
actEl !== null &&
|
|
77
|
+
typeof actEl === "object" &&
|
|
78
|
+
!Array.isArray(actEl)
|
|
79
|
+
) {
|
|
80
|
+
findings.push(...compareObject(expEl, actEl, childPath));
|
|
81
|
+
} else {
|
|
82
|
+
findings.push(...compareValues(expEl, actEl, childPath));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (let i = expected.length; i < actual.length; i++) {
|
|
87
|
+
const childPath = `${path}[${i}]`;
|
|
88
|
+
findings.push({
|
|
89
|
+
result: AssessmentResult.INCONCLUSIVE,
|
|
90
|
+
path: childPath,
|
|
91
|
+
expected: undefined,
|
|
92
|
+
actual: actual[i],
|
|
93
|
+
reason: "extra array element",
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return findings;
|
|
98
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Object comparison — recurses into nested keys, classifies missing/partial/extra.
|
|
3
|
+
*
|
|
4
|
+
* Findings classification:
|
|
5
|
+
* - missing key (in expected, not in actual) → CONTRADICTED
|
|
6
|
+
* - extra key (in actual, not in expected) → INCONCLUSIVE (allowed extras)
|
|
7
|
+
* - nested object → recurse via compareObject
|
|
8
|
+
* - nested array → delegate to compareArray
|
|
9
|
+
* - scalar → compareValues
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { AssessmentResult } from "./assessment-rules.js";
|
|
13
|
+
import { compareValues } from "./compare-values.js";
|
|
14
|
+
import { compareArray } from "./compare-array.js";
|
|
15
|
+
|
|
16
|
+
export function compareObject(expected, actual, path = "") {
|
|
17
|
+
const findings = [];
|
|
18
|
+
|
|
19
|
+
if (expected === null || typeof expected !== "object" || Array.isArray(expected)) {
|
|
20
|
+
findings.push({
|
|
21
|
+
result: AssessmentResult.INCONCLUSIVE,
|
|
22
|
+
path,
|
|
23
|
+
expected,
|
|
24
|
+
actual,
|
|
25
|
+
reason: "expected is not an object",
|
|
26
|
+
});
|
|
27
|
+
return findings;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (actual === null || typeof actual !== "object" || Array.isArray(actual)) {
|
|
31
|
+
findings.push({
|
|
32
|
+
result: AssessmentResult.CONTRADICTED,
|
|
33
|
+
path,
|
|
34
|
+
expected,
|
|
35
|
+
actual,
|
|
36
|
+
reason: "actual is not an object",
|
|
37
|
+
});
|
|
38
|
+
return findings;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const expectedKeys = Object.keys(expected);
|
|
42
|
+
const actualKeys = Object.keys(actual);
|
|
43
|
+
|
|
44
|
+
for (const key of expectedKeys) {
|
|
45
|
+
const childPath = path ? `${path}.${key}` : key;
|
|
46
|
+
if (!(key in actual)) {
|
|
47
|
+
findings.push({
|
|
48
|
+
result: AssessmentResult.CONTRADICTED,
|
|
49
|
+
path: childPath,
|
|
50
|
+
expected: expected[key],
|
|
51
|
+
actual: undefined,
|
|
52
|
+
reason: "missing key",
|
|
53
|
+
});
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const expVal = expected[key];
|
|
58
|
+
const actVal = actual[key];
|
|
59
|
+
|
|
60
|
+
if (Array.isArray(expVal) && Array.isArray(actVal)) {
|
|
61
|
+
findings.push(...compareArray(expVal, actVal, childPath));
|
|
62
|
+
} else if (
|
|
63
|
+
expVal !== null &&
|
|
64
|
+
typeof expVal === "object" &&
|
|
65
|
+
actVal !== null &&
|
|
66
|
+
typeof actVal === "object" &&
|
|
67
|
+
!Array.isArray(actVal)
|
|
68
|
+
) {
|
|
69
|
+
findings.push(...compareObject(expVal, actVal, childPath));
|
|
70
|
+
} else {
|
|
71
|
+
findings.push(...compareValues(expVal, actVal, childPath));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for (const key of actualKeys) {
|
|
76
|
+
if (!(key in expected)) {
|
|
77
|
+
const childPath = path ? `${path}.${key}` : key;
|
|
78
|
+
findings.push({
|
|
79
|
+
result: AssessmentResult.INCONCLUSIVE,
|
|
80
|
+
path: childPath,
|
|
81
|
+
expected: undefined,
|
|
82
|
+
actual: actual[key],
|
|
83
|
+
reason: "extra key",
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return findings;
|
|
89
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scalar comparison — atomic value vs atomic value.
|
|
3
|
+
* Produces a finding: { result, path, expected, actual }
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { AssessmentResult, classifyScalar, isMissing } from "./assessment-rules.js";
|
|
7
|
+
|
|
8
|
+
export function compareValues(expected, actual, path = "") {
|
|
9
|
+
if (isMissing(expected)) {
|
|
10
|
+
return [
|
|
11
|
+
{
|
|
12
|
+
result: AssessmentResult.INCONCLUSIVE,
|
|
13
|
+
path,
|
|
14
|
+
expected,
|
|
15
|
+
actual,
|
|
16
|
+
reason: "expected value missing",
|
|
17
|
+
},
|
|
18
|
+
];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (isMissing(actual)) {
|
|
22
|
+
return [
|
|
23
|
+
{
|
|
24
|
+
result: AssessmentResult.CONTRADICTED,
|
|
25
|
+
path,
|
|
26
|
+
expected,
|
|
27
|
+
actual,
|
|
28
|
+
reason: "actual value missing",
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return [
|
|
34
|
+
{
|
|
35
|
+
result: classifyScalar(expected, actual),
|
|
36
|
+
path,
|
|
37
|
+
expected,
|
|
38
|
+
actual,
|
|
39
|
+
reason: null,
|
|
40
|
+
},
|
|
41
|
+
];
|
|
42
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { assembleContext } from "./assembly.js";
|
|
2
|
+
import { buildContextQuery, CONTEXT_PURPOSE } from "./context-query-contract.js";
|
|
3
|
+
import { checkSufficiency, SufficiencyGate, SUFFICIENCY_LEVEL } from "./context-sufficiency-gate.js";
|
|
4
|
+
|
|
5
|
+
export const CONTEXT_ASSEMBLY_STATUS = Object.freeze({
|
|
6
|
+
PROCEED: "PROCEED",
|
|
7
|
+
BLOCK: "BLOCK",
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export function assembleContextWithQuery(params = {}) {
|
|
11
|
+
const query = params.query || buildContextQuery({
|
|
12
|
+
taskId: params.taskId,
|
|
13
|
+
purpose: params.purpose || CONTEXT_PURPOSE.PLANNING,
|
|
14
|
+
requirementId: params.requirementId,
|
|
15
|
+
hypothesisId: params.hypothesisId,
|
|
16
|
+
experimentId: params.experimentId,
|
|
17
|
+
executionId: params.executionId,
|
|
18
|
+
taskType: params.taskType,
|
|
19
|
+
phase: params.phase,
|
|
20
|
+
activeFiles: params.activeFiles || [],
|
|
21
|
+
activeTools: params.activeTools || [],
|
|
22
|
+
knownFailures: params.knownFailures || [],
|
|
23
|
+
unresolvedQuestions: params.unresolvedQuestions || [],
|
|
24
|
+
budget: params.budget,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const sufficiency = checkSufficiency(params.items || [], query.purpose);
|
|
28
|
+
const gate = new SufficiencyGate({
|
|
29
|
+
purpose: query.purpose,
|
|
30
|
+
minLevel: params.minLevel || SUFFICIENCY_LEVEL.SUFFICIENT,
|
|
31
|
+
requireMandatory: params.requireMandatory || false,
|
|
32
|
+
});
|
|
33
|
+
const gateResult = gate.evaluate(params.items || []);
|
|
34
|
+
const hasNoItems = !params.items || params.items.length === 0;
|
|
35
|
+
const minLevel = params.minLevel || SUFFICIENCY_LEVEL.SUFFICIENT;
|
|
36
|
+
const shouldBlock = !hasNoItems && (
|
|
37
|
+
gateResult.sufficiency.level === SUFFICIENCY_LEVEL.INSUFFICIENT ||
|
|
38
|
+
(params.minLevel && gateResult.sufficiency.level !== minLevel &&
|
|
39
|
+
gateResult.decision !== "PROCEED")
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
if (shouldBlock) {
|
|
43
|
+
return {
|
|
44
|
+
status: CONTEXT_ASSEMBLY_STATUS.BLOCK,
|
|
45
|
+
query,
|
|
46
|
+
sufficiency: gateResult.sufficiency,
|
|
47
|
+
gate: gateResult,
|
|
48
|
+
context: null,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const context = assembleContext({
|
|
53
|
+
...params,
|
|
54
|
+
query,
|
|
55
|
+
taskState: {
|
|
56
|
+
...(params.taskState || {}),
|
|
57
|
+
query,
|
|
58
|
+
unresolvedCriticalUnknowns: gateResult.sufficiency.unresolvedCriticalUnknowns,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
status: CONTEXT_ASSEMBLY_STATUS.PROCEED,
|
|
64
|
+
query,
|
|
65
|
+
sufficiency: gateResult.sufficiency,
|
|
66
|
+
gate: gateResult,
|
|
67
|
+
context,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export { assembleContext };
|