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.
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,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,2 @@
1
+ import { listCapsules, getCapsule, promoteCapsule, migrateLegacyCapsules } from "./context.js";
2
+ export { listCapsules, getCapsule, promoteCapsule, migrateLegacyCapsules };
@@ -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 };