@shapeshift-labs/loom 0.1.18 → 0.1.19
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 +5 -5
- package/dist/cli.js +2 -2
- package/dist/cli.js.map +1 -1
- package/dist/graph.d.ts +1 -6
- package/dist/graph.d.ts.map +1 -1
- package/dist/graph.js +1 -604
- package/dist/graph.js.map +1 -1
- package/dist/help.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +1 -60
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/dist/graph-live.d.ts +0 -6
- package/dist/graph-live.d.ts.map +0 -1
- package/dist/graph-live.js +0 -251
- package/dist/graph-live.js.map +0 -1
package/dist/graph.js
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { abs, nowIso, pathExists, readJson, resolveRoot, writeJson } from './common.js';
|
|
1
|
+
import { abs, pathExists, readJson, resolveRoot, writeJson } from './common.js';
|
|
3
2
|
import { readLoomConfig } from './config.js';
|
|
4
|
-
import { FRONTIER_SWARM_CODEX_LIVE_RUN_GRAPH_EVENTS_ARTIFACT, materializeSwarmCodexLiveRunGraphEvents } from './graph-live.js';
|
|
5
3
|
import { FRONTIER_RUN_GRAPH_SOURCE, normalizeFrontierRunEvents, parseFrontierRunEventsInput } from './graph-frontier-run.js';
|
|
6
4
|
export const LOOM_NATIVE_RUN_GRAPH_SOURCE = 'loom-native';
|
|
7
|
-
export const FRONTIER_SWARM_CODEX_RUN_GRAPH_SOURCE = 'frontier-swarm-codex';
|
|
8
5
|
export const LOOM_RUN_GRAPH_CHUNK_TEMPLATE_KIND = 'loom.run-graph.chunk-template';
|
|
9
|
-
export { FRONTIER_SWARM_CODEX_LIVE_RUN_GRAPH_EVENTS_ARTIFACT, FRONTIER_SWARM_CODEX_LIVE_RUN_GRAPH_EVENT_KIND, materializeSwarmCodexLiveRunGraphEvents, parseSwarmCodexRunGraphInput } from './graph-live.js';
|
|
10
6
|
export { FRONTIER_RUN_GRAPH_SOURCE, normalizeFrontierRunEvents, parseFrontierRunEventsInput } from './graph-frontier-run.js';
|
|
11
7
|
export function buildRunGraphChunkTemplate(options) {
|
|
12
8
|
const nodes = uniqueStrings([
|
|
@@ -105,95 +101,6 @@ export async function writeLoomRunGraph(graph, options = {}) {
|
|
|
105
101
|
await writeJson(file, graph);
|
|
106
102
|
return file;
|
|
107
103
|
}
|
|
108
|
-
export function normalizeSwarmCodexRunGraph(input, options = {}) {
|
|
109
|
-
assertSwarmCodexRunGraph(input);
|
|
110
|
-
return normalizeSwarmCodexRunGraphArtifact(input, options);
|
|
111
|
-
}
|
|
112
|
-
export function normalizeSwarmCodexLiveRunGraphEvents(input, options = {}) {
|
|
113
|
-
const artifact = materializeSwarmCodexLiveRunGraphEvents(input, options);
|
|
114
|
-
const eventTypes = Array.from(new Set(input.map((event) => event.type).filter(Boolean)));
|
|
115
|
-
return normalizeSwarmCodexRunGraphArtifact(artifact, options, {
|
|
116
|
-
artifactKind: FRONTIER_SWARM_CODEX_LIVE_RUN_GRAPH_EVENTS_ARTIFACT,
|
|
117
|
-
eventCount: input.length,
|
|
118
|
-
eventTypes
|
|
119
|
-
}, {
|
|
120
|
-
swarmCodexLive: toJsonValue({
|
|
121
|
-
eventCount: input.length,
|
|
122
|
-
eventTypes,
|
|
123
|
-
firstGeneratedAt: input[0]?.generatedAt,
|
|
124
|
-
lastGeneratedAt: input.at(-1)?.generatedAt
|
|
125
|
-
})
|
|
126
|
-
}, input);
|
|
127
|
-
}
|
|
128
|
-
function normalizeSwarmCodexRunGraphArtifact(input, options = {}, sourceMetadata = {}, extraMetadata = {}, liveEvents = []) {
|
|
129
|
-
const root = resolveRoot(options.root);
|
|
130
|
-
const runId = options.runId ?? swarmRunGraphRunId(input);
|
|
131
|
-
const graph = normalizeSwarmCodexJobGraph(input);
|
|
132
|
-
const decisionGraph = normalizeSwarmCodexDecisionGraph(input, liveEvents);
|
|
133
|
-
const typedCounts = summarizeTypedCounts(decisionGraph);
|
|
134
|
-
const panels = createLoomRunGraphPanelRecords(decisionGraph);
|
|
135
|
-
return {
|
|
136
|
-
kind: 'loom.run-graph',
|
|
137
|
-
version: 1,
|
|
138
|
-
generatedAt: timestampToIso(input.generatedAt),
|
|
139
|
-
root,
|
|
140
|
-
runId,
|
|
141
|
-
source: FRONTIER_SWARM_CODEX_RUN_GRAPH_SOURCE,
|
|
142
|
-
sourceKind: FRONTIER_SWARM_CODEX_RUN_GRAPH_SOURCE,
|
|
143
|
-
sourceMetadata: {
|
|
144
|
-
kind: FRONTIER_SWARM_CODEX_RUN_GRAPH_SOURCE,
|
|
145
|
-
artifactKind: input.kind,
|
|
146
|
-
artifactId: input.id,
|
|
147
|
-
...(options.sourcePath ? { path: options.sourcePath } : {}),
|
|
148
|
-
runDir: input.runDir,
|
|
149
|
-
outDir: input.outDir,
|
|
150
|
-
importedAt: nowIso(),
|
|
151
|
-
...sourceMetadata
|
|
152
|
-
},
|
|
153
|
-
summary: {
|
|
154
|
-
nodes: graph.nodes.length,
|
|
155
|
-
edges: graph.edges.length,
|
|
156
|
-
roots: graph.roots.length,
|
|
157
|
-
leaves: graph.leaves.length,
|
|
158
|
-
issues: graph.issues.length,
|
|
159
|
-
...(Object.keys(typedCounts).length ? { typedCounts } : {})
|
|
160
|
-
},
|
|
161
|
-
graph,
|
|
162
|
-
decisionGraph,
|
|
163
|
-
...(panels.length ? { projections: { panels } } : {}),
|
|
164
|
-
metadata: {
|
|
165
|
-
swarmCodex: toJsonValue({
|
|
166
|
-
id: input.id,
|
|
167
|
-
summary: input.summary,
|
|
168
|
-
indexes: input.indexes,
|
|
169
|
-
nodes: input.nodes,
|
|
170
|
-
edges: input.edges
|
|
171
|
-
}),
|
|
172
|
-
...extraMetadata
|
|
173
|
-
}
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
export async function importSwarmCodexRunGraph(input, options = {}) {
|
|
177
|
-
const graph = Array.isArray(input)
|
|
178
|
-
? normalizeSwarmCodexLiveRunGraphEvents(input, options)
|
|
179
|
-
: normalizeSwarmCodexRunGraph(input, options);
|
|
180
|
-
const runId = options.runId ?? graph.runId ?? 'current';
|
|
181
|
-
const file = await writeLoomRunGraph(graph, { root: options.root, runId });
|
|
182
|
-
const artifactKind = graph.sourceMetadata?.artifactKind === FRONTIER_SWARM_CODEX_LIVE_RUN_GRAPH_EVENTS_ARTIFACT
|
|
183
|
-
? 'live run graph events'
|
|
184
|
-
: 'run graph';
|
|
185
|
-
return {
|
|
186
|
-
ok: true,
|
|
187
|
-
message: `imported frontier-swarm-codex ${artifactKind} ${runId}`,
|
|
188
|
-
path: file,
|
|
189
|
-
runId,
|
|
190
|
-
present: true,
|
|
191
|
-
source: FRONTIER_SWARM_CODEX_RUN_GRAPH_SOURCE,
|
|
192
|
-
sourceKind: FRONTIER_SWARM_CODEX_RUN_GRAPH_SOURCE,
|
|
193
|
-
sourceMetadata: graph.sourceMetadata,
|
|
194
|
-
graphSummary: graph.summary
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
104
|
export async function importFrontierRunEvents(input, options = {}) {
|
|
198
105
|
const events = typeof input === 'string' ? parseFrontierRunEventsInput(input) : input;
|
|
199
106
|
const graph = normalizeFrontierRunEvents(events, options);
|
|
@@ -231,341 +138,6 @@ function assertLoomRunGraph(value, file = 'run graph') {
|
|
|
231
138
|
if (value.decisionGraph !== undefined)
|
|
232
139
|
assertLoomDecisionGraph(value.decisionGraph, file);
|
|
233
140
|
}
|
|
234
|
-
function normalizeSwarmCodexJobGraph(input) {
|
|
235
|
-
const nodeIds = new Set();
|
|
236
|
-
const edges = [];
|
|
237
|
-
const issues = [];
|
|
238
|
-
const seenEdges = new Set();
|
|
239
|
-
for (const node of input.nodes) {
|
|
240
|
-
if (typeof node.id === 'string' && node.id)
|
|
241
|
-
nodeIds.add(node.id);
|
|
242
|
-
}
|
|
243
|
-
for (const edge of input.edges) {
|
|
244
|
-
const from = typeof edge.from === 'string' ? edge.from : '';
|
|
245
|
-
const to = typeof edge.to === 'string' ? edge.to : '';
|
|
246
|
-
if (!from || !to) {
|
|
247
|
-
issues.push({
|
|
248
|
-
code: 'invalid-edge',
|
|
249
|
-
severity: 'warning',
|
|
250
|
-
message: `swarm edge ${edge.id || '<unknown>'} is missing from/to`
|
|
251
|
-
});
|
|
252
|
-
continue;
|
|
253
|
-
}
|
|
254
|
-
if (!nodeIds.has(from)) {
|
|
255
|
-
issues.push({
|
|
256
|
-
code: 'missing-edge-node',
|
|
257
|
-
severity: 'warning',
|
|
258
|
-
message: `swarm edge ${edge.id || '<unknown>'} references missing source node ${from}`,
|
|
259
|
-
path: `/edges/${edge.id || edges.length}/from`
|
|
260
|
-
});
|
|
261
|
-
nodeIds.add(from);
|
|
262
|
-
}
|
|
263
|
-
if (!nodeIds.has(to)) {
|
|
264
|
-
issues.push({
|
|
265
|
-
code: 'missing-edge-node',
|
|
266
|
-
severity: 'warning',
|
|
267
|
-
message: `swarm edge ${edge.id || '<unknown>'} references missing target node ${to}`,
|
|
268
|
-
path: `/edges/${edge.id || edges.length}/to`
|
|
269
|
-
});
|
|
270
|
-
nodeIds.add(to);
|
|
271
|
-
}
|
|
272
|
-
const normalized = { from, to, type: normalizeSwarmCodexEdgeKind(edge.kind) };
|
|
273
|
-
const key = `${normalized.type}\0${normalized.from}\0${normalized.to}`;
|
|
274
|
-
if (seenEdges.has(key))
|
|
275
|
-
continue;
|
|
276
|
-
seenEdges.add(key);
|
|
277
|
-
edges.push(normalized);
|
|
278
|
-
}
|
|
279
|
-
const nodes = Array.from(nodeIds).sort();
|
|
280
|
-
edges.sort((left, right) => left.from.localeCompare(right.from) ||
|
|
281
|
-
left.to.localeCompare(right.to) ||
|
|
282
|
-
left.type.localeCompare(right.type));
|
|
283
|
-
const dependentsByJobId = emptyGraphIndex(nodes);
|
|
284
|
-
const dependenciesByJobId = emptyGraphIndex(nodes);
|
|
285
|
-
for (const edge of edges) {
|
|
286
|
-
dependentsByJobId[edge.from]?.push(edge.to);
|
|
287
|
-
dependenciesByJobId[edge.to]?.push(edge.from);
|
|
288
|
-
}
|
|
289
|
-
sortGraphIndex(dependentsByJobId);
|
|
290
|
-
sortGraphIndex(dependenciesByJobId);
|
|
291
|
-
return {
|
|
292
|
-
nodes,
|
|
293
|
-
edges,
|
|
294
|
-
dependentsByJobId,
|
|
295
|
-
dependenciesByJobId,
|
|
296
|
-
roots: nodes.filter((node) => dependenciesByJobId[node]?.length === 0),
|
|
297
|
-
leaves: nodes.filter((node) => dependentsByJobId[node]?.length === 0),
|
|
298
|
-
issues
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
function normalizeSwarmCodexEdgeKind(kind) {
|
|
302
|
-
return kind === 'dependsOn' ? 'depends-on' : kind;
|
|
303
|
-
}
|
|
304
|
-
function normalizeSwarmCodexDecisionGraph(input, liveEvents = []) {
|
|
305
|
-
const nodes = input.nodes
|
|
306
|
-
.filter((node) => node && typeof node.id === 'string' && node.id)
|
|
307
|
-
.map((node) => normalizeSwarmCodexDecisionNode(node))
|
|
308
|
-
.sort((left, right) => left.id.localeCompare(right.id));
|
|
309
|
-
const edges = input.edges
|
|
310
|
-
.filter((edge) => edge && typeof edge.from === 'string' && typeof edge.to === 'string' && edge.from && edge.to)
|
|
311
|
-
.map((edge) => normalizeSwarmCodexDecisionEdge(edge))
|
|
312
|
-
.sort((left, right) => left.id.localeCompare(right.id));
|
|
313
|
-
const events = liveEvents
|
|
314
|
-
.map((event, index) => normalizeSwarmCodexDecisionEvent(event, index))
|
|
315
|
-
.sort((left, right) => left.generatedAt.localeCompare(right.generatedAt) || left.id.localeCompare(right.id));
|
|
316
|
-
const records = normalizeSwarmCodexDecisionRecords(nodes);
|
|
317
|
-
const snapshots = [
|
|
318
|
-
normalizeSwarmCodexDecisionSnapshot(input, nodes, edges, events, records)
|
|
319
|
-
];
|
|
320
|
-
return {
|
|
321
|
-
kind: 'loom.decision-graph',
|
|
322
|
-
version: 1,
|
|
323
|
-
generatedAt: timestampToIso(input.generatedAt),
|
|
324
|
-
nodes,
|
|
325
|
-
edges,
|
|
326
|
-
events,
|
|
327
|
-
snapshots,
|
|
328
|
-
indexes: createDecisionGraphIndexes(nodes, edges),
|
|
329
|
-
records,
|
|
330
|
-
metadata: toJsonRecord({
|
|
331
|
-
sourceKind: input.kind,
|
|
332
|
-
sourceId: input.id,
|
|
333
|
-
runDir: input.runDir,
|
|
334
|
-
outDir: input.outDir,
|
|
335
|
-
summary: input.summary
|
|
336
|
-
})
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
function normalizeSwarmCodexDecisionNode(node) {
|
|
340
|
-
const kind = normalizeSwarmCodexDecisionNodeKind(node.kind);
|
|
341
|
-
const timestamp = typeof node.generatedAt === 'number' ? timestampToIso(node.generatedAt) : undefined;
|
|
342
|
-
const data = toJsonRecord(node.data);
|
|
343
|
-
const refs = stringRecord(node.refs);
|
|
344
|
-
return {
|
|
345
|
-
id: node.id,
|
|
346
|
-
kind,
|
|
347
|
-
...(kind !== node.kind ? { sourceKind: node.kind } : {}),
|
|
348
|
-
...(node.label ? { label: node.label } : {}),
|
|
349
|
-
...(node.taskId ? { taskId: node.taskId } : {}),
|
|
350
|
-
...(node.jobId ? { jobId: node.jobId } : {}),
|
|
351
|
-
...(node.lane ? { lane: node.lane } : {}),
|
|
352
|
-
...(node.model ? { model: node.model } : {}),
|
|
353
|
-
...(node.computeId ? { computeId: node.computeId } : {}),
|
|
354
|
-
...(node.modelTier ? { modelTier: node.modelTier } : {}),
|
|
355
|
-
...(node.bucket ? { bucket: node.bucket } : {}),
|
|
356
|
-
...(node.status ? { status: node.status } : {}),
|
|
357
|
-
...(node.outcome ? { outcome: node.outcome } : {}),
|
|
358
|
-
...(node.path ? { path: node.path } : {}),
|
|
359
|
-
...(kind === 'worker' ? { workerId: node.jobId ?? node.id } : {}),
|
|
360
|
-
...(kind === 'candidate' ? { candidateId: node.jobId ?? node.id } : {}),
|
|
361
|
-
...(timestamp ? { createdAt: timestamp, updatedAt: timestamp } : {}),
|
|
362
|
-
...(refs ? { refs } : {}),
|
|
363
|
-
...(data ? { data } : {})
|
|
364
|
-
};
|
|
365
|
-
}
|
|
366
|
-
function normalizeSwarmCodexDecisionNodeKind(kind) {
|
|
367
|
-
if (kind === 'run')
|
|
368
|
-
return 'intent';
|
|
369
|
-
if (kind === 'job')
|
|
370
|
-
return 'worker';
|
|
371
|
-
if (kind === 'panel-projection')
|
|
372
|
-
return 'panel';
|
|
373
|
-
if (kind === 'improvement-loop')
|
|
374
|
-
return 'rsi';
|
|
375
|
-
return kind;
|
|
376
|
-
}
|
|
377
|
-
function normalizeSwarmCodexDecisionEdge(edge) {
|
|
378
|
-
const kind = normalizeSwarmCodexEdgeKind(edge.kind);
|
|
379
|
-
const data = toJsonRecord(edge.data);
|
|
380
|
-
return {
|
|
381
|
-
id: edge.id || `${kind}:${edge.from}->${edge.to}`,
|
|
382
|
-
kind,
|
|
383
|
-
from: edge.from,
|
|
384
|
-
to: edge.to,
|
|
385
|
-
...(kind !== edge.kind ? { sourceKind: edge.kind } : {}),
|
|
386
|
-
...(edge.label ? { label: edge.label } : {}),
|
|
387
|
-
...(data ? { data } : {})
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
function normalizeSwarmCodexDecisionEvent(event, index) {
|
|
391
|
-
const nodes = (event.nodes ?? [])
|
|
392
|
-
.filter((node) => node && typeof node.id === 'string' && node.id)
|
|
393
|
-
.map((node) => normalizeSwarmCodexDecisionNode(node))
|
|
394
|
-
.sort((left, right) => left.id.localeCompare(right.id));
|
|
395
|
-
const edges = (event.edges ?? [])
|
|
396
|
-
.filter((edge) => edge && typeof edge.from === 'string' && typeof edge.to === 'string' && edge.from && edge.to)
|
|
397
|
-
.map((edge) => normalizeSwarmCodexDecisionEdge(edge))
|
|
398
|
-
.sort((left, right) => left.id.localeCompare(right.id));
|
|
399
|
-
const data = toJsonRecord(event.data);
|
|
400
|
-
return {
|
|
401
|
-
kind: 'loom.decision-graph.event',
|
|
402
|
-
version: 1,
|
|
403
|
-
id: decisionGraphEventId(event, index),
|
|
404
|
-
type: event.type,
|
|
405
|
-
generatedAt: timestampToIso(event.generatedAt),
|
|
406
|
-
...(event.runId ? { runId: event.runId } : {}),
|
|
407
|
-
...(event.taskId ? { taskId: event.taskId } : {}),
|
|
408
|
-
...(event.jobId ? { jobId: event.jobId } : {}),
|
|
409
|
-
...(event.lane ? { lane: event.lane } : {}),
|
|
410
|
-
...(nodes.length ? { nodeIds: nodes.map((node) => node.id), nodes } : {}),
|
|
411
|
-
...(edges.length ? { edgeIds: edges.map((edge) => edge.id), edges } : {}),
|
|
412
|
-
...(data ? { data } : {})
|
|
413
|
-
};
|
|
414
|
-
}
|
|
415
|
-
function decisionGraphEventId(event, index) {
|
|
416
|
-
const scope = event.jobId ?? event.taskId ?? event.runId ?? 'event';
|
|
417
|
-
return `${scope}:${event.type}:${index}`;
|
|
418
|
-
}
|
|
419
|
-
function normalizeSwarmCodexDecisionSnapshot(input, nodes, edges, events, records) {
|
|
420
|
-
return {
|
|
421
|
-
kind: 'loom.decision-graph.snapshot',
|
|
422
|
-
version: 1,
|
|
423
|
-
id: `snapshot:${input.id}`,
|
|
424
|
-
generatedAt: timestampToIso(input.generatedAt),
|
|
425
|
-
label: 'current',
|
|
426
|
-
nodeIds: nodes.map((node) => node.id),
|
|
427
|
-
edgeIds: edges.map((edge) => edge.id),
|
|
428
|
-
eventIds: events.map((event) => event.id),
|
|
429
|
-
summary: {
|
|
430
|
-
nodes: nodes.length,
|
|
431
|
-
edges: edges.length,
|
|
432
|
-
events: events.length,
|
|
433
|
-
records: records.length
|
|
434
|
-
},
|
|
435
|
-
data: toJsonRecord({
|
|
436
|
-
sourceKind: input.kind,
|
|
437
|
-
sourceId: input.id,
|
|
438
|
-
runDir: input.runDir,
|
|
439
|
-
outDir: input.outDir
|
|
440
|
-
})
|
|
441
|
-
};
|
|
442
|
-
}
|
|
443
|
-
function normalizeSwarmCodexDecisionRecords(nodes) {
|
|
444
|
-
const records = [];
|
|
445
|
-
for (const node of nodes) {
|
|
446
|
-
if (node.kind === 'candidate') {
|
|
447
|
-
records.push({
|
|
448
|
-
kind: 'loom.decision-graph.merge-candidate',
|
|
449
|
-
id: `record:merge-candidate:${node.id}`,
|
|
450
|
-
nodeId: node.id,
|
|
451
|
-
taskId: node.taskId,
|
|
452
|
-
jobId: node.jobId,
|
|
453
|
-
lane: node.lane,
|
|
454
|
-
label: node.label,
|
|
455
|
-
status: node.status,
|
|
456
|
-
createdAt: node.createdAt,
|
|
457
|
-
updatedAt: node.updatedAt,
|
|
458
|
-
candidateId: node.candidateId ?? node.id,
|
|
459
|
-
sourceNodeId: node.id,
|
|
460
|
-
admissionStatus: admissionStatusFromNode(node),
|
|
461
|
-
admissionReasonCodes: admissionReasonsFromNode(node),
|
|
462
|
-
disposition: node.outcome,
|
|
463
|
-
data: node.data
|
|
464
|
-
});
|
|
465
|
-
}
|
|
466
|
-
else if (node.kind === 'evidence') {
|
|
467
|
-
records.push({
|
|
468
|
-
kind: 'loom.decision-graph.evidence',
|
|
469
|
-
id: `record:evidence:${node.id}`,
|
|
470
|
-
nodeId: node.id,
|
|
471
|
-
taskId: node.taskId,
|
|
472
|
-
jobId: node.jobId,
|
|
473
|
-
lane: node.lane,
|
|
474
|
-
label: node.label,
|
|
475
|
-
status: node.status,
|
|
476
|
-
createdAt: node.createdAt,
|
|
477
|
-
updatedAt: node.updatedAt,
|
|
478
|
-
evidenceKind: evidenceKindFromNode(node),
|
|
479
|
-
path: node.path,
|
|
480
|
-
producerNodeId: node.id,
|
|
481
|
-
data: node.data
|
|
482
|
-
});
|
|
483
|
-
}
|
|
484
|
-
else if (node.kind === 'gate') {
|
|
485
|
-
records.push({
|
|
486
|
-
kind: 'loom.decision-graph.gate',
|
|
487
|
-
id: `record:gate:${node.id}`,
|
|
488
|
-
nodeId: node.id,
|
|
489
|
-
taskId: node.taskId,
|
|
490
|
-
jobId: node.jobId,
|
|
491
|
-
lane: node.lane,
|
|
492
|
-
label: node.label,
|
|
493
|
-
status: node.status,
|
|
494
|
-
createdAt: node.createdAt,
|
|
495
|
-
updatedAt: node.updatedAt,
|
|
496
|
-
gateId: node.id,
|
|
497
|
-
data: node.data
|
|
498
|
-
});
|
|
499
|
-
}
|
|
500
|
-
else if (node.kind === 'replay') {
|
|
501
|
-
records.push({
|
|
502
|
-
kind: 'loom.decision-graph.replay',
|
|
503
|
-
id: `record:replay:${node.id}`,
|
|
504
|
-
nodeId: node.id,
|
|
505
|
-
taskId: node.taskId,
|
|
506
|
-
jobId: node.jobId,
|
|
507
|
-
lane: node.lane,
|
|
508
|
-
label: node.label,
|
|
509
|
-
status: node.status,
|
|
510
|
-
createdAt: node.createdAt,
|
|
511
|
-
updatedAt: node.updatedAt,
|
|
512
|
-
result: node.outcome,
|
|
513
|
-
data: node.data
|
|
514
|
-
});
|
|
515
|
-
}
|
|
516
|
-
else if (node.kind === 'rsi') {
|
|
517
|
-
records.push({
|
|
518
|
-
kind: 'loom.decision-graph.improvement-loop',
|
|
519
|
-
id: `record:improvement-loop:${node.id}`,
|
|
520
|
-
nodeId: node.id,
|
|
521
|
-
taskId: node.taskId,
|
|
522
|
-
jobId: node.jobId,
|
|
523
|
-
lane: node.lane,
|
|
524
|
-
label: node.label,
|
|
525
|
-
status: node.status,
|
|
526
|
-
createdAt: node.createdAt,
|
|
527
|
-
updatedAt: node.updatedAt,
|
|
528
|
-
loopId: node.id,
|
|
529
|
-
data: node.data
|
|
530
|
-
});
|
|
531
|
-
}
|
|
532
|
-
else if (node.kind === 'semantic-change') {
|
|
533
|
-
records.push({
|
|
534
|
-
kind: 'loom.decision-graph.semantic-change',
|
|
535
|
-
id: `record:semantic-change:${node.id}`,
|
|
536
|
-
nodeId: node.id,
|
|
537
|
-
taskId: node.taskId,
|
|
538
|
-
jobId: node.jobId,
|
|
539
|
-
lane: node.lane,
|
|
540
|
-
label: node.label,
|
|
541
|
-
status: node.status,
|
|
542
|
-
createdAt: node.createdAt,
|
|
543
|
-
updatedAt: node.updatedAt,
|
|
544
|
-
changeId: node.id,
|
|
545
|
-
conflictReason: stringFromData(node.data, 'conflictReason'),
|
|
546
|
-
data: node.data
|
|
547
|
-
});
|
|
548
|
-
}
|
|
549
|
-
else if (node.kind === 'tournament') {
|
|
550
|
-
records.push({
|
|
551
|
-
kind: 'loom.decision-graph.tournament',
|
|
552
|
-
id: `record:tournament:${node.id}`,
|
|
553
|
-
nodeId: node.id,
|
|
554
|
-
taskId: node.taskId,
|
|
555
|
-
jobId: node.jobId,
|
|
556
|
-
lane: node.lane,
|
|
557
|
-
label: node.label,
|
|
558
|
-
status: node.status,
|
|
559
|
-
createdAt: node.createdAt,
|
|
560
|
-
updatedAt: node.updatedAt,
|
|
561
|
-
candidateIds: stringArrayFromData(node.data, 'candidateIds'),
|
|
562
|
-
winnerCandidateId: stringFromData(node.data, 'winnerCandidateId'),
|
|
563
|
-
data: node.data
|
|
564
|
-
});
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
return records;
|
|
568
|
-
}
|
|
569
141
|
export function createLoomRunGraphPanelRecords(graph) {
|
|
570
142
|
const kinds = ['intent', 'decomposition', 'tournament', 'performance', 'evidence', 'merge', 'rsi'];
|
|
571
143
|
return kinds.map((panelKind) => ({
|
|
@@ -594,75 +166,6 @@ function panelSourceEdgeIds(graph, panelKind) {
|
|
|
594
166
|
const nodes = new Set(panelSourceNodeIds(graph, panelKind));
|
|
595
167
|
return graph.edges.filter((edge) => nodes.has(edge.from) || nodes.has(edge.to)).map((edge) => edge.id);
|
|
596
168
|
}
|
|
597
|
-
function summarizeTypedCounts(graph) {
|
|
598
|
-
const out = {};
|
|
599
|
-
for (const node of graph.nodes) {
|
|
600
|
-
if (node.kind === 'intent')
|
|
601
|
-
out.intents = (out.intents ?? 0) + 1;
|
|
602
|
-
else if (node.kind === 'task')
|
|
603
|
-
out.tasks = (out.tasks ?? 0) + 1;
|
|
604
|
-
else if (node.kind === 'worker')
|
|
605
|
-
out.workers = (out.workers ?? 0) + 1;
|
|
606
|
-
else if (node.kind === 'candidate')
|
|
607
|
-
out.candidates = (out.candidates ?? 0) + 1;
|
|
608
|
-
else if (node.kind === 'evidence')
|
|
609
|
-
out.evidence = (out.evidence ?? 0) + 1;
|
|
610
|
-
else if (node.kind === 'gate')
|
|
611
|
-
out.gates = (out.gates ?? 0) + 1;
|
|
612
|
-
else if (node.kind === 'decision')
|
|
613
|
-
out.decisions = (out.decisions ?? 0) + 1;
|
|
614
|
-
else if (node.kind === 'merge')
|
|
615
|
-
out.merges = (out.merges ?? 0) + 1;
|
|
616
|
-
else if (node.kind === 'replay')
|
|
617
|
-
out.replay = (out.replay ?? 0) + 1;
|
|
618
|
-
else if (node.kind === 'panel')
|
|
619
|
-
out.panels = (out.panels ?? 0) + 1;
|
|
620
|
-
else if (node.kind === 'tournament')
|
|
621
|
-
out.tournaments = (out.tournaments ?? 0) + 1;
|
|
622
|
-
else if (node.kind === 'rsi')
|
|
623
|
-
out.rsiLoops = (out.rsiLoops ?? 0) + 1;
|
|
624
|
-
else if (node.kind === 'semantic-change')
|
|
625
|
-
out.semanticChanges = (out.semanticChanges ?? 0) + 1;
|
|
626
|
-
}
|
|
627
|
-
for (const record of graph.records ?? []) {
|
|
628
|
-
if (record.kind === 'loom.decision-graph.panel-projection')
|
|
629
|
-
out.panels = Math.max(out.panels ?? 0, 1);
|
|
630
|
-
}
|
|
631
|
-
return out;
|
|
632
|
-
}
|
|
633
|
-
function createDecisionGraphIndexes(nodes, edges) {
|
|
634
|
-
const indexes = {
|
|
635
|
-
byNodeKind: {},
|
|
636
|
-
byEdgeKind: {},
|
|
637
|
-
byTaskId: {},
|
|
638
|
-
byJobId: {},
|
|
639
|
-
byLane: {}
|
|
640
|
-
};
|
|
641
|
-
for (const node of nodes) {
|
|
642
|
-
addIndexValue(indexes.byNodeKind, node.kind, node.id);
|
|
643
|
-
if (node.taskId)
|
|
644
|
-
addIndexValue(indexes.byTaskId, node.taskId, node.id);
|
|
645
|
-
if (node.jobId)
|
|
646
|
-
addIndexValue(indexes.byJobId, node.jobId, node.id);
|
|
647
|
-
if (node.lane)
|
|
648
|
-
addIndexValue(indexes.byLane, node.lane, node.id);
|
|
649
|
-
}
|
|
650
|
-
for (const edge of edges) {
|
|
651
|
-
addIndexValue(indexes.byEdgeKind, edge.kind, edge.id);
|
|
652
|
-
}
|
|
653
|
-
sortGraphIndex(indexes.byNodeKind);
|
|
654
|
-
sortGraphIndex(indexes.byEdgeKind);
|
|
655
|
-
sortGraphIndex(indexes.byTaskId);
|
|
656
|
-
sortGraphIndex(indexes.byJobId);
|
|
657
|
-
sortGraphIndex(indexes.byLane);
|
|
658
|
-
return indexes;
|
|
659
|
-
}
|
|
660
|
-
function addIndexValue(index, key, value) {
|
|
661
|
-
const values = index[key] ?? [];
|
|
662
|
-
if (!values.includes(value))
|
|
663
|
-
values.push(value);
|
|
664
|
-
index[key] = values;
|
|
665
|
-
}
|
|
666
169
|
function assertLoomDecisionGraph(value, file) {
|
|
667
170
|
if (!value || typeof value !== 'object')
|
|
668
171
|
throw new Error(`invalid ${file}: expected decisionGraph object`);
|
|
@@ -680,77 +183,6 @@ function assertLoomDecisionGraph(value, file) {
|
|
|
680
183
|
if (!Array.isArray(value.snapshots))
|
|
681
184
|
throw new Error(`invalid ${file}: expected decisionGraph snapshots array`);
|
|
682
185
|
}
|
|
683
|
-
function emptyGraphIndex(nodes) {
|
|
684
|
-
const out = {};
|
|
685
|
-
for (const node of nodes)
|
|
686
|
-
out[node] = [];
|
|
687
|
-
return out;
|
|
688
|
-
}
|
|
689
|
-
function sortGraphIndex(index) {
|
|
690
|
-
for (const values of Object.values(index)) {
|
|
691
|
-
values.sort();
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
function swarmRunGraphRunId(input) {
|
|
695
|
-
const basename = path.basename(input.runDir || '');
|
|
696
|
-
if (basename && basename !== path.sep)
|
|
697
|
-
return basename;
|
|
698
|
-
const idPart = input.id.split(':').filter(Boolean).at(-1);
|
|
699
|
-
return idPart || 'current';
|
|
700
|
-
}
|
|
701
|
-
function timestampToIso(value) {
|
|
702
|
-
const date = new Date(value);
|
|
703
|
-
return Number.isFinite(date.getTime()) ? date.toISOString() : nowIso();
|
|
704
|
-
}
|
|
705
|
-
function assertSwarmCodexRunGraph(value) {
|
|
706
|
-
if (!value || typeof value !== 'object')
|
|
707
|
-
throw new Error('invalid swarm run graph: expected object');
|
|
708
|
-
if (value.kind !== 'frontier.swarm-codex.run-graph') {
|
|
709
|
-
throw new Error('invalid swarm run graph: expected kind "frontier.swarm-codex.run-graph"');
|
|
710
|
-
}
|
|
711
|
-
if (value.version !== 1)
|
|
712
|
-
throw new Error('invalid swarm run graph: expected version 1');
|
|
713
|
-
if (!Array.isArray(value.nodes))
|
|
714
|
-
throw new Error('invalid swarm run graph: expected nodes array');
|
|
715
|
-
if (!Array.isArray(value.edges))
|
|
716
|
-
throw new Error('invalid swarm run graph: expected edges array');
|
|
717
|
-
}
|
|
718
|
-
function toJsonValue(value) {
|
|
719
|
-
if (value === null)
|
|
720
|
-
return null;
|
|
721
|
-
if (typeof value === 'boolean' || typeof value === 'string')
|
|
722
|
-
return value;
|
|
723
|
-
if (typeof value === 'number')
|
|
724
|
-
return Number.isFinite(value) ? value : null;
|
|
725
|
-
if (Array.isArray(value))
|
|
726
|
-
return value.map((item) => toJsonValue(item));
|
|
727
|
-
if (typeof value === 'object') {
|
|
728
|
-
const out = {};
|
|
729
|
-
for (const [key, item] of Object.entries(value)) {
|
|
730
|
-
if (item === undefined || typeof item === 'function' || typeof item === 'symbol')
|
|
731
|
-
continue;
|
|
732
|
-
out[key] = toJsonValue(item);
|
|
733
|
-
}
|
|
734
|
-
return out;
|
|
735
|
-
}
|
|
736
|
-
return String(value);
|
|
737
|
-
}
|
|
738
|
-
function toJsonRecord(value) {
|
|
739
|
-
const json = toJsonValue(value);
|
|
740
|
-
if (!json || typeof json !== 'object' || Array.isArray(json))
|
|
741
|
-
return undefined;
|
|
742
|
-
return Object.keys(json).length > 0 ? json : undefined;
|
|
743
|
-
}
|
|
744
|
-
function stringRecord(value) {
|
|
745
|
-
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
746
|
-
return undefined;
|
|
747
|
-
const out = {};
|
|
748
|
-
for (const [key, item] of Object.entries(value)) {
|
|
749
|
-
if (typeof item === 'string')
|
|
750
|
-
out[key] = item;
|
|
751
|
-
}
|
|
752
|
-
return Object.keys(out).length > 0 ? out : undefined;
|
|
753
|
-
}
|
|
754
186
|
function uniqueStrings(values) {
|
|
755
187
|
return Array.from(new Set(values.filter((value) => typeof value === 'string' && value.length > 0))).sort();
|
|
756
188
|
}
|
|
@@ -796,39 +228,4 @@ function stableChunkId(kind, nodes, edges) {
|
|
|
796
228
|
const edgePart = edges.map((edge) => `${edge.type}:${edge.from}->${edge.to}`).join('|');
|
|
797
229
|
return `chunk:${kind}:${[...nodes].join(',')}:${edgePart}`;
|
|
798
230
|
}
|
|
799
|
-
function admissionStatusFromNode(node) {
|
|
800
|
-
const value = stringFromData(node.data, 'admissionStatus') ?? stringFromData(node.data, 'mergeReadiness');
|
|
801
|
-
if (value === 'safe' || value === 'safe-with-losses' || value === 'review-required' || value === 'blocked') {
|
|
802
|
-
return value;
|
|
803
|
-
}
|
|
804
|
-
return undefined;
|
|
805
|
-
}
|
|
806
|
-
function admissionReasonsFromNode(node) {
|
|
807
|
-
const values = stringArrayFromData(node.data, 'admissionReasonCodes') ?? stringArrayFromData(node.data, 'reasonCodes');
|
|
808
|
-
const allowed = new Set([
|
|
809
|
-
'missing-sidecar',
|
|
810
|
-
'empty-sidecar',
|
|
811
|
-
'stale-source-hash',
|
|
812
|
-
'symbol-conflict',
|
|
813
|
-
'effect-conflict',
|
|
814
|
-
'lossy-import',
|
|
815
|
-
'tests-missing'
|
|
816
|
-
]);
|
|
817
|
-
const out = (values ?? []).filter((value) => allowed.has(value));
|
|
818
|
-
return out.length ? out : undefined;
|
|
819
|
-
}
|
|
820
|
-
function evidenceKindFromNode(node) {
|
|
821
|
-
return stringFromData(node.data, 'evidenceKind');
|
|
822
|
-
}
|
|
823
|
-
function stringFromData(data, key) {
|
|
824
|
-
const value = data?.[key];
|
|
825
|
-
return typeof value === 'string' && value ? value : undefined;
|
|
826
|
-
}
|
|
827
|
-
function stringArrayFromData(data, key) {
|
|
828
|
-
const value = data?.[key];
|
|
829
|
-
if (!Array.isArray(value))
|
|
830
|
-
return undefined;
|
|
831
|
-
const out = value.filter((item) => typeof item === 'string' && item.length > 0);
|
|
832
|
-
return out.length ? out : undefined;
|
|
833
|
-
}
|
|
834
231
|
//# sourceMappingURL=graph.js.map
|