@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/dist/graph.js CHANGED
@@ -1,12 +1,8 @@
1
- import path from 'node:path';
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