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,321 @@
1
+ /**
2
+ * WAM Context History Relevance
3
+ *
4
+ * Extends context selection to include historical task information
5
+ * based on relevance to the current task.
6
+ *
7
+ * Historical information is selected when related to:
8
+ * - current task
9
+ * - current requirement
10
+ * - current workflow stage
11
+ * - current artifact
12
+ * - active dependency
13
+ * - previous failure
14
+ * - previous decision
15
+ * - unresolved issue
16
+ * - verification state
17
+ *
18
+ * Ranking priority:
19
+ * 1. Explicit dependency
20
+ * 2. Active requirement
21
+ * 3. Same task/stage
22
+ * 4. Same artifact
23
+ * 5. Related failure
24
+ * 6. Related decision
25
+ * 7. Recency
26
+ *
27
+ * Invariant: Historical information retains provenance (current, historical,
28
+ * superseded, stale, invalidated, unresolved). Router must never silently
29
+ * convert historical information into current knowledge.
30
+ */
31
+
32
+ import { ContextGraph } from "./context-graph.js";
33
+ import { getRuns, getPreviousFailures, getPreviousDecisions, getUnresolvedRequirements } from "../state/task-runs.js";
34
+ import { getDependencies, detectContextGaps } from "../state/task-dependencies.js";
35
+ import { getTaskState } from "../skills/engine.js";
36
+
37
+ /**
38
+ * @typedef {Object} HistoricalCandidate
39
+ * @property {string} id
40
+ * @property {string} type - "run", "failure", "decision", "evidence"
41
+ * @property {string} content
42
+ * @property {number} relevanceScore
43
+ * @property {string} relevanceReason
44
+ * @property {string} status - "current", "historical", "superseded", "stale", "invalidated", "unresolved"
45
+ * @property {number} timestamp
46
+ * @property {string} runId
47
+ */
48
+
49
+ function estimateTokens(text = "") {
50
+ const str = typeof text === "string" ? text : "";
51
+ return Math.ceil(str.length / 4);
52
+ }
53
+
54
+ /**
55
+ * Coerce any run-item (string, {text}, {content}, null, etc.) into a
56
+ * lowercase-string token set for keyword analysis. Returns "" for empty/unknown
57
+ * inputs so callers never have to deal with `.toLowerCase is not a function`.
58
+ */
59
+ function asText(item) {
60
+ if (item == null) return "";
61
+ if (typeof item === "string") return item;
62
+ if (typeof item === "number" || typeof item === "boolean") return String(item);
63
+ if (typeof item === "object") {
64
+ if (typeof item.text === "string") return item.text;
65
+ if (typeof item.content === "string") return item.content;
66
+ if (typeof item.decision === "string") return item.decision;
67
+ if (typeof item.outcome === "string") return item.outcome;
68
+ try {
69
+ return JSON.stringify(item);
70
+ } catch {
71
+ return "";
72
+ }
73
+ }
74
+ return "";
75
+ }
76
+
77
+ function asLower(item) {
78
+ return asText(item).toLowerCase();
79
+ }
80
+
81
+ export function getHistoricalCandidates(taskId, root, graph = null) {
82
+ const candidates = [];
83
+ const runs = getRuns(taskId, root);
84
+ const failures = getPreviousFailures(taskId, root);
85
+ const decisions = getPreviousDecisions(taskId, root, 10);
86
+ const unresolved = getUnresolvedRequirements(taskId, root);
87
+
88
+ // Get current task context for relevance scoring
89
+ const taskNode = graph ? graph.getNode(taskId) : null;
90
+ const taskContent = taskNode?.content || "";
91
+
92
+ // Get keywords from task state if no graph content
93
+ const taskState = getTaskState(taskId, root);
94
+ const stateObjective = taskState?.contract?.objective || "";
95
+ const combinedContent = taskContent + " " + stateObjective;
96
+ const taskKeywords = new Set(combinedContent.toLowerCase().split(/\s+/).filter(Boolean));
97
+
98
+ // Score candidates based on relevance
99
+ for (const run of runs) {
100
+ // Determine status based on run outcome
101
+ let status = "historical";
102
+ if (run.status === "active") {
103
+ status = "current";
104
+ } else if (run.outcome?.toLowerCase().includes("superseded")) {
105
+ status = "superseded";
106
+ } else if (run.status === "abandoned") {
107
+ status = "stale";
108
+ }
109
+
110
+ // Score observations from this run
111
+ for (const obs of run.observations || []) {
112
+ const obsStr = asText(obs);
113
+ const obsLower = asLower(obs);
114
+ const obsKeywords = new Set(obsLower.split(/\s+/).filter(Boolean));
115
+ const overlap = [...taskKeywords].filter((k) => obsKeywords.has(k)).length;
116
+ const score = computeRelevanceScore({
117
+ hasExplicitDependency: false,
118
+ matchesActiveRequirement: unresolved.some((r) => obsLower.includes(asLower(r.title))),
119
+ sameTask: true,
120
+ sameArtifact: obsLower.includes("artifact") || obsLower.includes("build"),
121
+ relatedFailure: failures.some((f) => f.outcome && obsLower.includes(asLower(f.outcome))),
122
+ relatedDecision: decisions.some((d) => obsLower.includes(asLower(d.decision))),
123
+ keywordOverlap: overlap,
124
+ isRecent: run.startedAt > Date.now() - 86400000 * 7, // last 7 days
125
+ });
126
+
127
+ if (score > 0.15) {
128
+ candidates.push({
129
+ id: `obs-${run.id}-${candidates.length}`,
130
+ type: "observation",
131
+ content: obsStr,
132
+ relevanceScore: score,
133
+ relevanceReason: getRelevanceReason({ sameTask: true, keywordOverlap: overlap }),
134
+ status,
135
+ timestamp: run.startedAt,
136
+ runId: run.id,
137
+ });
138
+ }
139
+ }
140
+
141
+ // Score decisions from this run
142
+ for (const decision of run.decisions || []) {
143
+ const decisionStr = asText(decision);
144
+ const decisionLower = asLower(decision);
145
+ const decisionKeywords = new Set(decisionLower.split(/\s+/).filter(Boolean));
146
+ const overlap = [...taskKeywords].filter((k) => decisionKeywords.has(k)).length;
147
+ const score = computeRelevanceScore({
148
+ hasExplicitDependency: graph ? graph.getDependencies(taskId).some((d) => asLower(d.content).includes(decisionLower)) : false,
149
+ matchesActiveRequirement: unresolved.some((r) => decisionLower.includes(asLower(r.title))),
150
+ sameTask: true,
151
+ sameArtifact: decisionLower.includes("artifact") || decisionLower.includes("build"),
152
+ relatedFailure: false,
153
+ relatedDecision: false,
154
+ keywordOverlap: overlap,
155
+ isRecent: run.startedAt > Date.now() - 86400000 * 7,
156
+ });
157
+
158
+ if (score > 0.15) {
159
+ candidates.push({
160
+ id: `decision-${run.id}-${candidates.length}`,
161
+ type: "decision",
162
+ content: decisionStr,
163
+ relevanceScore: score,
164
+ relevanceReason: getRelevanceReason({ hasExplicitDependency: score > 0.8, sameTask: true, keywordOverlap: overlap }),
165
+ status,
166
+ timestamp: run.startedAt,
167
+ runId: run.id,
168
+ });
169
+ }
170
+ }
171
+
172
+ // Score evidence from this run
173
+ for (const evidence of run.evidence || []) {
174
+ const evidenceStr = asText(evidence);
175
+ const evidenceLower = asLower(evidence);
176
+ const score = computeRelevanceScore({
177
+ hasExplicitDependency: false,
178
+ matchesActiveRequirement: unresolved.some((r) => evidenceLower.includes(asLower(r.title))),
179
+ sameTask: true,
180
+ sameArtifact: false,
181
+ relatedFailure: false,
182
+ relatedDecision: false,
183
+ keywordOverlap: 0,
184
+ isRecent: run.startedAt > Date.now() - 86400000 * 7,
185
+ });
186
+
187
+ if (score > 0.15) {
188
+ candidates.push({
189
+ id: `evidence-${run.id}-${candidates.length}`,
190
+ type: "evidence",
191
+ content: evidenceStr,
192
+ relevanceScore: score,
193
+ relevanceReason: getRelevanceReason({ sameTask: true }),
194
+ status,
195
+ timestamp: run.startedAt,
196
+ runId: run.id,
197
+ });
198
+ }
199
+ }
200
+ }
201
+
202
+ // Add previous failures as candidates
203
+ for (const failure of failures) {
204
+ const failureOutcomeLower = asLower(failure.outcome);
205
+ const score = computeRelevanceScore({
206
+ hasExplicitDependency: false,
207
+ matchesActiveRequirement: false,
208
+ sameTask: true,
209
+ sameArtifact: failureOutcomeLower.includes("artifact") || failureOutcomeLower.includes("build"),
210
+ relatedFailure: true,
211
+ relatedDecision: false,
212
+ keywordOverlap: 0,
213
+ isRecent: false,
214
+ });
215
+
216
+ if (score > 0.1) {
217
+ candidates.push({
218
+ id: `failure-${failure.id}`,
219
+ type: "failure",
220
+ content: asText(failure.outcome) || "Failed run",
221
+ relevanceScore: score,
222
+ relevanceReason: getRelevanceReason({ relatedFailure: true }),
223
+ status: "historical",
224
+ timestamp: failure.completedAt || failure.startedAt,
225
+ runId: failure.id,
226
+ });
227
+ }
228
+ }
229
+
230
+ // Add unresolved requirements as candidates
231
+ for (const req of unresolved) {
232
+ const reqTitleLower = asLower(req.title);
233
+ const reqKeywords = new Set(reqTitleLower.split(/\s+/).filter(Boolean));
234
+ const overlap = [...taskKeywords].filter((k) => reqKeywords.has(k)).length;
235
+
236
+ if (overlap > 0) {
237
+ candidates.push({
238
+ id: `req-${req.id}`,
239
+ type: "requirement",
240
+ content: asText(req.title),
241
+ relevanceScore: 0.9,
242
+ relevanceReason: "active requirement",
243
+ status: "unresolved",
244
+ timestamp: Date.now(),
245
+ runId: null,
246
+ });
247
+ }
248
+ }
249
+
250
+ return candidates.sort((a, b) => b.relevanceScore - a.relevanceScore);
251
+ }
252
+
253
+ function computeRelevanceScore({
254
+ hasExplicitDependency,
255
+ matchesActiveRequirement,
256
+ sameTask,
257
+ sameArtifact,
258
+ relatedFailure,
259
+ relatedDecision,
260
+ keywordOverlap,
261
+ isRecent,
262
+ }) {
263
+ let score = 0;
264
+
265
+ if (hasExplicitDependency) score += 0.5;
266
+ if (matchesActiveRequirement) score += 0.4;
267
+ if (sameTask) score += 0.2;
268
+ if (sameArtifact) score += 0.15;
269
+ if (relatedFailure) score += 0.12;
270
+ if (relatedDecision) score += 0.1;
271
+ score += Math.min(keywordOverlap * 0.05, 0.2);
272
+ if (isRecent) score += 0.05;
273
+
274
+ return Math.min(score, 1.0);
275
+ }
276
+
277
+ function getRelevanceReason(props) {
278
+ if (props.hasExplicitDependency) return "explicit dependency";
279
+ if (props.matchesActiveRequirement) return "matches active requirement";
280
+ if (props.relatedFailure) return "related to previous failure";
281
+ if (props.relatedDecision) return "related to previous decision";
282
+ if (props.sameArtifact) return "same artifact context";
283
+ if (props.sameTask) return "same task";
284
+ if (props.keywordOverlap > 0) return `keyword overlap (${props.keywordOverlap})`;
285
+ return "recent";
286
+ }
287
+
288
+ export function selectHistoricalContext(taskId, root, graph, maxTokens = 1000) {
289
+ const candidates = getHistoricalCandidates(taskId, root, graph);
290
+
291
+ const selected = [];
292
+ let tokenCount = 0;
293
+
294
+ for (const candidate of candidates) {
295
+ const candidateTokens = estimateTokens(candidate.content) + 30;
296
+ if (tokenCount + candidateTokens > maxTokens) break;
297
+
298
+ selected.push(candidate);
299
+ tokenCount += candidateTokens;
300
+ }
301
+
302
+ return {
303
+ candidates: selected,
304
+ totalTokens: tokenCount,
305
+ tokenBudget: maxTokens,
306
+ includesCurrent: selected.some((s) => s.status === "current"),
307
+ includesHistorical: selected.some((s) => s.status === "historical"),
308
+ includesUnresolved: selected.some((s) => s.status === "unresolved"),
309
+ };
310
+ }
311
+
312
+ export function getRelevanceRankingExplanation(candidates, limit = 5) {
313
+ return candidates.slice(0, limit).map((c, i) => ({
314
+ rank: i + 1,
315
+ id: c.id,
316
+ type: c.type,
317
+ relevanceScore: c.relevanceScore,
318
+ reason: c.relevanceReason,
319
+ status: c.status,
320
+ }));
321
+ }
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Context Interception — filter and prioritize raw context before it reaches the LLM.
3
+ *
4
+ * Change 75: interceptContext(raw)
5
+ *
6
+ * Provides a gate through which all raw context must pass before being
7
+ * sent to the LLM. Filters noise, deduplicates, and re-sorts by
8
+ * relevance/priority. Deterministic, no side effects.
9
+ *
10
+ * Pipeline stages:
11
+ * 1. Deduplicate (by id or content hash)
12
+ * 2. Filter (by type, scope, relevance score, custom predicates)
13
+ * 3. Prioritize (sort by score, then by recency)
14
+ * 4. Cap (limit to maxItems)
15
+ */
16
+
17
+ /**
18
+ * @typedef {Object} InterceptedItem
19
+ * @property {string} id
20
+ * @property {string} content
21
+ * @property {string} source
22
+ * @property {string} type
23
+ * @property {number} timestamp
24
+ * @property {string} scope
25
+ * @property {number} relevanceScore
26
+ * @property {Object} [metadata]
27
+ */
28
+
29
+ /**
30
+ * @typedef {Object} InterceptOptions
31
+ * @property {number} [maxItems=50] - Maximum items after interception
32
+ * @property {string[]} [allowedTypes] - Only include these types
33
+ * @property {string[]} [allowedScopes] - Only include these scopes
34
+ * @property {number} [minRelevance=0] - Minimum relevance score 0-1
35
+ * @property {Function} [predicate] - Custom filter predicate (item => boolean)
36
+ * @property {string[]} [priorityTypes] - Types sorted first (higher priority)
37
+ * @property {boolean} [deduplicate=true] - Deduplicate by id
38
+ */
39
+
40
+ /**
41
+ * Compute a simple relevance score for an item.
42
+ * Higher = more relevant. Based on presence of keywords and recency.
43
+ * @param {Object} item
44
+ * @param {string[]} [keywords]
45
+ * @returns {number}
46
+ */
47
+ export function relevanceScore(item, keywords = []) {
48
+ if (!item || !item.content) return 0.5;
49
+ const content = String(item.content).toLowerCase();
50
+ let score = 0.5;
51
+ if (keywords.length > 0) {
52
+ const matches = keywords.filter((kw) => content.includes(kw.toLowerCase()));
53
+ score = 0.5 + (matches.length / keywords.length) * 0.5;
54
+ }
55
+ // Recency bonus: newer items slightly boosted
56
+ const age = Date.now() - (item.timestamp || 0);
57
+ const hoursOld = age / (1000 * 60 * 60);
58
+ const recencyBonus = Math.max(0, 0.1 - hoursOld * 0.001);
59
+ return Math.min(1, Math.max(0, score + recencyBonus));
60
+ }
61
+
62
+ /**
63
+ * Change 75 — interceptContext
64
+ *
65
+ * Filters, deduplicates, prioritizes, and caps raw context items
66
+ * before they reach the LLM.
67
+ *
68
+ * @param {Object[]} raw - Array of raw context items
69
+ * @param {InterceptOptions} [options={}]
70
+ * @returns {InterceptedItem[]} Filtered and ordered items
71
+ */
72
+ export function interceptContext(raw, options = {}) {
73
+ if (!Array.isArray(raw)) {
74
+ throw new TypeError("raw must be an array");
75
+ }
76
+
77
+ const {
78
+ maxItems = 50,
79
+ allowedTypes = null,
80
+ allowedScopes = null,
81
+ minRelevance = 0,
82
+ predicate = null,
83
+ priorityTypes = [],
84
+ deduplicate = true,
85
+ } = options;
86
+
87
+ // Stage 1: Deduplicate by id
88
+ let items = raw;
89
+ if (deduplicate) {
90
+ const seen = new Set();
91
+ items = raw.filter((item) => {
92
+ if (!item || !item.id) return false;
93
+ if (seen.has(item.id)) return false;
94
+ seen.add(item.id);
95
+ return true;
96
+ });
97
+ }
98
+
99
+ // Stage 2: Filter
100
+ items = items.filter((item) => {
101
+ if (!item || !item.id) return false;
102
+ if (allowedTypes && !allowedTypes.includes(item.type)) return false;
103
+ if (allowedScopes && !allowedScopes.includes(item.scope)) return false;
104
+ if (predicate && !predicate(item)) return false;
105
+ return true;
106
+ });
107
+
108
+ // Stage 3: Score and sort
109
+ items = items.map((item) => ({
110
+ ...item,
111
+ relevanceScore: relevanceScore(item, options.keywords || []),
112
+ }));
113
+
114
+ // Sort by: priority type first, then relevance score desc, then timestamp desc
115
+ items.sort((a, b) => {
116
+ const aPriority = priorityTypes.indexOf(a.type);
117
+ const bPriority = priorityTypes.indexOf(b.type);
118
+ const aRank = aPriority === -1 ? Infinity : aPriority;
119
+ const bRank = bPriority === -1 ? Infinity : bPriority;
120
+ if (aRank !== bRank) return aRank - bRank;
121
+ if (b.relevanceScore !== a.relevanceScore) return b.relevanceScore - a.relevanceScore;
122
+ return (b.timestamp || 0) - (a.timestamp || 0);
123
+ });
124
+
125
+ // Stage 4: Apply min relevance and cap
126
+ items = items.filter((item) => item.relevanceScore >= minRelevance);
127
+ items = items.slice(0, maxItems);
128
+
129
+ return items;
130
+ }
131
+
132
+ /**
133
+ * Build a quick filter that only keeps items matching a set of requirement IDs.
134
+ * Useful when only context related to specific requirements should reach the LLM.
135
+ * @param {string[]} requirementIds
136
+ * @returns {InterceptOptions}
137
+ */
138
+ export function requirementFilter(requirementIds) {
139
+ return {
140
+ predicate: (item) => {
141
+ const reqs = item.relatedRequirements || [];
142
+ return reqs.some((r) => requirementIds.includes(r));
143
+ },
144
+ };
145
+ }
@@ -0,0 +1,154 @@
1
+ import { CONTEXT_EVENT_TYPES } from "./context-event-ingress.js";
2
+ import { invalidateDependentEvidence } from "../evidence/evidence-lineage.js";
3
+ import { transition as lifecycleTransition } from "./context-lifecycle.js";
4
+
5
+ export const INVALIDATION_REASONS = Object.freeze({
6
+ REQUIREMENT_CHANGED: "REQUIREMENT_CHANGED",
7
+ STRATEGY_CHANGED: "STRATEGY_CHANGED",
8
+ HYPOTHESIS_CONTRADICTED: "HYPOTHESIS_CONTRADICTED",
9
+ TOOL_RESULT_CONTRADICTS_PREMISE: "TOOL_RESULT_CONTRADICTS_PREMISE",
10
+ DEPENDENCY_CHANGED: "DEPENDENCY_CHANGED",
11
+ CONFIG_CHANGED: "CONFIG_CHANGED",
12
+ USER_DECISION_CHANGED: "USER_DECISION_CHANGED",
13
+ BRANCH_CHANGED: "BRANCH_CHANGED",
14
+ FILE_MODIFIED: "FILE_MODIFIED",
15
+ ENV_CHANGED: "ENV_CHANGED",
16
+ });
17
+
18
+ export function createInvalidationEvent(taskId, reason, options = {}) {
19
+ const event = {
20
+ type: CONTEXT_EVENT_TYPES.CONTEXT_INVALIDATED,
21
+ taskId,
22
+ sessionId: options.sessionId || null,
23
+ executionId: options.executionId || null,
24
+ provenance: options.provenance || "context-invalidation",
25
+ payload: {
26
+ reason,
27
+ invalidatedAt: Date.now(),
28
+ invalidatedBy: options.invalidatedBy || "system",
29
+ scope: options.scope || "TASK",
30
+ invalidatedIds: options.invalidatedIds || [],
31
+ unresolvedCriticalUnknowns: options.unresolvedCriticalUnknowns || [],
32
+ ...options.payload,
33
+ },
34
+ };
35
+ return event;
36
+ }
37
+
38
+ export function applyInvalidation(contextManager, evidenceLineage, event) {
39
+ const { taskId, payload } = event;
40
+ const { reason, invalidatedIds, scope } = payload;
41
+
42
+ const affected = [];
43
+
44
+ if (invalidatedIds && invalidatedIds.length > 0) {
45
+ for (const itemId of invalidatedIds) {
46
+ const item = contextManager.getSource(itemId);
47
+ if (item) {
48
+ const updated = lifecycleTransition(item, "INVALIDATED", {
49
+ invalidationReason: reason,
50
+ invalidationEventId: event.id,
51
+ invalidatedAt: payload.invalidatedAt,
52
+ });
53
+ contextManager.register(updated);
54
+ affected.push({ itemId, reason, action: "INVALIDATED" });
55
+ }
56
+ }
57
+ }
58
+
59
+ if (reason === INVALIDATION_REASONS.HYPOTHESIS_CONTRADICTED) {
60
+ const hypothesisId = invalidatedIds?.[0] || options?.hypothesisId;
61
+ if (hypothesisId && evidenceLineage) {
62
+ const staleEvidence = invalidateDependentEvidence(taskId, hypothesisId, reason);
63
+ for (const ev of staleEvidence) {
64
+ const item = contextManager.getSource(ev.id);
65
+ if (item) {
66
+ const updated = lifecycleTransition(item, "INVALIDATED", {
67
+ invalidationReason: reason,
68
+ invalidationEventId: event.id,
69
+ });
70
+ contextManager.register(updated);
71
+ affected.push({ itemId: ev.id, reason, action: "INVALIDATED_EVIDENCE" });
72
+ }
73
+ }
74
+ }
75
+ }
76
+
77
+ return affected;
78
+ }
79
+
80
+ export function createFileModifiedEvent(taskId, path, sessionId, options = {}) {
81
+ return createInvalidationEvent(taskId, INVALIDATION_REASONS.FILE_MODIFIED, {
82
+ sessionId,
83
+ payload: { path, changeType: options.changeType || "modify", affectedItems: options.affectedItems },
84
+ });
85
+ }
86
+
87
+ export function createRequirementChangedEvent(taskId, requirementId, sessionId, options = {}) {
88
+ return createInvalidationEvent(taskId, INVALIDATION_REASONS.REQUIREMENT_CHANGED, {
89
+ sessionId,
90
+ invalidatedIds: [requirementId],
91
+ payload: { requirementId, changeType: options.changeType },
92
+ });
93
+ }
94
+
95
+ export function createStrategyChangedEvent(taskId, strategyId, sessionId, options = {}) {
96
+ return createInvalidationEvent(taskId, INVALIDATION_REASONS.STRATEGY_CHANGED, {
97
+ sessionId,
98
+ payload: { strategyId, changeType: options.changeType },
99
+ });
100
+ }
101
+
102
+ export function createHypothesisContradictedEvent(taskId, hypothesisId, sessionId, executionId, options = {}) {
103
+ return createInvalidationEvent(taskId, INVALIDATION_REASONS.HYPOTHESIS_CONTRADICTED, {
104
+ sessionId,
105
+ executionId,
106
+ invalidatedIds: [hypothesisId],
107
+ payload: { hypothesisId, assessment: options.assessment },
108
+ });
109
+ }
110
+
111
+ export function createDependencyChangedEvent(taskId, dependency, sessionId, options = {}) {
112
+ return createInvalidationEvent(taskId, INVALIDATION_REASONS.DEPENDENCY_CHANGED, {
113
+ sessionId,
114
+ payload: { dependency, changeType: options.changeType },
115
+ });
116
+ }
117
+
118
+ export function createConfigChangedEvent(taskId, configKey, sessionId, options = {}) {
119
+ return createInvalidationEvent(taskId, INVALIDATION_REASONS.CONFIG_CHANGED, {
120
+ sessionId,
121
+ payload: { configKey, changeType: options.changeType },
122
+ });
123
+ }
124
+
125
+ export function createToolResultContradictsPremiseEvent(taskId, premise, executionId, sessionId, options = {}) {
126
+ return createInvalidationEvent(taskId, INVALIDATION_REASONS.TOOL_RESULT_CONTRADICTS_PREMISE, {
127
+ sessionId,
128
+ executionId,
129
+ payload: { premise, toolResult: options.toolResult },
130
+ });
131
+ }
132
+
133
+ export const CONTEXT_INVALIDATION_ACTIONS = Object.freeze({
134
+ INVALIDATE_ITEM: "INVALIDATE_ITEM",
135
+ STALE_ITEM: "STALE_ITEM",
136
+ ARCHIVE_ITEM: "ARCHIVE_ITEM",
137
+ REEVALUATE_ITEM: "REEVALUATE_ITEM",
138
+ });
139
+
140
+ export function determineInvalidationAction(item, reason) {
141
+ if (item.lifecycle === "ARCHIVED" || item.lifecycle === "INVALIDATED") {
142
+ return CONTEXT_INVALIDATION_ACTIONS.REEVALUATE_ITEM;
143
+ }
144
+ if (reason === INVALIDATION_REASONS.HYPOTHESIS_CONTRADICTED) {
145
+ return CONTEXT_INVALIDATION_ACTIONS.INVALIDATE_ITEM;
146
+ }
147
+ if (reason === INVALIDATION_REASONS.FILE_MODIFIED) {
148
+ return CONTEXT_INVALIDATION_ACTIONS.STALE_ITEM;
149
+ }
150
+ if (reason === INVALIDATION_REASONS.CONFIG_CHANGED || reason === INVALIDATION_REASONS.ENV_CHANGED) {
151
+ return CONTEXT_INVALIDATION_ACTIONS.INVALIDATE_ITEM;
152
+ }
153
+ return CONTEXT_INVALIDATION_ACTIONS.INVALIDATE_ITEM;
154
+ }