@shapeshift-labs/frontier-swarm-codex 0.5.121 → 0.5.122

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.
@@ -1,799 +0,0 @@
1
- import fs from 'node:fs/promises';
2
- import path from 'node:path';
3
- import { stableCodexRunGraphPart } from './run-graph-utils.js';
4
- export const FRONTIER_SWARM_CODEX_LIVE_RUN_GRAPH_EVENTS_FILE = 'live-run-graph-events.jsonl';
5
- const liveRunGraphWriteQueues = new Map();
6
- export function resolveCodexLiveRunGraphEventsPath(input) {
7
- if (input.liveRunGraphEventsPath === false)
8
- return undefined;
9
- const base = input.cwd ?? process.cwd();
10
- return path.resolve(base, input.liveRunGraphEventsPath ?? path.join(input.outDir, FRONTIER_SWARM_CODEX_LIVE_RUN_GRAPH_EVENTS_FILE));
11
- }
12
- export async function initCodexLiveRunGraphEvents(file) {
13
- if (!file)
14
- return;
15
- const absolute = path.resolve(file);
16
- await fs.mkdir(path.dirname(absolute), { recursive: true });
17
- await writeTextFileAtomic(absolute, '');
18
- }
19
- export function createCodexLiveRunGraphDashboardMetadata(input) {
20
- if (!input.liveRunGraphEventsPath) {
21
- return {
22
- artifactPaths: {},
23
- runSource: { mode: 'disabled' }
24
- };
25
- }
26
- return {
27
- artifactPaths: { liveRunGraphEvents: input.liveRunGraphEventsPath },
28
- runSource: {
29
- mode: 'live-run-graph-events',
30
- format: 'jsonl',
31
- liveRunGraphEventsPath: input.liveRunGraphEventsPath
32
- }
33
- };
34
- }
35
- export async function appendCodexLiveRunGraphEvent(file, event) {
36
- if (!file)
37
- return;
38
- const absolute = path.resolve(file);
39
- const previous = liveRunGraphWriteQueues.get(absolute) ?? Promise.resolve();
40
- let next;
41
- next = previous
42
- .catch(() => { })
43
- .then(async () => {
44
- await fs.mkdir(path.dirname(absolute), { recursive: true });
45
- await fs.appendFile(absolute, JSON.stringify(event) + '\n');
46
- })
47
- .finally(() => {
48
- if (liveRunGraphWriteQueues.get(absolute) === next)
49
- liveRunGraphWriteQueues.delete(absolute);
50
- });
51
- liveRunGraphWriteQueues.set(absolute, next);
52
- return next;
53
- }
54
- export function createCodexLiveRunStartedEvent(input) {
55
- const generatedAt = input.generatedAt ?? Date.now();
56
- const runId = runGraphRunNodeId(input.runId, input.outDir);
57
- return {
58
- kind: 'frontier.swarm-codex.live-run-graph-event',
59
- version: 1,
60
- type: 'run.started',
61
- runId: input.runId,
62
- generatedAt,
63
- nodes: [{
64
- id: runId,
65
- kind: 'run',
66
- label: input.runId,
67
- path: input.outDir,
68
- status: 'running',
69
- generatedAt,
70
- data: { jobCount: input.jobCount }
71
- }],
72
- data: { outDir: input.outDir, jobCount: input.jobCount }
73
- };
74
- }
75
- export function createCodexLiveRunFinishedEvent(input) {
76
- const generatedAt = input.generatedAt ?? Date.now();
77
- const runId = runGraphRunNodeId(input.runId, input.outDir);
78
- return {
79
- kind: 'frontier.swarm-codex.live-run-graph-event',
80
- version: 1,
81
- type: 'run.finished',
82
- runId: input.runId,
83
- generatedAt,
84
- nodes: [{
85
- id: runId,
86
- kind: 'run',
87
- label: input.runId,
88
- path: input.outDir,
89
- status: input.ok ? 'completed' : 'failed',
90
- outcome: input.ok ? 'ok' : 'failed',
91
- generatedAt,
92
- data: { ok: input.ok, summary: input.summary }
93
- }],
94
- data: { ok: input.ok, summary: input.summary }
95
- };
96
- }
97
- export function createCodexLiveJobStartedEvent(input) {
98
- const generatedAt = input.generatedAt ?? Date.now();
99
- const { nodes, edges } = jobGraphFrame(input.job, {
100
- runId: input.runId,
101
- outDir: input.outDir,
102
- status: 'running',
103
- generatedAt
104
- });
105
- return {
106
- kind: 'frontier.swarm-codex.live-run-graph-event',
107
- version: 1,
108
- type: 'job.started',
109
- runId: input.runId,
110
- jobId: input.job.id,
111
- taskId: input.job.taskId,
112
- lane: input.job.lane,
113
- generatedAt,
114
- nodes,
115
- edges,
116
- data: input.data
117
- };
118
- }
119
- export function createCodexLiveJobResultEvents(input) {
120
- const generatedAt = input.generatedAt ?? input.result.finishedAt ?? Date.now();
121
- const events = [];
122
- const jobNodeId = runGraphJobNodeId(input.job.id);
123
- const candidateNodeId = `candidate:${input.job.id}`;
124
- const evidencePaths = uniqueStrings([
125
- ...(input.result.evidencePaths ?? []),
126
- ...(input.mergeBundle.evidencePaths ?? [])
127
- ]);
128
- if (evidencePaths.length > 0) {
129
- const nodes = evidencePaths.map((evidencePath) => ({
130
- id: runGraphEvidenceNodeId(input.job.id, evidencePath),
131
- kind: 'evidence',
132
- label: path.basename(evidencePath),
133
- jobId: input.job.id,
134
- taskId: input.job.taskId,
135
- lane: input.job.lane,
136
- path: evidencePath,
137
- generatedAt
138
- }));
139
- events.push(liveEvent(input, {
140
- type: 'evidence.discovered',
141
- generatedAt,
142
- nodes,
143
- edges: nodes.map((node) => ({
144
- id: `produces:${jobNodeId}->${node.id}`,
145
- kind: 'produces',
146
- from: jobNodeId,
147
- to: node.id
148
- })),
149
- data: { evidenceCount: evidencePaths.length, evidencePaths }
150
- }));
151
- }
152
- const gates = [
153
- ...input.mergeBundle.commandsPassed.map((command) => ({ command, status: 'passed' })),
154
- ...input.mergeBundle.commandsFailed.map((command) => ({ command, status: 'failed' }))
155
- ];
156
- for (const gate of gates) {
157
- const label = gate.command.name ?? gate.command.commandLine ?? gate.command.command.join(' ') ?? 'command';
158
- const gateNodeId = `gate:${input.job.id}:${stableCodexRunGraphPart(label)}`;
159
- events.push(liveEvent(input, {
160
- type: 'gate.result',
161
- generatedAt,
162
- nodes: [{
163
- id: gateNodeId,
164
- kind: 'gate',
165
- label,
166
- jobId: input.job.id,
167
- taskId: input.job.taskId,
168
- lane: input.job.lane,
169
- status: gate.status,
170
- generatedAt,
171
- data: { command: gate.command }
172
- }],
173
- edges: [{
174
- id: `verifies:${gateNodeId}->${jobNodeId}`,
175
- kind: 'verifies',
176
- from: gateNodeId,
177
- to: jobNodeId,
178
- label: gate.status
179
- }],
180
- data: { status: gate.status, command: gate.command }
181
- }));
182
- }
183
- const terminalNodeId = `decision:terminal:${input.job.id}`;
184
- events.push(liveEvent(input, {
185
- type: 'terminal.outcome',
186
- generatedAt,
187
- nodes: [{
188
- id: terminalNodeId,
189
- kind: 'decision',
190
- label: input.result.status ?? input.mergeBundle.status,
191
- jobId: input.job.id,
192
- taskId: input.job.taskId,
193
- lane: input.job.lane,
194
- status: input.result.status ?? input.mergeBundle.status,
195
- outcome: input.result.mergeDisposition ?? input.mergeBundle.disposition,
196
- generatedAt,
197
- data: {
198
- exitCode: input.result.exitCode,
199
- signal: input.result.signal,
200
- mergeReadiness: input.result.mergeReadiness ?? input.mergeBundle.mergeReadiness,
201
- changedPathCount: input.result.changedPaths?.length ?? input.mergeBundle.changedPaths.length,
202
- ownershipViolationCount: input.result.ownershipViolations?.length ?? input.mergeBundle.ownershipViolations.length,
203
- error: errorString(input.result.error)
204
- }
205
- }],
206
- edges: [{
207
- id: `decides:${terminalNodeId}->${jobNodeId}`,
208
- kind: 'decides',
209
- from: terminalNodeId,
210
- to: jobNodeId,
211
- label: input.result.status ?? input.mergeBundle.status
212
- }]
213
- }));
214
- const frame = jobGraphFrame(input.job, {
215
- runId: input.runId,
216
- outDir: input.outDir,
217
- status: input.result.status ?? input.mergeBundle.status,
218
- outcome: input.result.mergeDisposition ?? input.mergeBundle.disposition,
219
- generatedAt
220
- });
221
- const semanticAdmission = createLiveSemanticAdmissionGraph(input, {
222
- generatedAt,
223
- jobNodeId,
224
- candidateNodeId
225
- });
226
- frame.nodes.push({
227
- id: candidateNodeId,
228
- kind: 'candidate',
229
- label: input.job.title ?? input.job.id,
230
- jobId: input.job.id,
231
- taskId: input.job.taskId,
232
- lane: input.job.lane,
233
- status: input.mergeBundle.status,
234
- outcome: input.mergeBundle.disposition,
235
- generatedAt,
236
- data: {
237
- mergeReadiness: input.mergeBundle.mergeReadiness,
238
- riskLevel: input.mergeBundle.riskLevel,
239
- autoMergeable: input.mergeBundle.autoMergeable,
240
- changedPaths: input.mergeBundle.changedPaths,
241
- ownershipViolations: input.mergeBundle.ownershipViolations,
242
- semanticAdmission: semanticAdmission?.summary
243
- }
244
- });
245
- frame.edges.push({
246
- id: `produces:${jobNodeId}->${candidateNodeId}`,
247
- kind: 'produces',
248
- from: jobNodeId,
249
- to: candidateNodeId
250
- });
251
- if (semanticAdmission) {
252
- events.push(liveEvent(input, {
253
- type: 'semantic-admission.result',
254
- generatedAt,
255
- nodes: semanticAdmission.nodes,
256
- edges: semanticAdmission.edges,
257
- data: semanticAdmission.summary
258
- }));
259
- }
260
- events.push(liveEvent(input, {
261
- type: 'job.finished',
262
- generatedAt,
263
- nodes: frame.nodes,
264
- edges: frame.edges,
265
- data: {
266
- status: input.result.status ?? input.mergeBundle.status,
267
- mergeReadiness: input.result.mergeReadiness ?? input.mergeBundle.mergeReadiness,
268
- mergeDisposition: input.result.mergeDisposition ?? input.mergeBundle.disposition,
269
- changedPathCount: input.result.changedPaths?.length ?? input.mergeBundle.changedPaths.length,
270
- evidenceCount: evidencePaths.length,
271
- gateCount: gates.length
272
- }
273
- }));
274
- return events;
275
- }
276
- function createLiveSemanticAdmissionGraph(input, graph) {
277
- const decisions = createLiveSemanticAdmissionDecisions(input.result, input.mergeBundle);
278
- if (decisions.length === 0)
279
- return undefined;
280
- const nodes = decisions.map((decision) => ({
281
- id: `semantic-admission:${input.job.id}:${stableCodexRunGraphPart(decision.key)}`,
282
- kind: 'semantic-admission',
283
- label: decision.label,
284
- jobId: input.job.id,
285
- taskId: input.job.taskId,
286
- lane: input.job.lane,
287
- status: decision.status,
288
- outcome: decision.action,
289
- generatedAt: graph.generatedAt,
290
- refs: { job: graph.jobNodeId, candidate: graph.candidateNodeId },
291
- data: decision.data
292
- }));
293
- const edges = nodes.flatMap((node, index) => {
294
- const decision = decisions[index];
295
- return [
296
- {
297
- id: `produces:${graph.jobNodeId}->${node.id}`,
298
- kind: 'produces',
299
- from: graph.jobNodeId,
300
- to: node.id,
301
- label: decision.source
302
- },
303
- {
304
- id: `decides:${node.id}->${graph.candidateNodeId}`,
305
- kind: 'decides',
306
- from: node.id,
307
- to: graph.candidateNodeId,
308
- label: decision.status
309
- }
310
- ];
311
- });
312
- return {
313
- nodes,
314
- edges,
315
- summary: {
316
- decisionCount: decisions.length,
317
- statuses: countStrings(decisions.map((decision) => decision.status)),
318
- sources: countStrings(decisions.map((decision) => decision.source)),
319
- reasonCodes: uniqueStrings(decisions.flatMap((decision) => decision.reasonCodes)),
320
- reasons: uniqueStrings(decisions.flatMap((decision) => decision.reasons))
321
- }
322
- };
323
- }
324
- function createLiveSemanticAdmissionDecisions(result, mergeBundle) {
325
- const metadata = recordValue(mergeBundle.metadata);
326
- const resultMetadata = recordValue(result.metadata);
327
- const semanticImport = semanticImportSummaryForLiveEvent(result, mergeBundle);
328
- const decisions = [];
329
- const aggregateReasonCodes = uniqueStrings([
330
- ...semanticImportReasonCodes(semanticImport),
331
- ...semanticAdmissionReasonCodesFromMetadata(metadata),
332
- ...semanticAdmissionReasonCodesFromMetadata(resultMetadata)
333
- ]);
334
- const aggregateReasons = uniqueStrings([
335
- ...mergeBundle.reasons,
336
- ...stringValues(recordValue(metadata?.semanticEditAdmission)?.reasons),
337
- ...stringValues(recordValue(resultMetadata?.semanticEditAdmission)?.reasons)
338
- ]);
339
- if (semanticImport || aggregateReasonCodes.length > 0 || aggregateReasons.some(isSemanticAdmissionReason)) {
340
- const status = classifyLiveSemanticAdmissionStatus({
341
- staleAgainstHead: mergeBundle.staleAgainstHead || mergeBundle.disposition === 'stale-against-head' || result.mergeDisposition === 'stale-against-head',
342
- autoMergeable: mergeBundle.autoMergeable || mergeBundle.disposition === 'auto-mergeable' || result.mergeDisposition === 'auto-mergeable',
343
- signals: [
344
- mergeBundle.status,
345
- mergeBundle.mergeReadiness,
346
- mergeBundle.disposition,
347
- result.status,
348
- result.mergeReadiness,
349
- result.mergeDisposition,
350
- ...aggregateReasonCodes,
351
- ...aggregateReasons,
352
- ...semanticImportStatusSignals(semanticImport)
353
- ]
354
- });
355
- decisions.push({
356
- key: 'merge-admission',
357
- source: 'merge-admission',
358
- label: `semantic admission: ${status}`,
359
- status,
360
- action: status === 'safe' ? 'apply' : status === 'no-op' ? 'record' : status === 'stale' ? 'rerun' : status === 'block' ? 'block' : 'review',
361
- reasonCodes: aggregateReasonCodes,
362
- reasons: aggregateReasons,
363
- data: {
364
- source: 'merge-admission',
365
- status,
366
- mergeReadiness: result.mergeReadiness ?? mergeBundle.mergeReadiness,
367
- disposition: result.mergeDisposition ?? mergeBundle.disposition,
368
- autoMergeable: mergeBundle.autoMergeable,
369
- staleAgainstHead: mergeBundle.staleAgainstHead,
370
- semanticImportPresent: Boolean(semanticImport),
371
- reasonCodes: aggregateReasonCodes,
372
- reasons: aggregateReasons
373
- }
374
- });
375
- }
376
- for (const decision of semanticMetadataDecisions(metadata, 'metadata'))
377
- decisions.push(decision);
378
- for (const decision of semanticMetadataDecisions(resultMetadata, 'result-metadata'))
379
- decisions.push(decision);
380
- for (const decision of semanticEditSummaryDecisions(semanticImport))
381
- decisions.push(decision);
382
- const seen = new Set();
383
- return decisions.filter((decision) => {
384
- const dedupeKey = `${decision.source}:${decision.key}`;
385
- if (seen.has(dedupeKey))
386
- return false;
387
- seen.add(dedupeKey);
388
- return true;
389
- });
390
- }
391
- function semanticMetadataDecisions(metadata, sourcePrefix) {
392
- if (!metadata)
393
- return [];
394
- const out = [];
395
- for (const [key, label] of [
396
- ['semanticAdmission', 'semantic admission'],
397
- ['semanticMergeAdmission', 'semantic merge admission'],
398
- ['semanticEditAdmission', 'semantic edit admission'],
399
- ['safeMerge', 'safe merge'],
400
- ['safeMergeDecision', 'safe merge decision'],
401
- ['semanticSafeMerge', 'semantic safe merge']
402
- ]) {
403
- const record = recordValue(metadata[key]);
404
- if (!record)
405
- continue;
406
- const reasonCodes = semanticReasonCodesFromRecord(record);
407
- const reasons = stringValues(record.reasons);
408
- const status = classifyLiveSemanticAdmissionStatus({
409
- signals: [
410
- stringValue(record.status),
411
- stringValue(record.decision),
412
- stringValue(record.outcome),
413
- stringValue(record.action),
414
- ...reasonCodes,
415
- ...reasons
416
- ],
417
- autoMergeable: record.autoMergeCandidate === true || record.cleanEligible === true || record.safe === true
418
- });
419
- out.push({
420
- key,
421
- source: `${sourcePrefix}:${key}`,
422
- label: `${label}: ${status}`,
423
- status,
424
- action: stringValue(record.action) ?? stringValue(record.decision),
425
- reasonCodes,
426
- reasons,
427
- data: {
428
- source: `${sourcePrefix}:${key}`,
429
- status,
430
- action: stringValue(record.action),
431
- decision: stringValue(record.decision),
432
- outcome: stringValue(record.outcome),
433
- reasonCodes,
434
- reasons,
435
- record
436
- }
437
- });
438
- }
439
- return out;
440
- }
441
- function semanticEditSummaryDecisions(semanticImport) {
442
- if (!semanticImport)
443
- return [];
444
- const sections = [
445
- ['semantic-edit-script', 'semantic edit script', recordValue(semanticImport.semanticEditScripts)],
446
- ['semantic-edit-projection', 'semantic edit projection', recordValue(semanticImport.semanticEditProjections) ?? recordValue(semanticImport.semanticEditProjection)],
447
- ['semantic-edit-replay', 'semantic edit replay', recordValue(semanticImport.semanticEditReplays) ?? recordValue(semanticImport.semanticEditReplay)]
448
- ];
449
- return sections.flatMap(([key, label, section]) => {
450
- if (!section || !semanticEditSectionHasAdmissionData(section))
451
- return [];
452
- const reasonCodes = semanticReasonCodesFromRecord(section);
453
- const reasons = stringValues(section.reasons);
454
- const admissionStatuses = semanticAdmissionStatuses(section);
455
- const status = classifyLiveSemanticAdmissionStatus({
456
- signals: [
457
- stringValue(section.status),
458
- ...stringValues(section.actions),
459
- ...admissionStatuses,
460
- ...reasonCodes,
461
- ...reasons
462
- ]
463
- });
464
- return [{
465
- key,
466
- source: key,
467
- label: `${label}: ${status}`,
468
- status,
469
- action: firstString(stringValues(section.actions)) ?? actionForSemanticAdmissionStatus(status),
470
- reasonCodes,
471
- reasons,
472
- data: {
473
- source: key,
474
- status,
475
- action: firstString(stringValues(section.actions)) ?? actionForSemanticAdmissionStatus(status),
476
- admissionStatuses,
477
- reasonCodes,
478
- reasons,
479
- summary: section
480
- }
481
- }];
482
- });
483
- }
484
- function semanticImportSummaryForLiveEvent(result, mergeBundle) {
485
- const metadata = recordValue(mergeBundle.metadata);
486
- const resultMetadata = recordValue(result.metadata);
487
- const candidates = [
488
- recordValue(result.semanticImport),
489
- recordValue(mergeBundle.semanticImport),
490
- recordValue(resultMetadata?.semanticImport),
491
- recordValue(resultMetadata?.semanticImportSummary),
492
- recordValue(metadata?.semanticImport),
493
- recordValue(metadata?.semanticImportSummary)
494
- ].filter((entry) => Boolean(entry));
495
- return candidates.sort((left, right) => semanticImportRichness(right) - semanticImportRichness(left))[0];
496
- }
497
- function semanticImportRichness(summary) {
498
- const semanticIndex = recordValue(summary.semanticIndex);
499
- const semanticSidecars = recordValue(summary.semanticSidecars);
500
- const values = [
501
- summary.total,
502
- summary.selected,
503
- summary.eligible,
504
- summary.imported,
505
- summary.sourceMapMappingCount,
506
- semanticIndex?.symbols,
507
- semanticSidecars?.ownershipRegions,
508
- semanticSidecars?.patchHints
509
- ];
510
- return values.reduce((sum, value) => sum + nonNegativeNumber(value), 0);
511
- }
512
- function semanticImportReasonCodes(summary) {
513
- if (!summary)
514
- return [];
515
- return uniqueStrings([
516
- ...semanticReasonCodesFromRecord(summary),
517
- ...semanticReasonCodesFromRecord(recordValue(summary.semanticEditScripts)),
518
- ...semanticReasonCodesFromRecord(recordValue(summary.semanticEditProjections)),
519
- ...semanticReasonCodesFromRecord(recordValue(summary.semanticEditProjection)),
520
- ...semanticReasonCodesFromRecord(recordValue(summary.semanticEditReplays)),
521
- ...semanticReasonCodesFromRecord(recordValue(summary.semanticEditReplay)),
522
- ...semanticReasonCodesFromRecord(recordValue(summary.semanticLineage)),
523
- ...semanticReasonCodesFromRecord(recordValue(summary.semanticSliceAdmissions))
524
- ]);
525
- }
526
- function semanticAdmissionReasonCodesFromMetadata(metadata) {
527
- if (!metadata)
528
- return [];
529
- return uniqueStrings([
530
- ...semanticReasonCodesFromRecord(recordValue(metadata.semanticAdmission)),
531
- ...semanticReasonCodesFromRecord(recordValue(metadata.semanticMergeAdmission)),
532
- ...semanticReasonCodesFromRecord(recordValue(metadata.semanticEditAdmission)),
533
- ...semanticReasonCodesFromRecord(recordValue(metadata.safeMerge)),
534
- ...semanticReasonCodesFromRecord(recordValue(metadata.safeMergeDecision)),
535
- ...semanticReasonCodesFromRecord(recordValue(metadata.semanticSafeMerge))
536
- ]);
537
- }
538
- function semanticImportStatusSignals(summary) {
539
- if (!summary)
540
- return [];
541
- return uniqueStrings([
542
- ...semanticAdmissionStatuses(recordValue(summary.semanticEditScripts)),
543
- ...semanticAdmissionStatuses(recordValue(summary.semanticEditProjections)),
544
- ...semanticAdmissionStatuses(recordValue(summary.semanticEditProjection)),
545
- ...semanticAdmissionStatuses(recordValue(summary.semanticEditReplays)),
546
- ...semanticAdmissionStatuses(recordValue(summary.semanticEditReplay)),
547
- ...Object.keys(countRecord(summary.readiness)).filter((key) => countRecord(summary.readiness)[key] > 0)
548
- ]);
549
- }
550
- function semanticEditSectionHasAdmissionData(section) {
551
- return nonNegativeNumber(section.total) > 0 ||
552
- nonNegativeNumber(section.operations) > 0 ||
553
- nonNegativeNumber(section.acceptedClean) > 0 ||
554
- semanticAdmissionStatuses(section).length > 0 ||
555
- semanticReasonCodesFromRecord(section).length > 0;
556
- }
557
- function semanticAdmissionStatuses(section) {
558
- if (!section)
559
- return [];
560
- const counters = [
561
- countRecord(section.admission),
562
- countRecord(section.statusCounts),
563
- countRecord(section.byStatus)
564
- ];
565
- return uniqueStrings([
566
- ...counters.flatMap((record) => Object.entries(record).filter(([, count]) => count > 0).map(([key]) => key)),
567
- ...[
568
- ['accepted-clean', section.acceptedClean],
569
- ['already-applied', section.alreadyApplied],
570
- ['auto-merge-candidate', section.autoMergeCandidates],
571
- ['auto-apply-candidate', section.autoApplyCandidates],
572
- ['portable', section.portable],
573
- ['projected', section.projected],
574
- ['conflict', section.conflicts],
575
- ['stale', section.stale],
576
- ['blocked', section.blocked],
577
- ['needs-port', section.needsPort],
578
- ['needs-review', section.reviewRequired],
579
- ['evidence-only', section.evidenceOnly]
580
- ].filter(([, value]) => nonNegativeNumber(value) > 0).map(([status]) => String(status))
581
- ]);
582
- }
583
- function classifyLiveSemanticAdmissionStatus(input) {
584
- const signals = uniqueStrings(input.signals.filter((signal) => Boolean(signal))).map(normalizedAdmissionSignal);
585
- if (input.staleAgainstHead || signals.some((signal) => signal === 'stale' || signal === 'stale-against-head'))
586
- return 'stale';
587
- if (signals.some((signal) => signal.includes('conflict') ||
588
- signal === 'block' ||
589
- signal === 'blocked' ||
590
- signal === 'reject' ||
591
- signal === 'rejected' ||
592
- signal === 'failed' ||
593
- signal === 'fail'))
594
- return 'block';
595
- if (signals.some((signal) => signal === 'already-applied' ||
596
- signal === 'alreadyapplied' ||
597
- signal === 'no-op' ||
598
- signal === 'noop' ||
599
- signal === 'no-source-changes' ||
600
- signal === 'discovery-only' ||
601
- signal === 'record-only' ||
602
- signal === 'record'))
603
- return 'no-op';
604
- if (signals.some((signal) => signal === 'review' ||
605
- signal === 'needs-review' ||
606
- signal === 'needsreview' ||
607
- signal === 'review-required' ||
608
- signal === 'reviewrequired' ||
609
- signal === 'needs-port' ||
610
- signal === 'needsport' ||
611
- signal === 'manual-review' ||
612
- signal === 'human-review' ||
613
- signal === 'prioritize' ||
614
- signal === 'ambiguous' ||
615
- signal === 'evidence-only'))
616
- return 'review';
617
- if (input.autoMergeable || signals.some((signal) => signal === 'safe' ||
618
- signal === 'ready' ||
619
- signal === 'admit' ||
620
- signal === 'apply' ||
621
- signal === 'accepted-clean' ||
622
- signal === 'auto-merge-candidate' ||
623
- signal === 'auto-apply-candidate' ||
624
- signal === 'portable' ||
625
- signal === 'projected' ||
626
- signal === 'verified-patch' ||
627
- signal === 'auto-mergeable'))
628
- return 'safe';
629
- return 'review';
630
- }
631
- function actionForSemanticAdmissionStatus(status) {
632
- if (status === 'safe')
633
- return 'apply';
634
- if (status === 'no-op')
635
- return 'record';
636
- if (status === 'stale')
637
- return 'rerun';
638
- if (status === 'block')
639
- return 'block';
640
- return 'review';
641
- }
642
- function semanticReasonCodesFromRecord(record) {
643
- if (!record)
644
- return [];
645
- return uniqueStrings([
646
- ...stringValues(record.reasonCodes),
647
- ...stringValues(record.reasonCode),
648
- ...stringValues(record.semanticImportExpectedMissingReasonCodes),
649
- ...stringValues(record.expectedMissingReasonCodes)
650
- ]);
651
- }
652
- function isSemanticAdmissionReason(reason) {
653
- return normalizedAdmissionSignal(reason).startsWith('semantic-') ||
654
- normalizedAdmissionSignal(reason).startsWith('auto-merge-candidate-');
655
- }
656
- function normalizedAdmissionSignal(value) {
657
- return value.trim().replace(/_/g, '-').toLowerCase();
658
- }
659
- function liveEvent(input, event) {
660
- return {
661
- kind: 'frontier.swarm-codex.live-run-graph-event',
662
- version: 1,
663
- type: event.type,
664
- runId: input.runId,
665
- jobId: input.job.id,
666
- taskId: input.job.taskId,
667
- lane: input.job.lane,
668
- generatedAt: event.generatedAt,
669
- ...(event.nodes ? { nodes: event.nodes } : {}),
670
- ...(event.edges ? { edges: event.edges } : {}),
671
- ...(event.data ? { data: event.data } : {})
672
- };
673
- }
674
- function jobGraphFrame(job, input) {
675
- const runNodeId = runGraphRunNodeId(input.runId, input.outDir);
676
- const taskNodeId = runGraphTaskNodeId(job.taskId);
677
- const jobNodeId = runGraphJobNodeId(job.id);
678
- return {
679
- nodes: [
680
- {
681
- id: runNodeId,
682
- kind: 'run',
683
- label: input.runId ?? path.basename(input.outDir),
684
- path: input.outDir,
685
- status: 'running',
686
- generatedAt: input.generatedAt
687
- },
688
- {
689
- id: taskNodeId,
690
- kind: 'task',
691
- label: job.taskId,
692
- taskId: job.taskId,
693
- lane: job.lane,
694
- generatedAt: input.generatedAt,
695
- data: { title: job.task.title, workKind: job.task.workKind }
696
- },
697
- {
698
- id: jobNodeId,
699
- kind: 'job',
700
- label: job.id,
701
- jobId: job.id,
702
- taskId: job.taskId,
703
- lane: job.lane,
704
- model: job.compute.model ?? stringRecordValue(job.compute.metadata, 'model'),
705
- computeId: job.compute.id,
706
- modelTier: job.compute.serviceTier,
707
- status: input.status,
708
- outcome: input.outcome,
709
- generatedAt: input.generatedAt,
710
- data: { title: job.title, capabilities: job.capabilities, allowedWrites: job.allowedWrites }
711
- }
712
- ],
713
- edges: [
714
- { id: `contains:${runNodeId}->${taskNodeId}`, kind: 'contains', from: runNodeId, to: taskNodeId },
715
- { id: `contains:${runNodeId}->${jobNodeId}`, kind: 'contains', from: runNodeId, to: jobNodeId },
716
- { id: `produces:${taskNodeId}->${jobNodeId}`, kind: 'produces', from: taskNodeId, to: jobNodeId }
717
- ]
718
- };
719
- }
720
- function runGraphRunNodeId(runId, outDir) {
721
- return `run:${stableCodexRunGraphPart(runId ?? (path.basename(outDir) || 'run'))}`;
722
- }
723
- function runGraphTaskNodeId(taskId) {
724
- return `task:${stableCodexRunGraphPart(taskId)}`;
725
- }
726
- function runGraphJobNodeId(jobId) {
727
- return `job:${stableCodexRunGraphPart(jobId)}`;
728
- }
729
- function runGraphEvidenceNodeId(jobId, evidencePath) {
730
- return `evidence:${stableCodexRunGraphPart(`${jobId}:${evidencePath}`)}`;
731
- }
732
- function countStrings(values) {
733
- const counts = {};
734
- for (const value of values)
735
- counts[value] = (counts[value] ?? 0) + 1;
736
- return counts;
737
- }
738
- function uniqueStrings(values) {
739
- return Array.from(new Set(values.filter((value) => value.length > 0)));
740
- }
741
- function recordValue(value) {
742
- if (!value || typeof value !== 'object' || Array.isArray(value))
743
- return undefined;
744
- return value;
745
- }
746
- function countRecord(value) {
747
- const record = recordValue(value);
748
- if (!record)
749
- return {};
750
- const out = {};
751
- for (const [key, entry] of Object.entries(record)) {
752
- const count = nonNegativeNumber(entry);
753
- if (count > 0)
754
- out[key] = count;
755
- }
756
- return out;
757
- }
758
- function stringValue(value) {
759
- return typeof value === 'string' && value.length > 0 ? value : undefined;
760
- }
761
- function stringValues(value) {
762
- if (typeof value === 'string' && value.length > 0)
763
- return [value];
764
- if (!Array.isArray(value))
765
- return [];
766
- return value.filter((entry) => typeof entry === 'string' && entry.length > 0);
767
- }
768
- function firstString(values) {
769
- return values.find((value) => value.length > 0);
770
- }
771
- function nonNegativeNumber(value) {
772
- const number = Number(value);
773
- return Number.isFinite(number) && number > 0 ? number : 0;
774
- }
775
- function stringRecordValue(record, key) {
776
- if (!record || typeof record !== 'object')
777
- return undefined;
778
- const value = record[key];
779
- return typeof value === 'string' && value ? value : undefined;
780
- }
781
- function errorString(value) {
782
- if (value === undefined || value === null)
783
- return undefined;
784
- if (value instanceof Error)
785
- return value.message;
786
- return typeof value === 'string' ? value : JSON.stringify(value);
787
- }
788
- async function writeTextFileAtomic(file, text) {
789
- const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`;
790
- try {
791
- await fs.writeFile(tmp, text);
792
- await fs.rename(tmp, file);
793
- }
794
- catch (error) {
795
- await fs.rm(tmp, { force: true }).catch(() => { });
796
- throw error;
797
- }
798
- }
799
- //# sourceMappingURL=run-graph-live.js.map