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