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,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WAM Context Routing — Shadow Mode
|
|
3
|
+
*
|
|
4
|
+
* Computes what context WAM would send for a task, without modifying
|
|
5
|
+
* OpenCode context. Used for evaluation against the current context pack.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { ContextGraph } from "./context-graph.js";
|
|
9
|
+
import { getResumeContext, getPreviousFailures, getPreviousDecisions } from "../state/task-runs.js";
|
|
10
|
+
import { getDependencies, getUpstream, detectMissingDependencies, detectContextGaps } from "../state/task-dependencies.js";
|
|
11
|
+
import { resolveContext } from "./context-router.js";
|
|
12
|
+
import { getTaskState } from "../skills/engine.js";
|
|
13
|
+
|
|
14
|
+
// -- Types --
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {Object} ContextSnapshot
|
|
18
|
+
* @property {string[]} nodeIds
|
|
19
|
+
* @property {number} tokenEstimate
|
|
20
|
+
* @property {string[]} sources - Where each piece of context came from
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {Object} ContextGap
|
|
25
|
+
* @property {string} type
|
|
26
|
+
* @property {string} severity
|
|
27
|
+
* @property {string} description
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {Object} ShadowContextResult
|
|
32
|
+
* @property {string} taskId
|
|
33
|
+
* @property {ContextSnapshot} currentContext
|
|
34
|
+
* @property {ContextSnapshot} proposedContext
|
|
35
|
+
* @property {ContextGap[]} missing
|
|
36
|
+
* @property {{ current: number, proposed: number }} tokenEstimate
|
|
37
|
+
* @property {string[]} routingDecisions - Why each piece was selected
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
// -- Token estimation --
|
|
41
|
+
|
|
42
|
+
function estimateTokens(text = "") {
|
|
43
|
+
return Math.ceil((text || "").length / 4);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function nodeTokens(node) {
|
|
47
|
+
return estimateTokens(node.content) + 20;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// -- Current Context Pack (simulates what WAM currently sends) --
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Simulate the current WAM context pack for a task.
|
|
54
|
+
* This represents the N0-N3 model.
|
|
55
|
+
*/
|
|
56
|
+
export function getCurrentContextPack(taskId, root) {
|
|
57
|
+
const sources = [];
|
|
58
|
+
const nodeIds = [];
|
|
59
|
+
let tokens = 0;
|
|
60
|
+
|
|
61
|
+
// N0: Global policy (minimal)
|
|
62
|
+
sources.push("N0:global-policy");
|
|
63
|
+
tokens += 50;
|
|
64
|
+
|
|
65
|
+
// N2: Task state (required)
|
|
66
|
+
const state = getTaskState(taskId, root);
|
|
67
|
+
if (state) {
|
|
68
|
+
nodeIds.push(`task-${taskId}`);
|
|
69
|
+
const taskTokens = estimateTokens(JSON.stringify(state));
|
|
70
|
+
tokens += taskTokens;
|
|
71
|
+
sources.push(`N2:task-state (${taskTokens} tokens)`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// N3: Session context (top-level only)
|
|
75
|
+
sources.push("N3:session-context");
|
|
76
|
+
tokens += 200;
|
|
77
|
+
|
|
78
|
+
return { nodeIds, tokenEstimate: tokens, sources };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// -- Proposed Context (shadow routing) --
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Compute proposed context using dependency-aware routing.
|
|
85
|
+
* Combines: task state + dependencies + evidence + relevant history.
|
|
86
|
+
*/
|
|
87
|
+
export function getProposedContext(taskId, graph, root) {
|
|
88
|
+
const sources = [];
|
|
89
|
+
const nodeIds = [];
|
|
90
|
+
let tokens = 0;
|
|
91
|
+
|
|
92
|
+
// 1. Current task state (always included)
|
|
93
|
+
nodeIds.push(taskId);
|
|
94
|
+
sources.push("task-state");
|
|
95
|
+
tokens += 200;
|
|
96
|
+
|
|
97
|
+
// 2. Required dependencies (from graph)
|
|
98
|
+
if (graph) {
|
|
99
|
+
const deps = graph.getDependencies(taskId);
|
|
100
|
+
for (const dep of deps) {
|
|
101
|
+
nodeIds.push(dep.id);
|
|
102
|
+
tokens += nodeTokens(dep);
|
|
103
|
+
sources.push(`dependency:${dep.type}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 3. Upstream context (transitive deps)
|
|
107
|
+
const upstream = graph.getUpstream(taskId, 3);
|
|
108
|
+
for (const u of upstream) {
|
|
109
|
+
if (!nodeIds.includes(u.id)) {
|
|
110
|
+
nodeIds.push(u.id);
|
|
111
|
+
tokens += nodeTokens(u);
|
|
112
|
+
sources.push(`upstream:${u.type}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 4. Required evidence
|
|
118
|
+
if (graph) {
|
|
119
|
+
const evidenceNodes = graph.getNodes("evidence");
|
|
120
|
+
for (const e of evidenceNodes) {
|
|
121
|
+
if (!nodeIds.includes(e.id)) {
|
|
122
|
+
// Check if this evidence supports any of our dependencies
|
|
123
|
+
const supports = graph.getEdgesFrom(e.id);
|
|
124
|
+
const relevant = supports.some((s) => nodeIds.includes(s.to));
|
|
125
|
+
if (relevant) {
|
|
126
|
+
nodeIds.push(e.id);
|
|
127
|
+
tokens += nodeTokens(e);
|
|
128
|
+
sources.push("evidence:supports-dependency");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 5. Relevant execution history (last 2 failures + 3 decisions)
|
|
135
|
+
const failures = getPreviousFailures(taskId, root);
|
|
136
|
+
for (const f of failures.slice(-2)) {
|
|
137
|
+
nodeIds.push(`exec-failure-${f.id}`);
|
|
138
|
+
tokens += estimateTokens(f.outcome || "") + 20;
|
|
139
|
+
sources.push(`history:failure`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const decisions = getPreviousDecisions(taskId, root, 3);
|
|
143
|
+
for (const d of decisions) {
|
|
144
|
+
nodeIds.push(`exec-decision-${d.executionId}`);
|
|
145
|
+
tokens += estimateTokens(d.decision) + 20;
|
|
146
|
+
sources.push(`history:decision`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 6. Context gaps
|
|
150
|
+
let gaps = [];
|
|
151
|
+
if (graph) {
|
|
152
|
+
// Detect missing deps by checking edges from this task
|
|
153
|
+
const edges = graph.getEdgesFrom(taskId);
|
|
154
|
+
for (const edge of edges) {
|
|
155
|
+
if (["requires_output", "requires_completion", "requires_evidence",
|
|
156
|
+
"requires_decision", "depends_on_artifact"].includes(edge.type)) {
|
|
157
|
+
const targetNode = graph.getNode(edge.to);
|
|
158
|
+
if (!targetNode) {
|
|
159
|
+
gaps.push({ type: "missing_dependency", severity: "blocking", description: `Output ${edge.to} not found` });
|
|
160
|
+
} else if (!targetNode.verified) {
|
|
161
|
+
gaps.push({ type: "unverified_dependency", severity: "warning", description: `Output ${edge.to} not verified` });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
nodeIds,
|
|
169
|
+
tokenEstimate: tokens,
|
|
170
|
+
sources,
|
|
171
|
+
gaps,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// -- Shadow Router --
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Run shadow routing: compute both current and proposed context.
|
|
179
|
+
* Never modifies OpenCode input.
|
|
180
|
+
*/
|
|
181
|
+
export function shadowRoute(taskId, graph, root) {
|
|
182
|
+
const current = getCurrentContextPack(taskId, root);
|
|
183
|
+
const proposed = getProposedContext(taskId, graph, root);
|
|
184
|
+
const missing = proposed.gaps || [];
|
|
185
|
+
|
|
186
|
+
const routingDecisions = proposed.sources.map((s) => `Include: ${s}`);
|
|
187
|
+
|
|
188
|
+
if (missing.length > 0) {
|
|
189
|
+
routingDecisions.push(`Block: ${missing.length} context gaps detected`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
taskId,
|
|
194
|
+
currentContext: {
|
|
195
|
+
nodeIds: current.nodeIds,
|
|
196
|
+
tokenEstimate: current.tokenEstimate,
|
|
197
|
+
sources: current.sources,
|
|
198
|
+
},
|
|
199
|
+
proposedContext: {
|
|
200
|
+
nodeIds: proposed.nodeIds,
|
|
201
|
+
tokenEstimate: proposed.tokenEstimate,
|
|
202
|
+
sources: proposed.sources,
|
|
203
|
+
},
|
|
204
|
+
missing,
|
|
205
|
+
tokenEstimate: {
|
|
206
|
+
current: current.tokenEstimate,
|
|
207
|
+
proposed: proposed.tokenEstimate,
|
|
208
|
+
},
|
|
209
|
+
routingDecisions,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// -- Evaluation Helpers --
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Check if proposed context includes all required items from current context.
|
|
217
|
+
*/
|
|
218
|
+
export function hasRequiredContext(proposed, required) {
|
|
219
|
+
return required.every((r) => proposed.nodeIds.includes(r));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Compute context overlap between current and proposed.
|
|
224
|
+
*/
|
|
225
|
+
export function computeOverlap(current, proposed) {
|
|
226
|
+
const currentSet = new Set(current.nodeIds);
|
|
227
|
+
const proposedSet = new Set(proposed.nodeIds);
|
|
228
|
+
const intersection = [...currentSet].filter((id) => proposedSet.has(id));
|
|
229
|
+
const union = new Set([...currentSet, ...proposedSet]);
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
shared: intersection.length,
|
|
233
|
+
onlyInCurrent: currentSet.size - intersection.length,
|
|
234
|
+
onlyInProposed: proposedSet.size - intersection.length,
|
|
235
|
+
jaccard: union.size > 0 ? intersection.length / union.size : 0,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Scope — define and assign scope boundaries to context items.
|
|
3
|
+
*
|
|
4
|
+
* Scopes: GLOBAL, PROJECT, TASK, REQUIREMENT, ACTION, SESSION, TURN.
|
|
5
|
+
*
|
|
6
|
+
* Scope determines the visibility and lifetime of a context item.
|
|
7
|
+
* A context item's scope is assigned by analyzing the item's metadata,
|
|
8
|
+
* type, and associated identifiers.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** @typedef {"GLOBAL"|"PROJECT"|"TASK"|"REQUIREMENT"|"ACTION"|"SESSION"|"TURN"} ContextScope */
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {Object} ScopeResult
|
|
15
|
+
* @property {ContextScope} scope
|
|
16
|
+
* @property {number} confidence 0-1
|
|
17
|
+
* @property {string} reason
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
// Scope hierarchy (smaller = more specific)
|
|
21
|
+
const SCOPE_LEVELS = ["GLOBAL", "PROJECT", "TASK", "REQUIREMENT", "ACTION", "SESSION", "TURN"];
|
|
22
|
+
|
|
23
|
+
// Type-to-scope hints
|
|
24
|
+
const TYPE_SCOPE_HINTS = {
|
|
25
|
+
tool_call: "ACTION",
|
|
26
|
+
tool_result: "ACTION",
|
|
27
|
+
agent_message: "SESSION",
|
|
28
|
+
system_message: "GLOBAL",
|
|
29
|
+
file_modification: "ACTION",
|
|
30
|
+
git_change: "PROJECT",
|
|
31
|
+
requirement_change: "REQUIREMENT",
|
|
32
|
+
verification_result: "ACTION",
|
|
33
|
+
clarification_request: "SESSION",
|
|
34
|
+
plan: "TASK",
|
|
35
|
+
rationale: "SESSION",
|
|
36
|
+
assumption: "SESSION",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Infer scope from metadata fields.
|
|
41
|
+
* @param {Object} metadata
|
|
42
|
+
* @returns {ContextScope|null}
|
|
43
|
+
*/
|
|
44
|
+
function inferFromMetadata(metadata) {
|
|
45
|
+
if (!metadata || typeof metadata !== "object") return null;
|
|
46
|
+
|
|
47
|
+
// Explicit scope in metadata (uppercase normalized)
|
|
48
|
+
if (metadata.scope) {
|
|
49
|
+
const raw = String(metadata.scope).toUpperCase();
|
|
50
|
+
if (SCOPE_LEVELS.includes(raw)) {
|
|
51
|
+
return raw;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Task ID implies TASK scope (or more specific if requirementId present)
|
|
56
|
+
if (metadata.taskId || metadata.task_id) {
|
|
57
|
+
if (metadata.requirementId || metadata.requirement_id) {
|
|
58
|
+
return "REQUIREMENT";
|
|
59
|
+
}
|
|
60
|
+
if (metadata.actionId || metadata.action_id) {
|
|
61
|
+
return "ACTION";
|
|
62
|
+
}
|
|
63
|
+
return "TASK";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Requirement ID implies REQUIREMENT scope
|
|
67
|
+
if (metadata.requirementId || metadata.requirement_id) {
|
|
68
|
+
return "REQUIREMENT";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Session ID implies SESSION scope
|
|
72
|
+
if (metadata.sessionId || metadata.session_id || metadata.session) {
|
|
73
|
+
return "SESSION";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Turn number implies TURN scope
|
|
77
|
+
if (metadata.turn !== undefined || metadata.turnNumber !== undefined) {
|
|
78
|
+
return "TURN";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Project name/id implies PROJECT scope
|
|
82
|
+
if (metadata.projectId || metadata.project_id || metadata.project) {
|
|
83
|
+
return "PROJECT";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Infer scope from item type and content.
|
|
91
|
+
* @param {Object} item
|
|
92
|
+
* @returns {ContextScope|null}
|
|
93
|
+
*/
|
|
94
|
+
function inferFromType(item) {
|
|
95
|
+
const type = (item.type || "").toLowerCase();
|
|
96
|
+
return TYPE_SCOPE_HINTS[type] || null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Assign scope to a context item.
|
|
101
|
+
* @param {Object} item - Context item with type, metadata, content
|
|
102
|
+
* @param {Object} [metadata] - Additional context for scoping decisions
|
|
103
|
+
* @returns {ScopeResult}
|
|
104
|
+
*/
|
|
105
|
+
export function scopeContextItem(item, metadata = {}) {
|
|
106
|
+
if (!item || typeof item !== "object") {
|
|
107
|
+
return { scope: "SESSION", confidence: 0.3, reason: "default_fallback" };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const combinedMetadata = { ...item.metadata, ...metadata };
|
|
111
|
+
|
|
112
|
+
// 1. Explicit scope in metadata (highest priority)
|
|
113
|
+
const explicit = inferFromMetadata(combinedMetadata);
|
|
114
|
+
if (explicit) {
|
|
115
|
+
return { scope: explicit, confidence: 0.95, reason: "explicit_metadata" };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 2. Type-based inference
|
|
119
|
+
const typeScope = inferFromType(item);
|
|
120
|
+
if (typeScope) {
|
|
121
|
+
return { scope: typeScope, confidence: 0.8, reason: `type:${item.type}` };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 3. Content-based keywords
|
|
125
|
+
const text = typeof item.content === "string"
|
|
126
|
+
? item.content
|
|
127
|
+
: typeof item.summary === "string"
|
|
128
|
+
? item.summary
|
|
129
|
+
: JSON.stringify(item.content || "");
|
|
130
|
+
|
|
131
|
+
const lower = text.toLowerCase();
|
|
132
|
+
|
|
133
|
+
if (metadata.globalScope || combinedMetadata.globalScope || lower.includes("global")) {
|
|
134
|
+
return { scope: "GLOBAL", confidence: 0.7, reason: "global_signal" };
|
|
135
|
+
}
|
|
136
|
+
if (lower.includes("project") || combinedMetadata.project) {
|
|
137
|
+
return { scope: "PROJECT", confidence: 0.7, reason: "project_signal" };
|
|
138
|
+
}
|
|
139
|
+
if (lower.includes("requirement") || combinedMetadata.requirement) {
|
|
140
|
+
return { scope: "REQUIREMENT", confidence: 0.7, reason: "requirement_signal" };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 4. Default: SESSION scope
|
|
144
|
+
return { scope: "SESSION", confidence: 0.6, reason: "default_session" };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Normalize a scope string to a valid ContextScope.
|
|
149
|
+
* @param {string} scope
|
|
150
|
+
* @returns {ContextScope}
|
|
151
|
+
*/
|
|
152
|
+
export function normalizeScope(scope) {
|
|
153
|
+
if (!scope) return "SESSION";
|
|
154
|
+
const upper = String(scope).toUpperCase();
|
|
155
|
+
if (SCOPE_LEVELS.includes(upper)) return upper;
|
|
156
|
+
return "SESSION";
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Get parent scope (one level up in hierarchy).
|
|
161
|
+
* @param {ContextScope} scope
|
|
162
|
+
* @returns {ContextScope}
|
|
163
|
+
*/
|
|
164
|
+
export function parentScope(scope) {
|
|
165
|
+
const idx = SCOPE_LEVELS.indexOf(scope);
|
|
166
|
+
if (idx <= 0) return scope; // GLOBAL has no parent
|
|
167
|
+
return SCOPE_LEVELS[idx - 1];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Check if a scope covers another (is at least as broad).
|
|
172
|
+
* @param {ContextScope} container
|
|
173
|
+
* @param {ContextScope} contained
|
|
174
|
+
* @returns {boolean}
|
|
175
|
+
*/
|
|
176
|
+
export function scopeCovers(container, contained) {
|
|
177
|
+
const cIdx = SCOPE_LEVELS.indexOf(container);
|
|
178
|
+
const sIdx = SCOPE_LEVELS.indexOf(contained);
|
|
179
|
+
if (cIdx < 0 || sIdx < 0) return false;
|
|
180
|
+
return cIdx <= sIdx;
|
|
181
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Snapshot — continuation invalidation.
|
|
3
|
+
* Detects when context has become stale and needs rebuild.
|
|
4
|
+
*
|
|
5
|
+
* States: VALID | STALE | INVALID
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import crypto from "node:crypto";
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
|
|
12
|
+
const SNAPSHOT_DIR = ".wam/snapshots";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {Object} ContextSnapshot
|
|
16
|
+
* @property {string} taskId
|
|
17
|
+
* @property {string} gitRevision
|
|
18
|
+
* @property {string} relevantFilesHash
|
|
19
|
+
* @property {string} projectContextHash
|
|
20
|
+
* @property {string} taskStateHash
|
|
21
|
+
* @property {number} createdAt
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {"VALID" | "STALE" | "INVALID"} SnapshotStatus
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @typedef {Object} SnapshotCheck
|
|
30
|
+
* @property {SnapshotStatus} status
|
|
31
|
+
* @property {string} reason
|
|
32
|
+
* @property {string[]} changedSignals
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
function hashString(str = "") {
|
|
36
|
+
return crypto.createHash("sha256").update(str || "").digest("hex").slice(0, 16);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function hashFiles(filePaths, root) {
|
|
40
|
+
const parts = [];
|
|
41
|
+
for (const fp of filePaths) {
|
|
42
|
+
const full = path.join(root, fp);
|
|
43
|
+
try {
|
|
44
|
+
const content = fs.readFileSync(full, "utf-8");
|
|
45
|
+
parts.push(`${fp}:${hashString(content)}`);
|
|
46
|
+
} catch {
|
|
47
|
+
parts.push(`${fp}:missing`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return hashString(parts.join("|"));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function getGitRevision(root) {
|
|
54
|
+
try {
|
|
55
|
+
const head = fs.readFileSync(path.join(root, ".git", "HEAD"), "utf-8").trim();
|
|
56
|
+
const ref = head.replace(/^ref:\s*/, "");
|
|
57
|
+
const refPath = path.join(root, ".git", ref);
|
|
58
|
+
if (fs.existsSync(refPath)) {
|
|
59
|
+
return fs.readFileSync(refPath, "utf-8").trim().slice(0, 12);
|
|
60
|
+
}
|
|
61
|
+
return head.slice(0, 12);
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getRelevantFiles(root) {
|
|
68
|
+
const candidates = [
|
|
69
|
+
"package.json",
|
|
70
|
+
"package-lock.json",
|
|
71
|
+
"yarn.lock",
|
|
72
|
+
"pnpm-lock.yaml",
|
|
73
|
+
"tsconfig.json",
|
|
74
|
+
"nest-cli.json",
|
|
75
|
+
"angular.json",
|
|
76
|
+
"AGENTS.md",
|
|
77
|
+
".opencode/opencode.jsonc",
|
|
78
|
+
];
|
|
79
|
+
return candidates.filter((f) => fs.existsSync(path.join(root, f)));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function getProjectContextHash(root) {
|
|
83
|
+
const relevant = getRelevantFiles(root);
|
|
84
|
+
return hashFiles(relevant, root);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function getTaskStateHash(taskState) {
|
|
88
|
+
const serialized = JSON.stringify({
|
|
89
|
+
phase: taskState?.phase,
|
|
90
|
+
contractStatus: taskState?.contract?.status,
|
|
91
|
+
requirements: taskState?.requirements?.map((r) => ({
|
|
92
|
+
id: r.id,
|
|
93
|
+
status: r.status,
|
|
94
|
+
})),
|
|
95
|
+
approvedStrategy: taskState?.approvedStrategy,
|
|
96
|
+
});
|
|
97
|
+
return hashString(serialized);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function snapshotDir(root) {
|
|
101
|
+
const dir = path.join(root || process.cwd(), SNAPSHOT_DIR);
|
|
102
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
103
|
+
return dir;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function snapshotPath(taskId, root) {
|
|
107
|
+
return path.join(snapshotDir(root), `${taskId}.json`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Create a context snapshot for a task.
|
|
112
|
+
*/
|
|
113
|
+
export function createSnapshot(taskId, taskState, root) {
|
|
114
|
+
const snapshot = {
|
|
115
|
+
taskId,
|
|
116
|
+
gitRevision: getGitRevision(root),
|
|
117
|
+
relevantFilesHash: getProjectContextHash(root),
|
|
118
|
+
projectContextHash: hashString(getRelevantFiles(root).join(",")),
|
|
119
|
+
taskStateHash: getTaskStateHash(taskState),
|
|
120
|
+
createdAt: Date.now(),
|
|
121
|
+
};
|
|
122
|
+
fs.writeFileSync(snapshotPath(taskId, root), JSON.stringify(snapshot, null, 2));
|
|
123
|
+
return snapshot;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Load an existing snapshot for a task.
|
|
128
|
+
*/
|
|
129
|
+
export function loadSnapshot(taskId, root) {
|
|
130
|
+
try {
|
|
131
|
+
const raw = fs.readFileSync(snapshotPath(taskId, root), "utf-8");
|
|
132
|
+
return JSON.parse(raw);
|
|
133
|
+
} catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Check if a continuation can use the fast-path.
|
|
140
|
+
*
|
|
141
|
+
* @param {string} taskId
|
|
142
|
+
* @param {Object} currentTaskState
|
|
143
|
+
* @param {string} root
|
|
144
|
+
* @returns {SnapshotCheck}
|
|
145
|
+
*/
|
|
146
|
+
export function checkContinuation(taskId, currentTaskState, root, collector = null) {
|
|
147
|
+
const finish = (result) => {
|
|
148
|
+
if (collector) collector.record(result.status === "VALID" ? "Snapshot_hit" : "Snapshot_miss");
|
|
149
|
+
return result;
|
|
150
|
+
};
|
|
151
|
+
const previous = loadSnapshot(taskId, root);
|
|
152
|
+
if (!previous) {
|
|
153
|
+
return finish({ status: "STALE", reason: "no-previous-snapshot", changedSignals: ["no-snapshot"] });
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const changed = [];
|
|
157
|
+
|
|
158
|
+
// Check git revision
|
|
159
|
+
const currentGit = getGitRevision(root);
|
|
160
|
+
if (currentGit && previous.gitRevision && currentGit !== previous.gitRevision) {
|
|
161
|
+
changed.push("git-revision");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Check relevant files (package.json, config, etc.)
|
|
165
|
+
const currentFilesHash = getProjectContextHash(root);
|
|
166
|
+
if (currentFilesHash !== previous.relevantFilesHash) {
|
|
167
|
+
changed.push("relevant-files");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Check task state
|
|
171
|
+
const currentStateHash = getTaskStateHash(currentTaskState);
|
|
172
|
+
if (currentStateHash !== previous.taskStateHash) {
|
|
173
|
+
changed.push("task-state");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (changed.length === 0) {
|
|
177
|
+
return finish({ status: "VALID", reason: "no-changes", changedSignals: [] });
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Task state changes = INVALID (need full rebuild)
|
|
181
|
+
if (changed.includes("task-state")) {
|
|
182
|
+
return finish({ status: "INVALID", reason: "task-state-changed", changedSignals: changed });
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Other changes = STALE (need partial rebuild)
|
|
186
|
+
return finish({ status: "STALE", reason: "context-changed", changedSignals: changed });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Determine which context levels need rebuild based on changed signals.
|
|
191
|
+
*
|
|
192
|
+
* @param {string[]} changedSignals
|
|
193
|
+
* @returns {{ rebuildN1: boolean, rebuildN2: boolean, rebuildN3: boolean }}
|
|
194
|
+
*/
|
|
195
|
+
export function rebuildScope(changedSignals, collector = null) {
|
|
196
|
+
if (collector) {
|
|
197
|
+
collector.record("Reconstruction_count");
|
|
198
|
+
collector.record("Context_reconstructed");
|
|
199
|
+
}
|
|
200
|
+
const scope = { rebuildN1: false, rebuildN2: false, rebuildN3: false };
|
|
201
|
+
|
|
202
|
+
for (const signal of changedSignals) {
|
|
203
|
+
switch (signal) {
|
|
204
|
+
case "relevant-files":
|
|
205
|
+
case "git-revision":
|
|
206
|
+
// Project-level changes → rebuild N1 (project context) and N3 (skills)
|
|
207
|
+
scope.rebuildN1 = true;
|
|
208
|
+
scope.rebuildN3 = true;
|
|
209
|
+
break;
|
|
210
|
+
case "task-state":
|
|
211
|
+
// Task-level changes → rebuild everything
|
|
212
|
+
scope.rebuildN1 = true;
|
|
213
|
+
scope.rebuildN2 = true;
|
|
214
|
+
scope.rebuildN3 = true;
|
|
215
|
+
break;
|
|
216
|
+
case "no-snapshot":
|
|
217
|
+
// No previous snapshot → full rebuild
|
|
218
|
+
scope.rebuildN1 = true;
|
|
219
|
+
scope.rebuildN2 = true;
|
|
220
|
+
scope.rebuildN3 = true;
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return scope;
|
|
226
|
+
}
|