@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.
Files changed (105) hide show
  1. package/README.md +478 -148
  2. package/dist/bundled-agents/manifest.json +1 -0
  3. package/dist/commands/phase4.d.ts.map +1 -1
  4. package/dist/commands/phase4.js +4 -2
  5. package/dist/commands/phase4.js.map +1 -1
  6. package/dist/commands/phase6.d.ts.map +1 -1
  7. package/dist/commands/phase6.js +4 -2
  8. package/dist/commands/phase6.js.map +1 -1
  9. package/dist/mcp/mcp-server.js +11 -10
  10. package/dist/mcp/mcp-server.js.map +1 -1
  11. package/dist/pipeline/auto-chain.d.ts +5 -0
  12. package/dist/pipeline/auto-chain.d.ts.map +1 -1
  13. package/dist/pipeline/auto-chain.js +131 -47
  14. package/dist/pipeline/auto-chain.js.map +1 -1
  15. package/dist/pipeline/enterprise/artifact-assembler.d.ts +64 -0
  16. package/dist/pipeline/enterprise/artifact-assembler.d.ts.map +1 -0
  17. package/dist/pipeline/enterprise/artifact-assembler.js +542 -0
  18. package/dist/pipeline/enterprise/artifact-assembler.js.map +1 -0
  19. package/dist/pipeline/enterprise/artifact-renderers.d.ts +42 -0
  20. package/dist/pipeline/enterprise/artifact-renderers.d.ts.map +1 -0
  21. package/dist/pipeline/enterprise/artifact-renderers.js +513 -0
  22. package/dist/pipeline/enterprise/artifact-renderers.js.map +1 -0
  23. package/dist/pipeline/enterprise/code-resolver.d.ts +43 -0
  24. package/dist/pipeline/enterprise/code-resolver.d.ts.map +1 -0
  25. package/dist/pipeline/enterprise/code-resolver.js +219 -0
  26. package/dist/pipeline/enterprise/code-resolver.js.map +1 -0
  27. package/dist/pipeline/enterprise/decision-graph-client.d.ts +171 -0
  28. package/dist/pipeline/enterprise/decision-graph-client.d.ts.map +1 -0
  29. package/dist/pipeline/enterprise/decision-graph-client.js +222 -0
  30. package/dist/pipeline/enterprise/decision-graph-client.js.map +1 -0
  31. package/dist/pipeline/enterprise/decision-graph-memory.d.ts +104 -0
  32. package/dist/pipeline/enterprise/decision-graph-memory.d.ts.map +1 -0
  33. package/dist/pipeline/enterprise/decision-graph-memory.js +292 -0
  34. package/dist/pipeline/enterprise/decision-graph-memory.js.map +1 -0
  35. package/dist/pipeline/enterprise/decision-graph.d.ts +237 -0
  36. package/dist/pipeline/enterprise/decision-graph.d.ts.map +1 -0
  37. package/dist/pipeline/enterprise/decision-graph.js +654 -0
  38. package/dist/pipeline/enterprise/decision-graph.js.map +1 -0
  39. package/dist/pipeline/enterprise/index.d.ts +40 -0
  40. package/dist/pipeline/enterprise/index.d.ts.map +1 -0
  41. package/dist/pipeline/enterprise/index.js +43 -0
  42. package/dist/pipeline/enterprise/index.js.map +1 -0
  43. package/dist/pipeline/enterprise/pass-executor.d.ts +33 -0
  44. package/dist/pipeline/enterprise/pass-executor.d.ts.map +1 -0
  45. package/dist/pipeline/enterprise/pass-executor.js +459 -0
  46. package/dist/pipeline/enterprise/pass-executor.js.map +1 -0
  47. package/dist/pipeline/enterprise/pass-registry.d.ts +19 -0
  48. package/dist/pipeline/enterprise/pass-registry.d.ts.map +1 -0
  49. package/dist/pipeline/enterprise/pass-registry.js +243 -0
  50. package/dist/pipeline/enterprise/pass-registry.js.map +1 -0
  51. package/dist/pipeline/enterprise/pass2-simulation.d.ts +130 -0
  52. package/dist/pipeline/enterprise/pass2-simulation.d.ts.map +1 -0
  53. package/dist/pipeline/enterprise/pass2-simulation.js +691 -0
  54. package/dist/pipeline/enterprise/pass2-simulation.js.map +1 -0
  55. package/dist/pipeline/enterprise/pass4-governance.d.ts +195 -0
  56. package/dist/pipeline/enterprise/pass4-governance.d.ts.map +1 -0
  57. package/dist/pipeline/enterprise/pass4-governance.js +748 -0
  58. package/dist/pipeline/enterprise/pass4-governance.js.map +1 -0
  59. package/dist/pipeline/enterprise/pass5-decision.d.ts +90 -0
  60. package/dist/pipeline/enterprise/pass5-decision.d.ts.map +1 -0
  61. package/dist/pipeline/enterprise/pass5-decision.js +487 -0
  62. package/dist/pipeline/enterprise/pass5-decision.js.map +1 -0
  63. package/dist/pipeline/enterprise/pass7-observability.d.ts +198 -0
  64. package/dist/pipeline/enterprise/pass7-observability.d.ts.map +1 -0
  65. package/dist/pipeline/enterprise/pass7-observability.js +636 -0
  66. package/dist/pipeline/enterprise/pass7-observability.js.map +1 -0
  67. package/dist/pipeline/enterprise/pipeline-orchestrator.d.ts +29 -0
  68. package/dist/pipeline/enterprise/pipeline-orchestrator.d.ts.map +1 -0
  69. package/dist/pipeline/enterprise/pipeline-orchestrator.js +283 -0
  70. package/dist/pipeline/enterprise/pipeline-orchestrator.js.map +1 -0
  71. package/dist/pipeline/enterprise/provenance-tracker.d.ts +135 -0
  72. package/dist/pipeline/enterprise/provenance-tracker.d.ts.map +1 -0
  73. package/dist/pipeline/enterprise/provenance-tracker.js +437 -0
  74. package/dist/pipeline/enterprise/provenance-tracker.js.map +1 -0
  75. package/dist/pipeline/enterprise/trace-middleware.d.ts +37 -0
  76. package/dist/pipeline/enterprise/trace-middleware.d.ts.map +1 -0
  77. package/dist/pipeline/enterprise/trace-middleware.js +188 -0
  78. package/dist/pipeline/enterprise/trace-middleware.js.map +1 -0
  79. package/dist/pipeline/enterprise/types.d.ts +199 -0
  80. package/dist/pipeline/enterprise/types.d.ts.map +1 -0
  81. package/dist/pipeline/enterprise/types.js +30 -0
  82. package/dist/pipeline/enterprise/types.js.map +1 -0
  83. package/dist/pipeline/phase2/phases/adr-generator.d.ts.map +1 -1
  84. package/dist/pipeline/phase2/phases/adr-generator.js +56 -8
  85. package/dist/pipeline/phase2/phases/adr-generator.js.map +1 -1
  86. package/dist/pipeline/phase3/phases/test-generator.d.ts.map +1 -1
  87. package/dist/pipeline/phase3/phases/test-generator.js +53 -0
  88. package/dist/pipeline/phase3/phases/test-generator.js.map +1 -1
  89. package/dist/pipeline/phase4/phases/deployment-generator.d.ts.map +1 -1
  90. package/dist/pipeline/phase4/phases/deployment-generator.js +147 -0
  91. package/dist/pipeline/phase4/phases/deployment-generator.js.map +1 -1
  92. package/dist/pipeline/phase4-adrs/phase4-adrs-coordinator.d.ts.map +1 -1
  93. package/dist/pipeline/phase4-adrs/phase4-adrs-coordinator.js +52 -1
  94. package/dist/pipeline/phase4-adrs/phase4-adrs-coordinator.js.map +1 -1
  95. package/dist/pipeline/phase6/phases/deployment-finalizer.d.ts.map +1 -1
  96. package/dist/pipeline/phase6/phases/deployment-finalizer.js +226 -0
  97. package/dist/pipeline/phase6/phases/deployment-finalizer.js.map +1 -1
  98. package/dist/pipeline/phase6/phases/service-registrar.d.ts +1 -1
  99. package/dist/pipeline/phase6/phases/service-registrar.d.ts.map +1 -1
  100. package/dist/pipeline/phase6/phases/service-registrar.js +47 -7
  101. package/dist/pipeline/phase6/phases/service-registrar.js.map +1 -1
  102. package/dist/pipeline/swarm-orchestrator.d.ts.map +1 -1
  103. package/dist/pipeline/swarm-orchestrator.js +47 -19
  104. package/dist/pipeline/swarm-orchestrator.js.map +1 -1
  105. 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