@llm-dev-ops/agentics-cli 1.5.9 → 1.5.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +478 -148
- package/dist/bundled-agents/manifest.json +1 -0
- package/dist/commands/phase4.d.ts.map +1 -1
- package/dist/commands/phase4.js +4 -2
- package/dist/commands/phase4.js.map +1 -1
- package/dist/commands/phase6.d.ts.map +1 -1
- package/dist/commands/phase6.js +4 -2
- package/dist/commands/phase6.js.map +1 -1
- package/dist/mcp/mcp-server.js +11 -10
- package/dist/mcp/mcp-server.js.map +1 -1
- package/dist/pipeline/auto-chain.d.ts +5 -0
- package/dist/pipeline/auto-chain.d.ts.map +1 -1
- package/dist/pipeline/auto-chain.js +131 -47
- package/dist/pipeline/auto-chain.js.map +1 -1
- package/dist/pipeline/enterprise/artifact-assembler.d.ts +64 -0
- package/dist/pipeline/enterprise/artifact-assembler.d.ts.map +1 -0
- package/dist/pipeline/enterprise/artifact-assembler.js +542 -0
- package/dist/pipeline/enterprise/artifact-assembler.js.map +1 -0
- package/dist/pipeline/enterprise/artifact-renderers.d.ts +42 -0
- package/dist/pipeline/enterprise/artifact-renderers.d.ts.map +1 -0
- package/dist/pipeline/enterprise/artifact-renderers.js +513 -0
- package/dist/pipeline/enterprise/artifact-renderers.js.map +1 -0
- package/dist/pipeline/enterprise/code-resolver.d.ts +43 -0
- package/dist/pipeline/enterprise/code-resolver.d.ts.map +1 -0
- package/dist/pipeline/enterprise/code-resolver.js +219 -0
- package/dist/pipeline/enterprise/code-resolver.js.map +1 -0
- package/dist/pipeline/enterprise/decision-graph-client.d.ts +171 -0
- package/dist/pipeline/enterprise/decision-graph-client.d.ts.map +1 -0
- package/dist/pipeline/enterprise/decision-graph-client.js +222 -0
- package/dist/pipeline/enterprise/decision-graph-client.js.map +1 -0
- package/dist/pipeline/enterprise/decision-graph-memory.d.ts +104 -0
- package/dist/pipeline/enterprise/decision-graph-memory.d.ts.map +1 -0
- package/dist/pipeline/enterprise/decision-graph-memory.js +292 -0
- package/dist/pipeline/enterprise/decision-graph-memory.js.map +1 -0
- package/dist/pipeline/enterprise/decision-graph.d.ts +237 -0
- package/dist/pipeline/enterprise/decision-graph.d.ts.map +1 -0
- package/dist/pipeline/enterprise/decision-graph.js +654 -0
- package/dist/pipeline/enterprise/decision-graph.js.map +1 -0
- package/dist/pipeline/enterprise/index.d.ts +40 -0
- package/dist/pipeline/enterprise/index.d.ts.map +1 -0
- package/dist/pipeline/enterprise/index.js +43 -0
- package/dist/pipeline/enterprise/index.js.map +1 -0
- package/dist/pipeline/enterprise/pass-executor.d.ts +33 -0
- package/dist/pipeline/enterprise/pass-executor.d.ts.map +1 -0
- package/dist/pipeline/enterprise/pass-executor.js +459 -0
- package/dist/pipeline/enterprise/pass-executor.js.map +1 -0
- package/dist/pipeline/enterprise/pass-registry.d.ts +19 -0
- package/dist/pipeline/enterprise/pass-registry.d.ts.map +1 -0
- package/dist/pipeline/enterprise/pass-registry.js +243 -0
- package/dist/pipeline/enterprise/pass-registry.js.map +1 -0
- package/dist/pipeline/enterprise/pass2-simulation.d.ts +130 -0
- package/dist/pipeline/enterprise/pass2-simulation.d.ts.map +1 -0
- package/dist/pipeline/enterprise/pass2-simulation.js +691 -0
- package/dist/pipeline/enterprise/pass2-simulation.js.map +1 -0
- package/dist/pipeline/enterprise/pass4-governance.d.ts +195 -0
- package/dist/pipeline/enterprise/pass4-governance.d.ts.map +1 -0
- package/dist/pipeline/enterprise/pass4-governance.js +748 -0
- package/dist/pipeline/enterprise/pass4-governance.js.map +1 -0
- package/dist/pipeline/enterprise/pass5-decision.d.ts +90 -0
- package/dist/pipeline/enterprise/pass5-decision.d.ts.map +1 -0
- package/dist/pipeline/enterprise/pass5-decision.js +487 -0
- package/dist/pipeline/enterprise/pass5-decision.js.map +1 -0
- package/dist/pipeline/enterprise/pass7-observability.d.ts +198 -0
- package/dist/pipeline/enterprise/pass7-observability.d.ts.map +1 -0
- package/dist/pipeline/enterprise/pass7-observability.js +636 -0
- package/dist/pipeline/enterprise/pass7-observability.js.map +1 -0
- package/dist/pipeline/enterprise/pipeline-orchestrator.d.ts +29 -0
- package/dist/pipeline/enterprise/pipeline-orchestrator.d.ts.map +1 -0
- package/dist/pipeline/enterprise/pipeline-orchestrator.js +283 -0
- package/dist/pipeline/enterprise/pipeline-orchestrator.js.map +1 -0
- package/dist/pipeline/enterprise/provenance-tracker.d.ts +135 -0
- package/dist/pipeline/enterprise/provenance-tracker.d.ts.map +1 -0
- package/dist/pipeline/enterprise/provenance-tracker.js +437 -0
- package/dist/pipeline/enterprise/provenance-tracker.js.map +1 -0
- package/dist/pipeline/enterprise/trace-middleware.d.ts +37 -0
- package/dist/pipeline/enterprise/trace-middleware.d.ts.map +1 -0
- package/dist/pipeline/enterprise/trace-middleware.js +188 -0
- package/dist/pipeline/enterprise/trace-middleware.js.map +1 -0
- package/dist/pipeline/enterprise/types.d.ts +199 -0
- package/dist/pipeline/enterprise/types.d.ts.map +1 -0
- package/dist/pipeline/enterprise/types.js +30 -0
- package/dist/pipeline/enterprise/types.js.map +1 -0
- package/dist/pipeline/phase2/phases/adr-generator.d.ts.map +1 -1
- package/dist/pipeline/phase2/phases/adr-generator.js +56 -8
- package/dist/pipeline/phase2/phases/adr-generator.js.map +1 -1
- package/dist/pipeline/phase3/phases/test-generator.d.ts.map +1 -1
- package/dist/pipeline/phase3/phases/test-generator.js +53 -0
- package/dist/pipeline/phase3/phases/test-generator.js.map +1 -1
- package/dist/pipeline/phase4/phases/deployment-generator.d.ts.map +1 -1
- package/dist/pipeline/phase4/phases/deployment-generator.js +147 -0
- package/dist/pipeline/phase4/phases/deployment-generator.js.map +1 -1
- package/dist/pipeline/phase4-adrs/phase4-adrs-coordinator.d.ts.map +1 -1
- package/dist/pipeline/phase4-adrs/phase4-adrs-coordinator.js +52 -1
- package/dist/pipeline/phase4-adrs/phase4-adrs-coordinator.js.map +1 -1
- package/dist/pipeline/phase6/phases/deployment-finalizer.d.ts.map +1 -1
- package/dist/pipeline/phase6/phases/deployment-finalizer.js +226 -0
- package/dist/pipeline/phase6/phases/deployment-finalizer.js.map +1 -1
- package/dist/pipeline/phase6/phases/service-registrar.d.ts +1 -1
- package/dist/pipeline/phase6/phases/service-registrar.d.ts.map +1 -1
- package/dist/pipeline/phase6/phases/service-registrar.js +47 -7
- package/dist/pipeline/phase6/phases/service-registrar.js.map +1 -1
- package/dist/pipeline/swarm-orchestrator.d.ts.map +1 -1
- package/dist/pipeline/swarm-orchestrator.js +47 -19
- package/dist/pipeline/swarm-orchestrator.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,654 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DecisionGraph — Unified Data Model (ADR-033)
|
|
3
|
+
*
|
|
4
|
+
* Canonical shared data model for every pipeline execution.
|
|
5
|
+
* A directed acyclic graph (DAG) where nodes are data artifacts
|
|
6
|
+
* and edges represent derivation relationships.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Typed node/edge CRUD with schema validation
|
|
10
|
+
* - Append-only version history (originals preserved)
|
|
11
|
+
* - Automatic edge creation from derivedFrom references
|
|
12
|
+
* - Graph lifecycle management (created → accumulated → sealed)
|
|
13
|
+
* - Compound querying with pagination
|
|
14
|
+
* - Semantic search on summary fields
|
|
15
|
+
* - JSON file persistence + AgentDB memory sync
|
|
16
|
+
* - Concurrent-safe writes via monotonic timestamps
|
|
17
|
+
* - Statistics and metrics aggregation
|
|
18
|
+
*/
|
|
19
|
+
import * as crypto from 'node:crypto';
|
|
20
|
+
import * as fs from 'node:fs';
|
|
21
|
+
import * as path from 'node:path';
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// Runtime Node Type Enum (ADR-033 specifies enum, not just type union)
|
|
24
|
+
// ============================================================================
|
|
25
|
+
/** All valid DecisionNodeType values as a runtime-accessible object. */
|
|
26
|
+
export const DECISION_NODE_TYPES = {
|
|
27
|
+
// Pass 1 — Problem Intelligence
|
|
28
|
+
PROBLEM_DEFINITION: 'problem_definition',
|
|
29
|
+
BUSINESS_CONTEXT: 'business_context',
|
|
30
|
+
TECHNICAL_SCOPE: 'technical_scope',
|
|
31
|
+
ARCHITECTURE_HYPOTHESIS: 'architecture_hypothesis',
|
|
32
|
+
EXECUTION_PLAN: 'execution_plan',
|
|
33
|
+
DECISION_LINEAGE: 'decision_lineage',
|
|
34
|
+
// Pass 2 — Simulation
|
|
35
|
+
SCENARIO: 'scenario',
|
|
36
|
+
RISK_SIGNAL: 'risk_signal',
|
|
37
|
+
RELIABILITY_REPORT: 'reliability_report',
|
|
38
|
+
// Pass 3 — Architecture
|
|
39
|
+
SYSTEM_ARCHITECTURE: 'system_architecture',
|
|
40
|
+
INTEGRATION_MAP: 'integration_map',
|
|
41
|
+
DATA_FLOW: 'data_flow',
|
|
42
|
+
SERVICE_BOUNDARY: 'service_boundary',
|
|
43
|
+
PROTOTYPE: 'prototype',
|
|
44
|
+
API_CONTRACT: 'api_contract',
|
|
45
|
+
SDK_INTERFACE: 'sdk_interface',
|
|
46
|
+
// Pass 4 — Governance
|
|
47
|
+
SECURITY_ANALYSIS: 'security_analysis',
|
|
48
|
+
COMPLIANCE_REPORT: 'compliance_report',
|
|
49
|
+
COST_MODEL: 'cost_model',
|
|
50
|
+
GOVERNANCE_REPORT: 'governance_report',
|
|
51
|
+
POLICY_ANALYSIS: 'policy_analysis',
|
|
52
|
+
// Pass 5 — Decision Artifacts
|
|
53
|
+
EXECUTIVE_MEMO: 'executive_memo',
|
|
54
|
+
EXECUTIVE_SUMMARY: 'executive_summary',
|
|
55
|
+
RISK_ASSESSMENT: 'risk_assessment',
|
|
56
|
+
IMPLEMENTATION_ROADMAP: 'implementation_roadmap',
|
|
57
|
+
ORG_IMPACT: 'org_impact',
|
|
58
|
+
STAKEHOLDER_MAP: 'stakeholder_map',
|
|
59
|
+
KPI_DEFINITION: 'kpi_definition',
|
|
60
|
+
OKR_ALIGNMENT: 'okr_alignment',
|
|
61
|
+
RAID_LOG: 'raid_log',
|
|
62
|
+
CONFIDENCE_SCORE: 'confidence_score',
|
|
63
|
+
// Pass 6 — Deployment
|
|
64
|
+
DEPLOYMENT_ENDPOINT: 'deployment_endpoint',
|
|
65
|
+
INTEGRATION_RESULT: 'integration_result',
|
|
66
|
+
// Pass 7 — Observability
|
|
67
|
+
TELEMETRY_CONFIG: 'telemetry_config',
|
|
68
|
+
HEALTH_CHECK: 'health_check',
|
|
69
|
+
LEARNED_PATTERN: 'learned_pattern',
|
|
70
|
+
// Cross-cutting
|
|
71
|
+
PROVENANCE_MANIFEST: 'provenance_manifest',
|
|
72
|
+
};
|
|
73
|
+
/** Set of all valid node type values for runtime validation. */
|
|
74
|
+
export const VALID_NODE_TYPES = new Set(Object.values(DECISION_NODE_TYPES));
|
|
75
|
+
/** All valid EdgeRelation values as a runtime-accessible object. */
|
|
76
|
+
export const EDGE_RELATIONS = {
|
|
77
|
+
DERIVED_FROM: 'derived_from',
|
|
78
|
+
INFORMS: 'informs',
|
|
79
|
+
VALIDATES: 'validates',
|
|
80
|
+
SUPERSEDES: 'supersedes',
|
|
81
|
+
AGGREGATES: 'aggregates',
|
|
82
|
+
};
|
|
83
|
+
/** Set of all valid edge relation values for runtime validation. */
|
|
84
|
+
export const VALID_EDGE_RELATIONS = new Set(Object.values(EDGE_RELATIONS));
|
|
85
|
+
// ============================================================================
|
|
86
|
+
// DecisionGraph
|
|
87
|
+
// ============================================================================
|
|
88
|
+
export class DecisionGraph {
|
|
89
|
+
nodes = new Map();
|
|
90
|
+
edges = [];
|
|
91
|
+
versionHistory = new Map();
|
|
92
|
+
executionId;
|
|
93
|
+
outputDir;
|
|
94
|
+
lifecycle;
|
|
95
|
+
writeSequence = 0;
|
|
96
|
+
constructor(executionId, outputDir) {
|
|
97
|
+
this.executionId = executionId;
|
|
98
|
+
this.outputDir = outputDir;
|
|
99
|
+
this.lifecycle = 'created';
|
|
100
|
+
}
|
|
101
|
+
// --------------------------------------------------------------------------
|
|
102
|
+
// Identity & Lifecycle
|
|
103
|
+
// --------------------------------------------------------------------------
|
|
104
|
+
/** Get the execution ID. */
|
|
105
|
+
getExecutionId() {
|
|
106
|
+
return this.executionId;
|
|
107
|
+
}
|
|
108
|
+
/** Get the current lifecycle state. */
|
|
109
|
+
getLifecycle() {
|
|
110
|
+
return this.lifecycle;
|
|
111
|
+
}
|
|
112
|
+
/** Transition to accumulated state (called after first node beyond root). */
|
|
113
|
+
markAccumulated() {
|
|
114
|
+
if (this.lifecycle === 'created') {
|
|
115
|
+
this.lifecycle = 'accumulated';
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/** Seal the graph — no further writes allowed. */
|
|
119
|
+
seal() {
|
|
120
|
+
this.lifecycle = 'sealed';
|
|
121
|
+
}
|
|
122
|
+
/** Mark as queryable — sealed + available for external queries. */
|
|
123
|
+
markQueryable() {
|
|
124
|
+
if (this.lifecycle === 'sealed') {
|
|
125
|
+
this.lifecycle = 'queryable';
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/** Check if the graph accepts writes. */
|
|
129
|
+
isWritable() {
|
|
130
|
+
return this.lifecycle === 'created' || this.lifecycle === 'accumulated';
|
|
131
|
+
}
|
|
132
|
+
// --------------------------------------------------------------------------
|
|
133
|
+
// Node ID Generation
|
|
134
|
+
// --------------------------------------------------------------------------
|
|
135
|
+
/** Generate a new unique node ID. */
|
|
136
|
+
generateNodeId() {
|
|
137
|
+
return crypto.randomUUID();
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Generate the canonical AgentDB memory key for a node.
|
|
141
|
+
* Format: decisiongraph:{execution_id}:{node_id}
|
|
142
|
+
*/
|
|
143
|
+
memoryKey(nodeId) {
|
|
144
|
+
return `decisiongraph:${this.executionId}:${nodeId}`;
|
|
145
|
+
}
|
|
146
|
+
// --------------------------------------------------------------------------
|
|
147
|
+
// Node Validation
|
|
148
|
+
// --------------------------------------------------------------------------
|
|
149
|
+
/** Validate a node against the schema. Returns errors (empty = valid). */
|
|
150
|
+
validateNode(node) {
|
|
151
|
+
const errors = [];
|
|
152
|
+
if (!node.id || typeof node.id !== 'string') {
|
|
153
|
+
errors.push({ field: 'id', message: 'Node ID is required and must be a non-empty string' });
|
|
154
|
+
}
|
|
155
|
+
if (!VALID_NODE_TYPES.has(node.type)) {
|
|
156
|
+
errors.push({ field: 'type', message: `Invalid node type "${node.type}". Must be one of: ${Array.from(VALID_NODE_TYPES).join(', ')}` });
|
|
157
|
+
}
|
|
158
|
+
if (!node.name || typeof node.name !== 'string') {
|
|
159
|
+
errors.push({ field: 'name', message: 'Node name is required and must be a non-empty string' });
|
|
160
|
+
}
|
|
161
|
+
if (node.content === null || node.content === undefined || typeof node.content !== 'object') {
|
|
162
|
+
errors.push({ field: 'content', message: 'Content is required and must be an object' });
|
|
163
|
+
}
|
|
164
|
+
if (typeof node.summary !== 'string') {
|
|
165
|
+
errors.push({ field: 'summary', message: 'Summary is required and must be a string' });
|
|
166
|
+
}
|
|
167
|
+
// producedBy is MANDATORY per ADR-033
|
|
168
|
+
if (!node.producedBy || !node.producedBy.domain || !node.producedBy.agent) {
|
|
169
|
+
errors.push({ field: 'producedBy', message: 'producedBy is required with domain and agent fields' });
|
|
170
|
+
}
|
|
171
|
+
if (typeof node.pass !== 'number' || node.pass < 1 || node.pass > 7) {
|
|
172
|
+
errors.push({ field: 'pass', message: 'Pass must be a number between 1 and 7' });
|
|
173
|
+
}
|
|
174
|
+
// derivedFrom is MANDATORY (can be empty array for root nodes)
|
|
175
|
+
if (!Array.isArray(node.derivedFrom)) {
|
|
176
|
+
errors.push({ field: 'derivedFrom', message: 'derivedFrom is required and must be an array of node IDs' });
|
|
177
|
+
}
|
|
178
|
+
if (typeof node.confidence !== 'number' || node.confidence < 0 || node.confidence > 1) {
|
|
179
|
+
errors.push({ field: 'confidence', message: 'Confidence must be a number between 0.0 and 1.0' });
|
|
180
|
+
}
|
|
181
|
+
const validStatuses = ['complete', 'partial', 'degraded', 'failed'];
|
|
182
|
+
if (!validStatuses.includes(node.status)) {
|
|
183
|
+
errors.push({ field: 'status', message: `Status must be one of: ${validStatuses.join(', ')}` });
|
|
184
|
+
}
|
|
185
|
+
if (!Array.isArray(node.tags)) {
|
|
186
|
+
errors.push({ field: 'tags', message: 'Tags must be an array of strings' });
|
|
187
|
+
}
|
|
188
|
+
if (typeof node.version !== 'number' || node.version < 1) {
|
|
189
|
+
errors.push({ field: 'version', message: 'Version must be a positive integer' });
|
|
190
|
+
}
|
|
191
|
+
if (!node.timestamp || typeof node.timestamp !== 'string') {
|
|
192
|
+
errors.push({ field: 'timestamp', message: 'Timestamp is required and must be an ISO 8601 string' });
|
|
193
|
+
}
|
|
194
|
+
return errors;
|
|
195
|
+
}
|
|
196
|
+
// --------------------------------------------------------------------------
|
|
197
|
+
// Node Operations — Write
|
|
198
|
+
// --------------------------------------------------------------------------
|
|
199
|
+
/** Add a validated node to the graph. Throws on validation failure. */
|
|
200
|
+
addNode(node) {
|
|
201
|
+
if (!this.isWritable()) {
|
|
202
|
+
throw new Error(`DecisionGraph: cannot write to graph in "${this.lifecycle}" state`);
|
|
203
|
+
}
|
|
204
|
+
if (this.nodes.has(node.id)) {
|
|
205
|
+
throw new Error(`DecisionGraph: duplicate node ID "${node.id}"`);
|
|
206
|
+
}
|
|
207
|
+
// Validate
|
|
208
|
+
const errors = this.validateNode(node);
|
|
209
|
+
if (errors.length > 0) {
|
|
210
|
+
throw new Error(`DecisionGraph: node validation failed:\n${errors.map(e => ` ${e.field}: ${e.message}`).join('\n')}`);
|
|
211
|
+
}
|
|
212
|
+
this.nodes.set(node.id, node);
|
|
213
|
+
this.writeSequence++;
|
|
214
|
+
// Store initial version in history
|
|
215
|
+
this.versionHistory.set(node.id, [{
|
|
216
|
+
version: node.version,
|
|
217
|
+
timestamp: node.timestamp,
|
|
218
|
+
content: node.content,
|
|
219
|
+
summary: node.summary,
|
|
220
|
+
status: node.status,
|
|
221
|
+
confidence: node.confidence,
|
|
222
|
+
}]);
|
|
223
|
+
// Auto-create derived_from edges from derivedFrom references
|
|
224
|
+
for (const parentId of node.derivedFrom) {
|
|
225
|
+
this.edges.push({ from: parentId, to: node.id, relation: 'derived_from' });
|
|
226
|
+
}
|
|
227
|
+
// Transition lifecycle
|
|
228
|
+
if (this.lifecycle === 'created' && this.nodes.size > 1) {
|
|
229
|
+
this.lifecycle = 'accumulated';
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Update an existing node (ADR-033: append-only history).
|
|
234
|
+
* Original content is preserved in version history.
|
|
235
|
+
* Increments version and updates timestamp.
|
|
236
|
+
*/
|
|
237
|
+
updateNode(nodeId, updates) {
|
|
238
|
+
if (!this.isWritable()) {
|
|
239
|
+
throw new Error(`DecisionGraph: cannot write to graph in "${this.lifecycle}" state`);
|
|
240
|
+
}
|
|
241
|
+
const existing = this.nodes.get(nodeId);
|
|
242
|
+
if (!existing) {
|
|
243
|
+
throw new Error(`DecisionGraph: node "${nodeId}" not found`);
|
|
244
|
+
}
|
|
245
|
+
const newVersion = existing.version + 1;
|
|
246
|
+
const newTimestamp = new Date().toISOString();
|
|
247
|
+
const updated = {
|
|
248
|
+
...existing,
|
|
249
|
+
...updates,
|
|
250
|
+
version: newVersion,
|
|
251
|
+
timestamp: newTimestamp,
|
|
252
|
+
};
|
|
253
|
+
// Validate the updated node
|
|
254
|
+
const errors = this.validateNode(updated);
|
|
255
|
+
if (errors.length > 0) {
|
|
256
|
+
throw new Error(`DecisionGraph: update validation failed:\n${errors.map(e => ` ${e.field}: ${e.message}`).join('\n')}`);
|
|
257
|
+
}
|
|
258
|
+
this.nodes.set(nodeId, updated);
|
|
259
|
+
this.writeSequence++;
|
|
260
|
+
// Append to version history (original preserved)
|
|
261
|
+
const history = this.versionHistory.get(nodeId);
|
|
262
|
+
if (history) {
|
|
263
|
+
history.push({
|
|
264
|
+
version: newVersion,
|
|
265
|
+
timestamp: newTimestamp,
|
|
266
|
+
content: updated.content,
|
|
267
|
+
summary: updated.summary,
|
|
268
|
+
status: updated.status,
|
|
269
|
+
confidence: updated.confidence,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
return updated;
|
|
273
|
+
}
|
|
274
|
+
// --------------------------------------------------------------------------
|
|
275
|
+
// Node Operations — Read
|
|
276
|
+
// --------------------------------------------------------------------------
|
|
277
|
+
/** Get a node by ID. Returns undefined if not found. */
|
|
278
|
+
getNode(nodeId) {
|
|
279
|
+
return this.nodes.get(nodeId);
|
|
280
|
+
}
|
|
281
|
+
/** Get all nodes of a specific type. */
|
|
282
|
+
getNodesByType(type) {
|
|
283
|
+
const result = [];
|
|
284
|
+
for (const node of this.nodes.values()) {
|
|
285
|
+
if (node.type === type) {
|
|
286
|
+
result.push(node);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return result;
|
|
290
|
+
}
|
|
291
|
+
/** Get all nodes from a specific pass. */
|
|
292
|
+
getNodesByPass(pass) {
|
|
293
|
+
const result = [];
|
|
294
|
+
for (const node of this.nodes.values()) {
|
|
295
|
+
if (node.pass === pass) {
|
|
296
|
+
result.push(node);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return result;
|
|
300
|
+
}
|
|
301
|
+
/** Get all nodes produced by a specific agent. */
|
|
302
|
+
getNodesByAgent(domain, agent) {
|
|
303
|
+
const result = [];
|
|
304
|
+
for (const node of this.nodes.values()) {
|
|
305
|
+
if (node.producedBy.domain === domain && node.producedBy.agent === agent) {
|
|
306
|
+
result.push(node);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return result;
|
|
310
|
+
}
|
|
311
|
+
/** Get all nodes matching a tag. */
|
|
312
|
+
getNodesByTag(tag) {
|
|
313
|
+
const result = [];
|
|
314
|
+
for (const node of this.nodes.values()) {
|
|
315
|
+
if (node.tags.includes(tag)) {
|
|
316
|
+
result.push(node);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return result;
|
|
320
|
+
}
|
|
321
|
+
/** Get total node count. */
|
|
322
|
+
get nodeCount() {
|
|
323
|
+
return this.nodes.size;
|
|
324
|
+
}
|
|
325
|
+
/** Get total edge count. */
|
|
326
|
+
get edgeCount() {
|
|
327
|
+
return this.edges.length;
|
|
328
|
+
}
|
|
329
|
+
/** Get all nodes as an array. */
|
|
330
|
+
getAllNodes() {
|
|
331
|
+
return Array.from(this.nodes.values());
|
|
332
|
+
}
|
|
333
|
+
/** Get all edges as an array. */
|
|
334
|
+
getAllEdges() {
|
|
335
|
+
return [...this.edges];
|
|
336
|
+
}
|
|
337
|
+
// --------------------------------------------------------------------------
|
|
338
|
+
// Compound Querying with Pagination (ADR-033)
|
|
339
|
+
// --------------------------------------------------------------------------
|
|
340
|
+
/**
|
|
341
|
+
* Query nodes with compound filters and optional pagination.
|
|
342
|
+
* All filter fields are ANDed together.
|
|
343
|
+
*/
|
|
344
|
+
queryNodes(filter, pagination) {
|
|
345
|
+
let results = [];
|
|
346
|
+
for (const node of this.nodes.values()) {
|
|
347
|
+
if (filter.type !== undefined && node.type !== filter.type)
|
|
348
|
+
continue;
|
|
349
|
+
if (filter.pass !== undefined && node.pass !== filter.pass)
|
|
350
|
+
continue;
|
|
351
|
+
if (filter.tag !== undefined && !node.tags.includes(filter.tag))
|
|
352
|
+
continue;
|
|
353
|
+
if (filter.status !== undefined && node.status !== filter.status)
|
|
354
|
+
continue;
|
|
355
|
+
if (filter.domain !== undefined && node.producedBy.domain !== filter.domain)
|
|
356
|
+
continue;
|
|
357
|
+
if (filter.agent !== undefined && node.producedBy.agent !== filter.agent)
|
|
358
|
+
continue;
|
|
359
|
+
if (filter.minConfidence !== undefined && node.confidence < filter.minConfidence)
|
|
360
|
+
continue;
|
|
361
|
+
results.push(node);
|
|
362
|
+
}
|
|
363
|
+
const total = results.length;
|
|
364
|
+
const offset = pagination?.offset ?? 0;
|
|
365
|
+
const limit = pagination?.limit ?? total;
|
|
366
|
+
results = results.slice(offset, offset + limit);
|
|
367
|
+
return {
|
|
368
|
+
items: results,
|
|
369
|
+
total,
|
|
370
|
+
offset,
|
|
371
|
+
limit,
|
|
372
|
+
hasMore: offset + limit < total,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
// --------------------------------------------------------------------------
|
|
376
|
+
// Semantic Search (ADR-033 — text similarity on summary fields)
|
|
377
|
+
// --------------------------------------------------------------------------
|
|
378
|
+
/**
|
|
379
|
+
* Search nodes by text similarity on the summary field.
|
|
380
|
+
* Uses simple token overlap scoring (production systems would use
|
|
381
|
+
* vector embeddings via AgentDB HNSW — this provides the interface).
|
|
382
|
+
*/
|
|
383
|
+
searchBySummary(query, topK = 10) {
|
|
384
|
+
if (!query.trim())
|
|
385
|
+
return [];
|
|
386
|
+
const queryTokens = tokenize(query);
|
|
387
|
+
if (queryTokens.size === 0)
|
|
388
|
+
return [];
|
|
389
|
+
const scored = [];
|
|
390
|
+
for (const node of this.nodes.values()) {
|
|
391
|
+
if (!node.summary)
|
|
392
|
+
continue;
|
|
393
|
+
const nodeTokens = tokenize(node.summary);
|
|
394
|
+
if (nodeTokens.size === 0)
|
|
395
|
+
continue;
|
|
396
|
+
// Jaccard similarity
|
|
397
|
+
let intersection = 0;
|
|
398
|
+
for (const t of queryTokens) {
|
|
399
|
+
if (nodeTokens.has(t))
|
|
400
|
+
intersection++;
|
|
401
|
+
}
|
|
402
|
+
const union = queryTokens.size + nodeTokens.size - intersection;
|
|
403
|
+
const score = union > 0 ? intersection / union : 0;
|
|
404
|
+
if (score > 0) {
|
|
405
|
+
scored.push({ node, score });
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
scored.sort((a, b) => b.score - a.score);
|
|
409
|
+
return scored.slice(0, topK).map(s => s.node);
|
|
410
|
+
}
|
|
411
|
+
// --------------------------------------------------------------------------
|
|
412
|
+
// Edge Operations
|
|
413
|
+
// --------------------------------------------------------------------------
|
|
414
|
+
/** Add an edge to the graph with validation. */
|
|
415
|
+
addEdge(from, to, relation) {
|
|
416
|
+
if (!VALID_EDGE_RELATIONS.has(relation)) {
|
|
417
|
+
throw new Error(`DecisionGraph: invalid edge relation "${relation}"`);
|
|
418
|
+
}
|
|
419
|
+
// Check for duplicate edges
|
|
420
|
+
const exists = this.edges.some(e => e.from === from && e.to === to && e.relation === relation);
|
|
421
|
+
if (!exists) {
|
|
422
|
+
this.edges.push({ from, to, relation });
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
/** Get all edges from a specific node. */
|
|
426
|
+
getEdgesFrom(nodeId) {
|
|
427
|
+
return this.edges.filter(e => e.from === nodeId);
|
|
428
|
+
}
|
|
429
|
+
/** Get all edges to a specific node. */
|
|
430
|
+
getEdgesTo(nodeId) {
|
|
431
|
+
return this.edges.filter(e => e.to === nodeId);
|
|
432
|
+
}
|
|
433
|
+
/** Get all edges with a specific relation. */
|
|
434
|
+
getEdgesByRelation(relation) {
|
|
435
|
+
return this.edges.filter(e => e.relation === relation);
|
|
436
|
+
}
|
|
437
|
+
/** Get the full derivation chain for a node (all ancestors, depth-first). */
|
|
438
|
+
getDerivationChain(nodeId) {
|
|
439
|
+
const visited = new Set();
|
|
440
|
+
const chain = [];
|
|
441
|
+
const walk = (id) => {
|
|
442
|
+
if (visited.has(id))
|
|
443
|
+
return;
|
|
444
|
+
visited.add(id);
|
|
445
|
+
const node = this.nodes.get(id);
|
|
446
|
+
if (!node)
|
|
447
|
+
return;
|
|
448
|
+
for (const parentId of node.derivedFrom) {
|
|
449
|
+
walk(parentId);
|
|
450
|
+
}
|
|
451
|
+
chain.push(node);
|
|
452
|
+
};
|
|
453
|
+
walk(nodeId);
|
|
454
|
+
return chain;
|
|
455
|
+
}
|
|
456
|
+
/** Get the immediate children of a node (nodes that derive from it). */
|
|
457
|
+
getChildren(nodeId) {
|
|
458
|
+
const children = [];
|
|
459
|
+
for (const node of this.nodes.values()) {
|
|
460
|
+
if (node.derivedFrom.includes(nodeId)) {
|
|
461
|
+
children.push(node);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
return children;
|
|
465
|
+
}
|
|
466
|
+
// --------------------------------------------------------------------------
|
|
467
|
+
// Version History (ADR-033 — append-only, originals preserved)
|
|
468
|
+
// --------------------------------------------------------------------------
|
|
469
|
+
/** Get the full version history for a node. */
|
|
470
|
+
getVersionHistory(nodeId) {
|
|
471
|
+
return this.versionHistory.get(nodeId) ?? [];
|
|
472
|
+
}
|
|
473
|
+
/** Get a specific version of a node's content. */
|
|
474
|
+
getNodeAtVersion(nodeId, version) {
|
|
475
|
+
const history = this.versionHistory.get(nodeId);
|
|
476
|
+
if (!history)
|
|
477
|
+
return undefined;
|
|
478
|
+
return history.find(h => h.version === version);
|
|
479
|
+
}
|
|
480
|
+
// --------------------------------------------------------------------------
|
|
481
|
+
// Convenience Node Factory
|
|
482
|
+
// --------------------------------------------------------------------------
|
|
483
|
+
/** Create and add a node with minimal boilerplate. Auto-tags with pass/type/agent. */
|
|
484
|
+
createNode(params) {
|
|
485
|
+
// Auto-generate tags from pass, type, agent, and status
|
|
486
|
+
const status = params.status ?? 'complete';
|
|
487
|
+
const autoTags = [
|
|
488
|
+
`pass:${params.pass}`,
|
|
489
|
+
`type:${params.type}`,
|
|
490
|
+
`domain:${params.producedBy.domain}`,
|
|
491
|
+
`agent:${params.producedBy.agent}`,
|
|
492
|
+
`execution:${this.executionId}`,
|
|
493
|
+
`status:${status}`,
|
|
494
|
+
];
|
|
495
|
+
const userTags = params.tags ? [...params.tags] : [];
|
|
496
|
+
// Merge without duplicates
|
|
497
|
+
const allTags = [...new Set([...autoTags, ...userTags])];
|
|
498
|
+
const node = {
|
|
499
|
+
id: this.generateNodeId(),
|
|
500
|
+
type: params.type,
|
|
501
|
+
name: params.name,
|
|
502
|
+
content: params.content,
|
|
503
|
+
summary: params.summary,
|
|
504
|
+
producedBy: params.producedBy,
|
|
505
|
+
pass: params.pass,
|
|
506
|
+
derivedFrom: params.derivedFrom ?? [],
|
|
507
|
+
timestamp: new Date().toISOString(),
|
|
508
|
+
confidence: params.confidence ?? 1.0,
|
|
509
|
+
status: params.status ?? 'complete',
|
|
510
|
+
tags: allTags,
|
|
511
|
+
version: 1,
|
|
512
|
+
};
|
|
513
|
+
this.addNode(node);
|
|
514
|
+
return node;
|
|
515
|
+
}
|
|
516
|
+
// --------------------------------------------------------------------------
|
|
517
|
+
// Statistics (ADR-033 — graph metrics)
|
|
518
|
+
// --------------------------------------------------------------------------
|
|
519
|
+
/** Compute aggregate statistics for the graph. */
|
|
520
|
+
getStatistics() {
|
|
521
|
+
const nodesByPass = {};
|
|
522
|
+
const nodesByType = {};
|
|
523
|
+
const nodesByStatus = {};
|
|
524
|
+
let totalConfidence = 0;
|
|
525
|
+
let completeCount = 0;
|
|
526
|
+
for (const node of this.nodes.values()) {
|
|
527
|
+
nodesByPass[node.pass] = (nodesByPass[node.pass] ?? 0) + 1;
|
|
528
|
+
nodesByType[node.type] = (nodesByType[node.type] ?? 0) + 1;
|
|
529
|
+
nodesByStatus[node.status] = (nodesByStatus[node.status] ?? 0) + 1;
|
|
530
|
+
totalConfidence += node.confidence;
|
|
531
|
+
if (node.status === 'complete')
|
|
532
|
+
completeCount++;
|
|
533
|
+
}
|
|
534
|
+
const nodeCount = this.nodes.size;
|
|
535
|
+
return {
|
|
536
|
+
executionId: this.executionId,
|
|
537
|
+
lifecycle: this.lifecycle,
|
|
538
|
+
nodeCount,
|
|
539
|
+
edgeCount: this.edges.length,
|
|
540
|
+
nodesByPass,
|
|
541
|
+
nodesByType,
|
|
542
|
+
nodesByStatus,
|
|
543
|
+
averageConfidence: nodeCount > 0 ? totalConfidence / nodeCount : 0,
|
|
544
|
+
completenessRatio: nodeCount > 0 ? completeCount / nodeCount : 0,
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
// --------------------------------------------------------------------------
|
|
548
|
+
// Serialization
|
|
549
|
+
// --------------------------------------------------------------------------
|
|
550
|
+
/** Serialize the graph to JSON (includes version history). */
|
|
551
|
+
toJSON() {
|
|
552
|
+
const historyObj = {};
|
|
553
|
+
for (const [nodeId, entries] of this.versionHistory.entries()) {
|
|
554
|
+
historyObj[nodeId] = entries;
|
|
555
|
+
}
|
|
556
|
+
return {
|
|
557
|
+
executionId: this.executionId,
|
|
558
|
+
lifecycle: this.lifecycle,
|
|
559
|
+
nodes: Array.from(this.nodes.values()),
|
|
560
|
+
edges: [...this.edges],
|
|
561
|
+
versionHistory: historyObj,
|
|
562
|
+
writeSequence: this.writeSequence,
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
/** Persist the graph to the output directory. */
|
|
566
|
+
persist() {
|
|
567
|
+
const graphDir = path.join(this.outputDir, 'decision-graph');
|
|
568
|
+
fs.mkdirSync(graphDir, { recursive: true });
|
|
569
|
+
const filePath = path.join(graphDir, 'decision-graph.json');
|
|
570
|
+
fs.writeFileSync(filePath, JSON.stringify(this.toJSON(), null, 2), 'utf-8');
|
|
571
|
+
return filePath;
|
|
572
|
+
}
|
|
573
|
+
/** Load a previously persisted graph (rehydrates lifecycle, history, sequence). */
|
|
574
|
+
static load(executionId, outputDir) {
|
|
575
|
+
const filePath = path.join(outputDir, 'decision-graph', 'decision-graph.json');
|
|
576
|
+
if (!fs.existsSync(filePath)) {
|
|
577
|
+
return new DecisionGraph(executionId, outputDir);
|
|
578
|
+
}
|
|
579
|
+
const raw = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
580
|
+
const graph = new DecisionGraph(raw.executionId, outputDir);
|
|
581
|
+
graph.lifecycle = raw.lifecycle ?? (raw.nodes.length > 0 ? 'accumulated' : 'created');
|
|
582
|
+
graph.writeSequence = raw.writeSequence ?? raw.nodes.length;
|
|
583
|
+
// Load nodes directly (skip validation for persisted data)
|
|
584
|
+
for (const node of raw.nodes) {
|
|
585
|
+
graph.nodes.set(node.id, node);
|
|
586
|
+
}
|
|
587
|
+
for (const edge of raw.edges) {
|
|
588
|
+
graph.edges.push(edge);
|
|
589
|
+
}
|
|
590
|
+
// Load version history
|
|
591
|
+
if (raw.versionHistory) {
|
|
592
|
+
for (const [nodeId, entries] of Object.entries(raw.versionHistory)) {
|
|
593
|
+
graph.versionHistory.set(nodeId, entries);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
else {
|
|
597
|
+
// Reconstruct minimal history from current nodes
|
|
598
|
+
for (const node of raw.nodes) {
|
|
599
|
+
graph.versionHistory.set(node.id, [{
|
|
600
|
+
version: node.version,
|
|
601
|
+
timestamp: node.timestamp,
|
|
602
|
+
content: node.content,
|
|
603
|
+
summary: node.summary,
|
|
604
|
+
status: node.status,
|
|
605
|
+
confidence: node.confidence,
|
|
606
|
+
}]);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
return graph;
|
|
610
|
+
}
|
|
611
|
+
// --------------------------------------------------------------------------
|
|
612
|
+
// Summary
|
|
613
|
+
// --------------------------------------------------------------------------
|
|
614
|
+
/** Generate a human-readable summary of the graph. */
|
|
615
|
+
summarize() {
|
|
616
|
+
const stats = this.getStatistics();
|
|
617
|
+
const lines = [
|
|
618
|
+
`DecisionGraph: ${this.executionId}`,
|
|
619
|
+
` Lifecycle: ${stats.lifecycle}`,
|
|
620
|
+
` Nodes: ${stats.nodeCount}`,
|
|
621
|
+
` Edges: ${stats.edgeCount}`,
|
|
622
|
+
` Avg Confidence: ${(stats.averageConfidence * 100).toFixed(0)}%`,
|
|
623
|
+
` Completeness: ${(stats.completenessRatio * 100).toFixed(0)}%`,
|
|
624
|
+
'',
|
|
625
|
+
];
|
|
626
|
+
lines.push(' By Pass:');
|
|
627
|
+
for (const [pass, count] of Object.entries(stats.nodesByPass).sort(([a], [b]) => Number(a) - Number(b))) {
|
|
628
|
+
lines.push(` Pass ${pass}: ${count} nodes`);
|
|
629
|
+
}
|
|
630
|
+
lines.push(' By Type:');
|
|
631
|
+
for (const [type, count] of Object.entries(stats.nodesByType).sort()) {
|
|
632
|
+
lines.push(` ${type}: ${count}`);
|
|
633
|
+
}
|
|
634
|
+
if (Object.keys(stats.nodesByStatus).length > 0) {
|
|
635
|
+
lines.push(' By Status:');
|
|
636
|
+
for (const [status, count] of Object.entries(stats.nodesByStatus).sort()) {
|
|
637
|
+
lines.push(` ${status}: ${count}`);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
return lines.join('\n');
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
// ============================================================================
|
|
644
|
+
// Helpers
|
|
645
|
+
// ============================================================================
|
|
646
|
+
/** Tokenize a string for simple text similarity. */
|
|
647
|
+
function tokenize(text) {
|
|
648
|
+
return new Set(text
|
|
649
|
+
.toLowerCase()
|
|
650
|
+
.replace(/[^a-z0-9\s]/g, ' ')
|
|
651
|
+
.split(/\s+/)
|
|
652
|
+
.filter(t => t.length > 2));
|
|
653
|
+
}
|
|
654
|
+
//# sourceMappingURL=decision-graph.js.map
|