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,451 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WAM Context Graph — Internal structured representation of task relationships.
|
|
3
|
+
*
|
|
4
|
+
* Represents tasks, outputs, evidence, decisions, constraints, and their typed
|
|
5
|
+
* relationships. Storage-independent, in-memory implementation for now.
|
|
6
|
+
*
|
|
7
|
+
* Invariant: A task depends on an output from another task, not on the entire
|
|
8
|
+
* context of that task.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// -- Types --
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {string} ContextNodeType
|
|
15
|
+
* @typedef {string} ContextEdgeType
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** @type {ContextNodeType[]} */
|
|
19
|
+
export const NODE_TYPES = [
|
|
20
|
+
"task", "subtask", "requirement", "claim", "action",
|
|
21
|
+
"observation", "evidence", "decision", "constraint", "artifact", "output"
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
/** @type {ContextEdgeType[]} */
|
|
25
|
+
export const EDGE_TYPES = [
|
|
26
|
+
"requires_completion", "requires_output", "requires_evidence",
|
|
27
|
+
"requires_decision", "depends_on_artifact", "produces",
|
|
28
|
+
"supports", "contradicts", "invalidates", "related_to"
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {Object} ContextNode
|
|
33
|
+
* @property {string} id
|
|
34
|
+
* @property {ContextNodeType} type
|
|
35
|
+
* @property {string} content
|
|
36
|
+
* @property {string} [taskId]
|
|
37
|
+
* @property {boolean} verified
|
|
38
|
+
* @property {number} createdAt
|
|
39
|
+
* @property {number} updatedAt
|
|
40
|
+
* @property {Record<string, unknown>} [metadata]
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @typedef {Object} ContextEdge
|
|
45
|
+
* @property {string} from
|
|
46
|
+
* @property {string} to
|
|
47
|
+
* @property {ContextEdgeType} type
|
|
48
|
+
* @property {number} [weight]
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
// -- Validation --
|
|
52
|
+
|
|
53
|
+
function validateNode(node) {
|
|
54
|
+
if (!node || typeof node !== "object") throw new Error("Node must be an object");
|
|
55
|
+
if (!node.id || typeof node.id !== "string") throw new Error("Node id required");
|
|
56
|
+
if (!NODE_TYPES.includes(node.type)) throw new Error(`Invalid node type: ${node.type}`);
|
|
57
|
+
if (typeof node.content !== "string") throw new Error("Node content required");
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function validateEdge(edge) {
|
|
62
|
+
if (!edge || typeof edge !== "object") throw new Error("Edge must be an object");
|
|
63
|
+
if (!edge.from || typeof edge.from !== "string") throw new Error("Edge from required");
|
|
64
|
+
if (!edge.to || typeof edge.to !== "string") throw new Error("Edge to required");
|
|
65
|
+
if (!EDGE_TYPES.includes(edge.type)) throw new Error(`Invalid edge type: ${edge.type}`);
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// -- ContextGraph --
|
|
70
|
+
|
|
71
|
+
export class ContextGraph {
|
|
72
|
+
constructor() {
|
|
73
|
+
/** @type {Map<string, ContextNode>} */
|
|
74
|
+
this.nodes = new Map();
|
|
75
|
+
/** @type {Map<string, ContextEdge[]>} */
|
|
76
|
+
this.edgesFrom = new Map();
|
|
77
|
+
/** @type {Map<string, ContextEdge[]>} */
|
|
78
|
+
this.edgesTo = new Map();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// -- Node operations --
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Add a node to the graph.
|
|
85
|
+
* @param {ContextNode} node
|
|
86
|
+
* @returns {ContextNode}
|
|
87
|
+
*/
|
|
88
|
+
addNode(node) {
|
|
89
|
+
validateNode(node);
|
|
90
|
+
const now = Date.now();
|
|
91
|
+
const existing = this.nodes.get(node.id);
|
|
92
|
+
const entry = {
|
|
93
|
+
...node,
|
|
94
|
+
createdAt: existing?.createdAt || now,
|
|
95
|
+
updatedAt: now,
|
|
96
|
+
};
|
|
97
|
+
this.nodes.set(node.id, entry);
|
|
98
|
+
return entry;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get a node by id.
|
|
103
|
+
* @param {string} id
|
|
104
|
+
* @returns {ContextNode | undefined}
|
|
105
|
+
*/
|
|
106
|
+
getNode(id) {
|
|
107
|
+
return this.nodes.get(id);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Check if a node exists.
|
|
112
|
+
* @param {string} id
|
|
113
|
+
* @returns {boolean}
|
|
114
|
+
*/
|
|
115
|
+
hasNode(id) {
|
|
116
|
+
return this.nodes.has(id);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Get all nodes, optionally filtered by type.
|
|
121
|
+
* @param {ContextNodeType} [type]
|
|
122
|
+
* @returns {ContextNode[]}
|
|
123
|
+
*/
|
|
124
|
+
getNodes(type) {
|
|
125
|
+
const all = [...this.nodes.values()];
|
|
126
|
+
return type ? all.filter((n) => n.type === type) : all;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Update a node's properties.
|
|
131
|
+
* @param {string} id
|
|
132
|
+
* @param {Partial<ContextNode>} patch
|
|
133
|
+
* @returns {ContextNode | null}
|
|
134
|
+
*/
|
|
135
|
+
updateNode(id, patch) {
|
|
136
|
+
const node = this.nodes.get(id);
|
|
137
|
+
if (!node) return null;
|
|
138
|
+
const updated = { ...node, ...patch, id: node.id, updatedAt: Date.now() };
|
|
139
|
+
this.nodes.set(id, updated);
|
|
140
|
+
return updated;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Remove a node and all its edges.
|
|
145
|
+
* @param {string} id
|
|
146
|
+
* @returns {boolean}
|
|
147
|
+
*/
|
|
148
|
+
removeNode(id) {
|
|
149
|
+
const existed = this.nodes.delete(id);
|
|
150
|
+
if (!existed) return false;
|
|
151
|
+
|
|
152
|
+
// Remove all edges involving this node
|
|
153
|
+
const outgoing = this.edgesFrom.get(id) || [];
|
|
154
|
+
for (const edge of outgoing) {
|
|
155
|
+
this._removeFromEdgesTo(edge);
|
|
156
|
+
}
|
|
157
|
+
this.edgesFrom.delete(id);
|
|
158
|
+
|
|
159
|
+
const incoming = this.edgesTo.get(id) || [];
|
|
160
|
+
for (const edge of incoming) {
|
|
161
|
+
this._removeFromEdgesFrom(edge);
|
|
162
|
+
}
|
|
163
|
+
this.edgesTo.delete(id);
|
|
164
|
+
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// -- Edge operations --
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Add an edge to the graph.
|
|
172
|
+
* @param {ContextEdge} edge
|
|
173
|
+
* @returns {ContextEdge}
|
|
174
|
+
*/
|
|
175
|
+
addEdge(edge) {
|
|
176
|
+
validateEdge(edge);
|
|
177
|
+
if (!this.nodes.has(edge.from)) throw new Error(`Source node ${edge.from} not found`);
|
|
178
|
+
if (!this.nodes.has(edge.to)) throw new Error(`Target node ${edge.to} not found`);
|
|
179
|
+
|
|
180
|
+
const entry = { ...edge, weight: edge.weight ?? 1.0 };
|
|
181
|
+
|
|
182
|
+
// Check for duplicate
|
|
183
|
+
const fromEdges = this.edgesFrom.get(edge.from) || [];
|
|
184
|
+
const exists = fromEdges.some(
|
|
185
|
+
(e) => e.to === edge.to && e.type === edge.type
|
|
186
|
+
);
|
|
187
|
+
if (exists) return entry;
|
|
188
|
+
|
|
189
|
+
fromEdges.push(entry);
|
|
190
|
+
this.edgesFrom.set(edge.from, fromEdges);
|
|
191
|
+
|
|
192
|
+
const toEdges = this.edgesTo.get(edge.to) || [];
|
|
193
|
+
toEdges.push(entry);
|
|
194
|
+
this.edgesTo.set(edge.to, toEdges);
|
|
195
|
+
|
|
196
|
+
return entry;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Get all edges from a node.
|
|
201
|
+
* @param {string} nodeId
|
|
202
|
+
* @returns {ContextEdge[]}
|
|
203
|
+
*/
|
|
204
|
+
getEdgesFrom(nodeId) {
|
|
205
|
+
return this.edgesFrom.get(nodeId) || [];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Get all edges to a node.
|
|
210
|
+
* @param {string} nodeId
|
|
211
|
+
* @returns {ContextEdge[]}
|
|
212
|
+
*/
|
|
213
|
+
getEdgesTo(nodeId) {
|
|
214
|
+
return this.edgesTo.get(nodeId) || [];
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Get edges of a specific type from a node.
|
|
219
|
+
* @param {string} nodeId
|
|
220
|
+
* @param {ContextEdgeType} type
|
|
221
|
+
* @returns {ContextEdge[]}
|
|
222
|
+
*/
|
|
223
|
+
getEdgesFromByType(nodeId, type) {
|
|
224
|
+
return this.getEdgesFrom(nodeId).filter((e) => e.type === type);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Get edges of a specific type to a node.
|
|
229
|
+
* @param {string} nodeId
|
|
230
|
+
* @param {ContextEdgeType} type
|
|
231
|
+
* @returns {ContextEdge[]}
|
|
232
|
+
*/
|
|
233
|
+
getEdgesToByType(nodeId, type) {
|
|
234
|
+
return this.getEdgesTo(nodeId).filter((e) => e.type === type);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// -- Traversal --
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Get direct dependencies of a node (nodes it requires).
|
|
241
|
+
* @param {string} nodeId
|
|
242
|
+
* @returns {ContextNode[]}
|
|
243
|
+
*/
|
|
244
|
+
getDependencies(nodeId) {
|
|
245
|
+
const depEdges = this.getEdgesFromByType(nodeId, "requires_output")
|
|
246
|
+
.concat(this.getEdgesFromByType(nodeId, "requires_completion"))
|
|
247
|
+
.concat(this.getEdgesFromByType(nodeId, "requires_evidence"))
|
|
248
|
+
.concat(this.getEdgesFromByType(nodeId, "requires_decision"))
|
|
249
|
+
.concat(this.getEdgesFromByType(nodeId, "depends_on_artifact"));
|
|
250
|
+
|
|
251
|
+
return depEdges
|
|
252
|
+
.map((e) => this.nodes.get(e.to))
|
|
253
|
+
.filter(Boolean);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Get upstream context (what this node depends on, transitively).
|
|
258
|
+
* @param {string} nodeId
|
|
259
|
+
* @param {number} [maxDepth=10]
|
|
260
|
+
* @returns {ContextNode[]}
|
|
261
|
+
*/
|
|
262
|
+
getUpstream(nodeId, maxDepth = 10) {
|
|
263
|
+
const visited = new Set();
|
|
264
|
+
const result = [];
|
|
265
|
+
|
|
266
|
+
const traverse = (id, depth) => {
|
|
267
|
+
if (depth >= maxDepth || visited.has(id)) return;
|
|
268
|
+
visited.add(id);
|
|
269
|
+
|
|
270
|
+
const deps = this.getDependencies(id);
|
|
271
|
+
for (const dep of deps) {
|
|
272
|
+
if (!visited.has(dep.id)) {
|
|
273
|
+
result.push(dep);
|
|
274
|
+
traverse(dep.id, depth + 1);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Also traverse through "produces" edges to find tasks that produce this node
|
|
279
|
+
const producers = this.getEdgesToByType(id, "produces");
|
|
280
|
+
for (const edge of producers) {
|
|
281
|
+
if (!visited.has(edge.from)) {
|
|
282
|
+
const producerNode = this.nodes.get(edge.from);
|
|
283
|
+
if (producerNode) {
|
|
284
|
+
result.push(producerNode);
|
|
285
|
+
traverse(edge.from, depth + 1);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
traverse(nodeId, 0);
|
|
292
|
+
return result;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Get downstream context (what depends on this node, transitively).
|
|
297
|
+
* @param {string} nodeId
|
|
298
|
+
* @param {number} [maxDepth=10]
|
|
299
|
+
* @returns {ContextNode[]}
|
|
300
|
+
*/
|
|
301
|
+
getDownstream(nodeId, maxDepth = 10) {
|
|
302
|
+
const visited = new Set();
|
|
303
|
+
const result = [];
|
|
304
|
+
|
|
305
|
+
const traverse = (id, depth) => {
|
|
306
|
+
if (depth > maxDepth || visited.has(id)) return;
|
|
307
|
+
visited.add(id);
|
|
308
|
+
|
|
309
|
+
const dependents = this.getEdgesTo(id)
|
|
310
|
+
.filter((e) => ["requires_output", "requires_completion", "depends_on_artifact"].includes(e.type));
|
|
311
|
+
|
|
312
|
+
for (const edge of dependents) {
|
|
313
|
+
if (!visited.has(edge.from)) {
|
|
314
|
+
const node = this.nodes.get(edge.from);
|
|
315
|
+
if (node) {
|
|
316
|
+
result.push(node);
|
|
317
|
+
traverse(edge.from, depth + 1);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
traverse(nodeId, 0);
|
|
324
|
+
return result;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Get nodes that are invalidated by a given node.
|
|
329
|
+
* @param {string} nodeId
|
|
330
|
+
* @returns {ContextNode[]}
|
|
331
|
+
*/
|
|
332
|
+
getInvalidatedBy(nodeId) {
|
|
333
|
+
return this.getEdgesFromByType(nodeId, "invalidates")
|
|
334
|
+
.map((e) => this.nodes.get(e.to))
|
|
335
|
+
.filter(Boolean);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Get nodes that contradict a given node.
|
|
340
|
+
* @param {string} nodeId
|
|
341
|
+
* @returns {ContextNode[]}
|
|
342
|
+
*/
|
|
343
|
+
getContradicting(nodeId) {
|
|
344
|
+
const fromContradictions = this.getEdgesFromByType(nodeId, "contradicts")
|
|
345
|
+
.map((e) => this.nodes.get(e.to));
|
|
346
|
+
const toContradictions = this.getEdgesToByType(nodeId, "contradicts")
|
|
347
|
+
.map((e) => this.nodes.get(e.from));
|
|
348
|
+
|
|
349
|
+
return [...fromContradictions, ...toContradictions].filter(Boolean);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// -- Validation --
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Validate graph consistency.
|
|
356
|
+
* @returns {{ valid: boolean, errors: string[] }}
|
|
357
|
+
*/
|
|
358
|
+
validate() {
|
|
359
|
+
const errors = [];
|
|
360
|
+
|
|
361
|
+
// Check all edges reference existing nodes
|
|
362
|
+
for (const [fromId, edges] of this.edgesFrom) {
|
|
363
|
+
if (!this.nodes.has(fromId)) {
|
|
364
|
+
errors.push(`Edge source ${fromId} not in nodes`);
|
|
365
|
+
}
|
|
366
|
+
for (const edge of edges) {
|
|
367
|
+
if (!this.nodes.has(edge.to)) {
|
|
368
|
+
errors.push(`Edge target ${edge.to} not in nodes`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Check for cycles in dependency edges
|
|
374
|
+
const visited = new Set();
|
|
375
|
+
const inStack = new Set();
|
|
376
|
+
|
|
377
|
+
const hasCycle = (nodeId) => {
|
|
378
|
+
if (inStack.has(nodeId)) return true;
|
|
379
|
+
if (visited.has(nodeId)) return false;
|
|
380
|
+
|
|
381
|
+
visited.add(nodeId);
|
|
382
|
+
inStack.add(nodeId);
|
|
383
|
+
|
|
384
|
+
const deps = this.getEdgesFromByType(nodeId, "requires_output")
|
|
385
|
+
.concat(this.getEdgesFromByType(nodeId, "depends_on_artifact"));
|
|
386
|
+
|
|
387
|
+
for (const edge of deps) {
|
|
388
|
+
if (hasCycle(edge.to)) return true;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
inStack.delete(nodeId);
|
|
392
|
+
return false;
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
for (const nodeId of this.nodes.keys()) {
|
|
396
|
+
if (hasCycle(nodeId)) {
|
|
397
|
+
errors.push(`Cycle detected involving node ${nodeId}`);
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return { valid: errors.length === 0, errors };
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Get graph statistics.
|
|
407
|
+
* @returns {{ nodeCount: number, edgeCount: number, nodesByType: Record<string, number>, edgesByType: Record<string, number> }}
|
|
408
|
+
*/
|
|
409
|
+
stats() {
|
|
410
|
+
const nodesByType = {};
|
|
411
|
+
for (const node of this.nodes.values()) {
|
|
412
|
+
nodesByType[node.type] = (nodesByType[node.type] || 0) + 1;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const edgesByType = {};
|
|
416
|
+
let edgeCount = 0;
|
|
417
|
+
for (const edges of this.edgesFrom.values()) {
|
|
418
|
+
edgeCount += edges.length;
|
|
419
|
+
for (const edge of edges) {
|
|
420
|
+
edgesByType[edge.type] = (edgesByType[edge.type] || 0) + 1;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return {
|
|
425
|
+
nodeCount: this.nodes.size,
|
|
426
|
+
edgeCount,
|
|
427
|
+
nodesByType,
|
|
428
|
+
edgesByType,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// -- Internal helpers --
|
|
433
|
+
|
|
434
|
+
_removeFromEdgesTo(edge) {
|
|
435
|
+
const toEdges = this.edgesTo.get(edge.to);
|
|
436
|
+
if (!toEdges) return;
|
|
437
|
+
const idx = toEdges.findIndex(
|
|
438
|
+
(e) => e.from === edge.from && e.type === edge.type
|
|
439
|
+
);
|
|
440
|
+
if (idx !== -1) toEdges.splice(idx, 1);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
_removeFromEdgesFrom(edge) {
|
|
444
|
+
const fromEdges = this.edgesFrom.get(edge.from);
|
|
445
|
+
if (!fromEdges) return;
|
|
446
|
+
const idx = fromEdges.findIndex(
|
|
447
|
+
(e) => e.to === edge.to && e.type === edge.type
|
|
448
|
+
);
|
|
449
|
+
if (idx !== -1) fromEdges.splice(idx, 1);
|
|
450
|
+
}
|
|
451
|
+
}
|