@shapeshift-labs/frontier-swarm 0.3.0 → 0.4.0
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 +21 -5
- package/benchmarks/package-bench.mjs +68 -0
- package/dist/index.d.ts +451 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +767 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -30,6 +30,26 @@ export const FRONTIER_SWARM_MERGE_PLAN_KIND = 'frontier.swarm.merge-plan';
|
|
|
30
30
|
export const FRONTIER_SWARM_MERGE_PLAN_VERSION = 1;
|
|
31
31
|
export const FRONTIER_SWARM_MERGE_BUNDLE_KIND = 'frontier.swarm.merge-bundle';
|
|
32
32
|
export const FRONTIER_SWARM_MERGE_BUNDLE_VERSION = 1;
|
|
33
|
+
export const FRONTIER_SWARM_QUEUE_OVERLAY_KIND = 'frontier.swarm.queue-overlay';
|
|
34
|
+
export const FRONTIER_SWARM_QUEUE_OVERLAY_VERSION = 1;
|
|
35
|
+
export const FRONTIER_SWARM_MERGE_INDEX_KIND = 'frontier.swarm.merge-index';
|
|
36
|
+
export const FRONTIER_SWARM_MERGE_INDEX_VERSION = 1;
|
|
37
|
+
export const FRONTIER_SWARM_HOTSPOT_REPORT_KIND = 'frontier.swarm.hotspot-report';
|
|
38
|
+
export const FRONTIER_SWARM_HOTSPOT_REPORT_VERSION = 1;
|
|
39
|
+
export const FRONTIER_SWARM_REVIEWER_LANE_PLAN_KIND = 'frontier.swarm.reviewer-lane-plan';
|
|
40
|
+
export const FRONTIER_SWARM_REVIEWER_LANE_PLAN_VERSION = 1;
|
|
41
|
+
export const FRONTIER_SWARM_RUN_STORE_SHARDS_KIND = 'frontier.swarm.run-store-shards';
|
|
42
|
+
export const FRONTIER_SWARM_RUN_STORE_SHARDS_VERSION = 1;
|
|
43
|
+
export const FRONTIER_SWARM_MERGE_ADMISSION_KIND = 'frontier.swarm.merge-admission';
|
|
44
|
+
export const FRONTIER_SWARM_MERGE_ADMISSION_VERSION = 1;
|
|
45
|
+
export const FRONTIER_SWARM_CONTEXT_PACK_KIND = 'frontier.swarm.context-pack';
|
|
46
|
+
export const FRONTIER_SWARM_CONTEXT_PACK_VERSION = 1;
|
|
47
|
+
export const FRONTIER_SWARM_ORACLE_CORPUS_KIND = 'frontier.swarm.oracle-corpus';
|
|
48
|
+
export const FRONTIER_SWARM_ORACLE_CORPUS_VERSION = 1;
|
|
49
|
+
export const FRONTIER_SWARM_LANE_PLAYBOOK_KIND = 'frontier.swarm.lane-playbook';
|
|
50
|
+
export const FRONTIER_SWARM_LANE_PLAYBOOK_VERSION = 1;
|
|
51
|
+
export const FRONTIER_SWARM_PATCH_STACK_PLAN_KIND = 'frontier.swarm.patch-stack-plan';
|
|
52
|
+
export const FRONTIER_SWARM_PATCH_STACK_PLAN_VERSION = 1;
|
|
33
53
|
export const FRONTIER_SWARM_DEFAULT_CODEX_COMPUTE_ID = 'codex.gpt-5.5.xhigh';
|
|
34
54
|
export const FRONTIER_SWARM_DEFAULT_MODEL = 'gpt-5.5';
|
|
35
55
|
export const FRONTIER_SWARM_DEFAULT_REASONING_EFFORT = 'xhigh';
|
|
@@ -426,6 +446,576 @@ export function createSwarmMergeBundle(input) {
|
|
|
426
446
|
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
427
447
|
};
|
|
428
448
|
}
|
|
449
|
+
export function createSwarmQueueOverlay(input = {}) {
|
|
450
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
451
|
+
const entries = [];
|
|
452
|
+
for (const bundle of input.bundles ?? []) {
|
|
453
|
+
const status = queueOverlayStatusFromBundle(bundle);
|
|
454
|
+
const queueItemIds = bundle.queueItemIds.length ? bundle.queueItemIds : [bundle.taskId ?? bundle.jobId];
|
|
455
|
+
for (const queueItemId of queueItemIds) {
|
|
456
|
+
entries.push({
|
|
457
|
+
queueItemId,
|
|
458
|
+
jobId: bundle.jobId,
|
|
459
|
+
status,
|
|
460
|
+
mergeReadiness: bundle.mergeReadiness,
|
|
461
|
+
disposition: bundle.disposition,
|
|
462
|
+
riskLevel: bundle.riskLevel,
|
|
463
|
+
...(bundle.patchPath ? { patchPath: bundle.patchPath } : {}),
|
|
464
|
+
evidencePaths: [...bundle.evidencePaths],
|
|
465
|
+
changedPaths: [...bundle.changedPaths],
|
|
466
|
+
changedRegions: [...bundle.changedRegions],
|
|
467
|
+
reasons: [...bundle.reasons],
|
|
468
|
+
generatedAt: bundle.generatedAt
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
for (const raw of input.results ?? []) {
|
|
473
|
+
const result = isSwarmJobResult(raw) ? cloneJsonValue(raw) : normalizeResult(raw);
|
|
474
|
+
const queueItemIds = result.queueItemIds.length ? result.queueItemIds : [result.jobId];
|
|
475
|
+
for (const queueItemId of queueItemIds) {
|
|
476
|
+
entries.push({
|
|
477
|
+
queueItemId,
|
|
478
|
+
jobId: result.jobId,
|
|
479
|
+
status: queueOverlayStatusFromResult(result),
|
|
480
|
+
mergeReadiness: result.mergeReadiness,
|
|
481
|
+
disposition: result.mergeDisposition,
|
|
482
|
+
riskLevel: result.riskLevel,
|
|
483
|
+
...(result.patchPath ? { patchPath: result.patchPath } : {}),
|
|
484
|
+
evidencePaths: [...result.evidencePaths],
|
|
485
|
+
changedPaths: [...result.changedPaths],
|
|
486
|
+
changedRegions: [...result.changedRegions],
|
|
487
|
+
reasons: result.error ? [result.error] : [],
|
|
488
|
+
generatedAt
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
const byQueueItemId = groupOverlayEntries(entries);
|
|
493
|
+
return {
|
|
494
|
+
kind: FRONTIER_SWARM_QUEUE_OVERLAY_KIND,
|
|
495
|
+
version: FRONTIER_SWARM_QUEUE_OVERLAY_VERSION,
|
|
496
|
+
id: input.id ?? 'swarm-queue-overlay:' + stableHash([input.runId, entries, generatedAt]),
|
|
497
|
+
...(input.runId ? { runId: input.runId } : {}),
|
|
498
|
+
generatedAt,
|
|
499
|
+
entries,
|
|
500
|
+
byQueueItemId,
|
|
501
|
+
summary: {
|
|
502
|
+
entryCount: entries.length,
|
|
503
|
+
queueItemCount: Object.keys(byQueueItemId).length,
|
|
504
|
+
readyToApplyCount: entries.filter((entry) => entry.status === 'ready-to-apply').length,
|
|
505
|
+
needsHumanPortCount: entries.filter((entry) => entry.status === 'needs-human-port').length,
|
|
506
|
+
failedEvidenceCount: entries.filter((entry) => entry.status === 'failed-evidence').length,
|
|
507
|
+
staleAgainstHeadCount: entries.filter((entry) => entry.status === 'stale-against-head').length,
|
|
508
|
+
discoveryOnlyCount: entries.filter((entry) => entry.status === 'discovery-only').length
|
|
509
|
+
},
|
|
510
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
export function deriveSwarmQueueStatus(input) {
|
|
514
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
515
|
+
const latestByQueueItem = new Map();
|
|
516
|
+
for (const overlay of input.overlays ?? []) {
|
|
517
|
+
for (const entry of overlay.entries) {
|
|
518
|
+
const existing = latestByQueueItem.get(entry.queueItemId);
|
|
519
|
+
if (!existing || entry.generatedAt >= existing.generatedAt)
|
|
520
|
+
latestByQueueItem.set(entry.queueItemId, entry);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
const jobs = input.snapshot.jobs.map((job) => {
|
|
524
|
+
const overlay = latestByQueueItem.get(job.taskId ?? job.jobId) ?? latestByQueueItem.get(job.jobId);
|
|
525
|
+
if (!overlay)
|
|
526
|
+
return cloneJsonValue(job);
|
|
527
|
+
return {
|
|
528
|
+
...cloneJsonValue(job),
|
|
529
|
+
status: queueJobStatusFromOverlay(overlay),
|
|
530
|
+
lastError: overlay.status === 'failed-evidence' || overlay.status === 'stale-against-head' ? overlay.reasons.join(', ') : job.lastError,
|
|
531
|
+
metadata: toJsonObject({
|
|
532
|
+
...(job.metadata ?? {}),
|
|
533
|
+
overlayStatus: overlay.status,
|
|
534
|
+
mergeDisposition: overlay.disposition,
|
|
535
|
+
mergeReadiness: overlay.mergeReadiness,
|
|
536
|
+
evidencePaths: overlay.evidencePaths
|
|
537
|
+
})
|
|
538
|
+
};
|
|
539
|
+
});
|
|
540
|
+
const byStatus = groupIds(jobs, (job) => job.status);
|
|
541
|
+
return {
|
|
542
|
+
generatedAt,
|
|
543
|
+
jobs,
|
|
544
|
+
byStatus,
|
|
545
|
+
summary: {
|
|
546
|
+
jobCount: jobs.length,
|
|
547
|
+
leaseCount: input.snapshot.leases.length,
|
|
548
|
+
readyCount: byStatus.ready?.length ?? 0,
|
|
549
|
+
leasedCount: byStatus.leased?.length ?? 0,
|
|
550
|
+
completedCount: byStatus.completed?.length ?? 0,
|
|
551
|
+
failedCount: byStatus.failed?.length ?? 0,
|
|
552
|
+
deadLetterCount: byStatus['dead-letter']?.length ?? 0
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
export function createSwarmMergeIndex(input) {
|
|
557
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
558
|
+
const entries = input.bundles.map((bundle) => {
|
|
559
|
+
const patchStatus = input.patchStatuses?.[bundle.jobId] ?? (bundle.staleAgainstHead ? 'stale' : bundle.patchPath ? 'unknown' : 'missing');
|
|
560
|
+
const staleAgainstHead = bundle.staleAgainstHead || patchStatus === 'stale' || patchStatus === 'failed-check';
|
|
561
|
+
return {
|
|
562
|
+
jobId: bundle.jobId,
|
|
563
|
+
...(bundle.taskId ? { taskId: bundle.taskId } : {}),
|
|
564
|
+
...(bundle.lane ? { lane: bundle.lane } : {}),
|
|
565
|
+
...(bundle.title ? { title: bundle.title } : {}),
|
|
566
|
+
status: bundle.status,
|
|
567
|
+
mergeReadiness: bundle.mergeReadiness,
|
|
568
|
+
disposition: staleAgainstHead ? 'stale-against-head' : bundle.disposition,
|
|
569
|
+
riskLevel: bundle.riskLevel,
|
|
570
|
+
patchStatus,
|
|
571
|
+
staleAgainstHead,
|
|
572
|
+
autoMergeable: bundle.autoMergeable && !staleAgainstHead,
|
|
573
|
+
changedPaths: [...bundle.changedPaths],
|
|
574
|
+
changedRegions: [...bundle.changedRegions],
|
|
575
|
+
conflictKeys: mergeIndexConflictKeys(bundle),
|
|
576
|
+
conflictingJobIds: [],
|
|
577
|
+
ownedFilesTouched: [...bundle.ownedFilesTouched],
|
|
578
|
+
ownershipViolations: [...bundle.ownershipViolations],
|
|
579
|
+
...(bundle.patchPath ? { patchPath: bundle.patchPath } : {}),
|
|
580
|
+
...(bundle.patchHash ? { patchHash: bundle.patchHash } : {}),
|
|
581
|
+
evidencePaths: [...bundle.evidencePaths],
|
|
582
|
+
queueItemIds: [...bundle.queueItemIds],
|
|
583
|
+
reasons: uniqueStrings([...bundle.reasons, ...(staleAgainstHead ? ['stale-against-head'] : [])]),
|
|
584
|
+
generatedAt: bundle.generatedAt
|
|
585
|
+
};
|
|
586
|
+
});
|
|
587
|
+
const conflicts = createMergeIndexConflicts(entries);
|
|
588
|
+
const conflictsByJob = new Map();
|
|
589
|
+
for (const conflict of conflicts) {
|
|
590
|
+
for (const jobId of conflict.jobIds) {
|
|
591
|
+
const set = conflictsByJob.get(jobId) ?? new Set();
|
|
592
|
+
for (const other of conflict.jobIds)
|
|
593
|
+
if (other !== jobId)
|
|
594
|
+
set.add(other);
|
|
595
|
+
conflictsByJob.set(jobId, set);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
const indexed = entries.map((entry) => ({
|
|
599
|
+
...entry,
|
|
600
|
+
conflictingJobIds: Array.from(conflictsByJob.get(entry.jobId) ?? []).sort()
|
|
601
|
+
}));
|
|
602
|
+
const byDisposition = groupJobIdsBy(indexed, (entry) => entry.disposition);
|
|
603
|
+
const byPath = groupJobIdsByMany(indexed, (entry) => entry.changedPaths);
|
|
604
|
+
const byRegion = groupJobIdsByMany(indexed, (entry) => entry.changedRegions);
|
|
605
|
+
return {
|
|
606
|
+
kind: FRONTIER_SWARM_MERGE_INDEX_KIND,
|
|
607
|
+
version: FRONTIER_SWARM_MERGE_INDEX_VERSION,
|
|
608
|
+
id: input.id ?? 'swarm-merge-index:' + stableHash([input.runId, input.planId, indexed, conflicts, generatedAt]),
|
|
609
|
+
...(input.runId ? { runId: input.runId } : {}),
|
|
610
|
+
...(input.planId ? { planId: input.planId } : {}),
|
|
611
|
+
generatedAt,
|
|
612
|
+
entries: indexed,
|
|
613
|
+
conflicts,
|
|
614
|
+
byDisposition,
|
|
615
|
+
byPath,
|
|
616
|
+
byRegion,
|
|
617
|
+
summary: {
|
|
618
|
+
entryCount: indexed.length,
|
|
619
|
+
readyToApplyCount: indexed.filter((entry) => entry.disposition === 'auto-mergeable' && entry.autoMergeable && !entry.conflictingJobIds.length).length,
|
|
620
|
+
needsHumanPortCount: indexed.filter((entry) => entry.disposition === 'needs-port').length,
|
|
621
|
+
failedEvidenceCount: indexed.filter((entry) => entry.disposition === 'rejected' || entry.disposition === 'blocked' || entry.ownershipViolations.length > 0).length,
|
|
622
|
+
staleAgainstHeadCount: indexed.filter((entry) => entry.staleAgainstHead || entry.disposition === 'stale-against-head').length,
|
|
623
|
+
discoveryOnlyCount: indexed.filter((entry) => entry.disposition === 'discovery-only').length,
|
|
624
|
+
conflictCount: conflicts.length,
|
|
625
|
+
conflictedJobCount: conflictsByJob.size
|
|
626
|
+
},
|
|
627
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
export function checkSwarmRegionOwnership(job, input = {}) {
|
|
631
|
+
const changedPaths = uniqueStrings(input.changedPaths ?? []);
|
|
632
|
+
const resolvedRegions = resolveSwarmChangedRegions(job, changedPaths);
|
|
633
|
+
const changedRegions = uniqueStrings([...(input.changedRegions ?? []), ...resolvedRegions]);
|
|
634
|
+
const ownedRegions = new Set(job.ownedRegions);
|
|
635
|
+
const regionViolations = changedRegions.filter((region) => !ownedRegions.has(region));
|
|
636
|
+
const classifiedPaths = new Set();
|
|
637
|
+
for (const region of job.ownershipRegions) {
|
|
638
|
+
for (const file of changedPaths) {
|
|
639
|
+
if (region.globs.some((glob) => matchesGlob(file, glob)))
|
|
640
|
+
classifiedPaths.add(file);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
const unclassifiedChangedPaths = changedPaths.filter((file) => !classifiedPaths.has(file));
|
|
644
|
+
return {
|
|
645
|
+
ok: regionViolations.length === 0 && (job.ownershipRegions.length === 0 || unclassifiedChangedPaths.length === 0),
|
|
646
|
+
jobId: job.id,
|
|
647
|
+
changedPaths,
|
|
648
|
+
changedRegions,
|
|
649
|
+
ownedRegions: [...job.ownedRegions],
|
|
650
|
+
regionViolations,
|
|
651
|
+
unclassifiedChangedPaths
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
export function createSwarmHotspotReport(input = {}) {
|
|
655
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
656
|
+
const threshold = Math.max(2, Math.floor(input.threshold ?? 3));
|
|
657
|
+
const byPath = new Map();
|
|
658
|
+
for (const bundle of input.bundles ?? []) {
|
|
659
|
+
for (const file of bundle.changedPaths) {
|
|
660
|
+
const current = byPath.get(file) ?? {
|
|
661
|
+
path: file,
|
|
662
|
+
touchCount: 0,
|
|
663
|
+
jobIds: [],
|
|
664
|
+
regions: [],
|
|
665
|
+
dispositions: [],
|
|
666
|
+
riskLevels: []
|
|
667
|
+
};
|
|
668
|
+
current.touchCount += 1;
|
|
669
|
+
current.jobIds = uniqueStrings([...current.jobIds, bundle.jobId]);
|
|
670
|
+
current.regions = uniqueStrings([...current.regions, ...bundle.changedRegions]);
|
|
671
|
+
current.dispositions = uniqueStrings([...current.dispositions, bundle.disposition]);
|
|
672
|
+
current.riskLevels = uniqueStrings([...current.riskLevels, bundle.riskLevel]);
|
|
673
|
+
byPath.set(file, current);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
for (const raw of input.results ?? []) {
|
|
677
|
+
const result = isSwarmJobResult(raw) ? raw : normalizeResult(raw);
|
|
678
|
+
for (const file of result.changedPaths) {
|
|
679
|
+
const current = byPath.get(file) ?? {
|
|
680
|
+
path: file,
|
|
681
|
+
touchCount: 0,
|
|
682
|
+
jobIds: [],
|
|
683
|
+
regions: [],
|
|
684
|
+
dispositions: [],
|
|
685
|
+
riskLevels: []
|
|
686
|
+
};
|
|
687
|
+
current.touchCount += 1;
|
|
688
|
+
current.jobIds = uniqueStrings([...current.jobIds, result.jobId]);
|
|
689
|
+
current.regions = uniqueStrings([...current.regions, ...result.changedRegions]);
|
|
690
|
+
current.dispositions = uniqueStrings([...current.dispositions, result.mergeDisposition]);
|
|
691
|
+
current.riskLevels = uniqueStrings([...current.riskLevels, result.riskLevel]);
|
|
692
|
+
byPath.set(file, current);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
const entries = Array.from(byPath.values()).sort((left, right) => right.touchCount - left.touchCount || left.path.localeCompare(right.path));
|
|
696
|
+
const recommendations = entries
|
|
697
|
+
.filter((entry) => entry.touchCount >= threshold || entry.regions.length > 1)
|
|
698
|
+
.map((entry) => ({
|
|
699
|
+
path: entry.path,
|
|
700
|
+
reason: entry.regions.length > 1 ? 'region-overlap' : 'hot-file',
|
|
701
|
+
suggestedModuleId: suggestedModuleId(entry.path),
|
|
702
|
+
suggestedOwnershipRegions: entry.regions.length ? entry.regions : [`${suggestedModuleId(entry.path)}.*`],
|
|
703
|
+
jobIds: [...entry.jobIds]
|
|
704
|
+
}));
|
|
705
|
+
return {
|
|
706
|
+
kind: FRONTIER_SWARM_HOTSPOT_REPORT_KIND,
|
|
707
|
+
version: FRONTIER_SWARM_HOTSPOT_REPORT_VERSION,
|
|
708
|
+
id: input.id ?? 'swarm-hotspot-report:' + stableHash([entries, threshold, generatedAt]),
|
|
709
|
+
generatedAt,
|
|
710
|
+
threshold,
|
|
711
|
+
entries,
|
|
712
|
+
recommendations,
|
|
713
|
+
summary: {
|
|
714
|
+
pathCount: entries.length,
|
|
715
|
+
hotspotCount: entries.filter((entry) => entry.touchCount >= threshold).length,
|
|
716
|
+
recommendationCount: recommendations.length
|
|
717
|
+
},
|
|
718
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
export function createSwarmReviewerLanePlan(input) {
|
|
722
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
723
|
+
const reviewerLane = input.reviewerLane ?? 'review';
|
|
724
|
+
const reviewers = uniqueStrings(input.reviewers ?? []);
|
|
725
|
+
const deferralsByJob = new Map((input.admission?.deferred ?? []).map((entry) => [entry.jobId, entry.reasons]));
|
|
726
|
+
const candidates = input.index.entries.filter((entry) => input.includeAutoMergeable
|
|
727
|
+
|| deferralsByJob.has(entry.jobId)
|
|
728
|
+
|| entry.conflictingJobIds.length > 0
|
|
729
|
+
|| entry.riskLevel === 'high'
|
|
730
|
+
|| entry.disposition !== 'auto-mergeable'
|
|
731
|
+
|| !entry.autoMergeable);
|
|
732
|
+
const assignments = candidates.map((entry) => ({
|
|
733
|
+
jobId: entry.jobId,
|
|
734
|
+
reviewers: selectReviewers(reviewers, reviewers.length ? 1 : 0, entry.jobId),
|
|
735
|
+
required: deferralsByJob.has(entry.jobId) || entry.conflictingJobIds.length > 0 || entry.riskLevel === 'high' || entry.disposition !== 'auto-mergeable',
|
|
736
|
+
reasons: uniqueStrings([...reviewerLaneReasons(entry), ...(deferralsByJob.get(entry.jobId) ?? [])])
|
|
737
|
+
}));
|
|
738
|
+
const tasks = candidates.map((entry) => ({
|
|
739
|
+
id: `review-${slug(entry.jobId)}`,
|
|
740
|
+
lane: reviewerLane,
|
|
741
|
+
kind: 'review',
|
|
742
|
+
title: `Review ${entry.title ?? entry.jobId}`,
|
|
743
|
+
objective: `Review swarm merge bundle ${entry.jobId}.`,
|
|
744
|
+
sourceRefs: entry.evidencePaths,
|
|
745
|
+
targetRefs: entry.changedPaths,
|
|
746
|
+
ownedRegions: entry.changedRegions,
|
|
747
|
+
acceptance: [
|
|
748
|
+
'Review evidence, patch applicability, ownership, conflicts, and risk.',
|
|
749
|
+
`Merge disposition: ${entry.disposition}.`
|
|
750
|
+
],
|
|
751
|
+
metadata: {
|
|
752
|
+
mergeJobId: entry.jobId,
|
|
753
|
+
conflictingJobIds: entry.conflictingJobIds,
|
|
754
|
+
reasons: uniqueStrings([...reviewerLaneReasons(entry), ...(deferralsByJob.get(entry.jobId) ?? [])])
|
|
755
|
+
}
|
|
756
|
+
}));
|
|
757
|
+
return {
|
|
758
|
+
kind: FRONTIER_SWARM_REVIEWER_LANE_PLAN_KIND,
|
|
759
|
+
version: FRONTIER_SWARM_REVIEWER_LANE_PLAN_VERSION,
|
|
760
|
+
id: input.id ?? 'swarm-reviewer-lane-plan:' + stableHash([input.index.id, assignments, generatedAt]),
|
|
761
|
+
mergeIndexId: input.index.id,
|
|
762
|
+
generatedAt,
|
|
763
|
+
reviewerLane,
|
|
764
|
+
assignments,
|
|
765
|
+
tasks,
|
|
766
|
+
summary: {
|
|
767
|
+
assignmentCount: assignments.length,
|
|
768
|
+
taskCount: tasks.length
|
|
769
|
+
},
|
|
770
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
export function createSwarmRunStoreShards(input = {}) {
|
|
774
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
775
|
+
const root = input.root ?? 'agent-runs/shards';
|
|
776
|
+
const shardSize = Math.max(1, Math.floor(input.shardSize ?? 100));
|
|
777
|
+
const groupBy = input.groupBy ?? 'lane';
|
|
778
|
+
const jobs = input.run?.jobs ?? input.plan?.jobs ?? [];
|
|
779
|
+
const groups = new Map();
|
|
780
|
+
for (const job of jobs) {
|
|
781
|
+
const key = groupBy === 'none' ? 'all' : groupBy === 'hash' ? String(hashBucket(job.id, shardSize)) : job.lane;
|
|
782
|
+
groups.set(key, [...(groups.get(key) ?? []), job]);
|
|
783
|
+
}
|
|
784
|
+
const shards = [];
|
|
785
|
+
for (const [group, groupJobs] of Array.from(groups.entries()).sort((left, right) => left[0].localeCompare(right[0]))) {
|
|
786
|
+
for (let index = 0; index < groupJobs.length; index += shardSize) {
|
|
787
|
+
const slice = groupJobs.slice(index, index + shardSize);
|
|
788
|
+
const suffix = `${slug(group)}-${Math.floor(index / shardSize)}`;
|
|
789
|
+
const shardRoot = joinPathParts(root, suffix);
|
|
790
|
+
shards.push({
|
|
791
|
+
id: 'swarm-run-store-shard:' + stableHash([input.run?.id, input.plan?.id, group, index, slice.map((job) => job.id)]),
|
|
792
|
+
...(groupBy === 'lane' ? { lane: group } : {}),
|
|
793
|
+
path: shardRoot,
|
|
794
|
+
eventPath: joinPathParts(shardRoot, 'events.jsonl'),
|
|
795
|
+
resultPath: joinPathParts(shardRoot, 'results.jsonl'),
|
|
796
|
+
checkpointPath: joinPathParts(shardRoot, 'checkpoint.json'),
|
|
797
|
+
jobIds: slice.map((job) => job.id)
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
return {
|
|
802
|
+
kind: FRONTIER_SWARM_RUN_STORE_SHARDS_KIND,
|
|
803
|
+
version: FRONTIER_SWARM_RUN_STORE_SHARDS_VERSION,
|
|
804
|
+
id: input.id ?? 'swarm-run-store-shards:' + stableHash([input.run?.id, input.plan?.id, root, shardSize, groupBy, shards, generatedAt]),
|
|
805
|
+
...(input.run ? { runId: input.run.id } : {}),
|
|
806
|
+
...(input.plan ? { planId: input.plan.id } : {}),
|
|
807
|
+
root,
|
|
808
|
+
generatedAt,
|
|
809
|
+
groupBy,
|
|
810
|
+
shardSize,
|
|
811
|
+
shards,
|
|
812
|
+
summary: {
|
|
813
|
+
shardCount: shards.length,
|
|
814
|
+
jobCount: jobs.length
|
|
815
|
+
},
|
|
816
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
export function createSwarmMergeAdmission(input) {
|
|
820
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
821
|
+
const maxReady = Math.max(0, Math.floor(input.maxReady ?? input.index.entries.length));
|
|
822
|
+
const maxChangedPaths = input.maxChangedPaths === undefined ? undefined : Math.max(0, Math.floor(input.maxChangedPaths));
|
|
823
|
+
const maxChangedRegions = input.maxChangedRegions === undefined ? undefined : Math.max(0, Math.floor(input.maxChangedRegions));
|
|
824
|
+
const maxHighRisk = input.maxHighRisk === undefined ? undefined : Math.max(0, Math.floor(input.maxHighRisk));
|
|
825
|
+
const allowRisks = uniqueStrings(input.allowRisks ?? ['low', 'medium']);
|
|
826
|
+
const admitted = [];
|
|
827
|
+
const deferred = [];
|
|
828
|
+
const usedPaths = new Set();
|
|
829
|
+
const usedRegions = new Set();
|
|
830
|
+
let highRiskCount = 0;
|
|
831
|
+
for (const entry of input.index.entries) {
|
|
832
|
+
const reasons = [];
|
|
833
|
+
if (entry.disposition !== 'auto-mergeable' || !entry.autoMergeable)
|
|
834
|
+
reasons.push('not-auto-mergeable');
|
|
835
|
+
if (entry.staleAgainstHead)
|
|
836
|
+
reasons.push('stale-against-head');
|
|
837
|
+
if (entry.conflictingJobIds.length)
|
|
838
|
+
reasons.push('conflicting-changes');
|
|
839
|
+
if (!allowRisks.includes(entry.riskLevel))
|
|
840
|
+
reasons.push('risk-not-admitted');
|
|
841
|
+
if (admitted.length >= maxReady)
|
|
842
|
+
reasons.push('max-ready');
|
|
843
|
+
const nextPaths = new Set([...usedPaths, ...entry.changedPaths]);
|
|
844
|
+
const nextRegions = new Set([...usedRegions, ...entry.changedRegions]);
|
|
845
|
+
const nextHighRiskCount = highRiskCount + (entry.riskLevel === 'high' ? 1 : 0);
|
|
846
|
+
if (maxChangedPaths !== undefined && nextPaths.size > maxChangedPaths)
|
|
847
|
+
reasons.push('max-changed-paths');
|
|
848
|
+
if (maxChangedRegions !== undefined && nextRegions.size > maxChangedRegions)
|
|
849
|
+
reasons.push('max-changed-regions');
|
|
850
|
+
if (maxHighRisk !== undefined && nextHighRiskCount > maxHighRisk)
|
|
851
|
+
reasons.push('max-high-risk');
|
|
852
|
+
if (reasons.length) {
|
|
853
|
+
deferred.push({ jobId: entry.jobId, reasons: uniqueStrings(reasons) });
|
|
854
|
+
continue;
|
|
855
|
+
}
|
|
856
|
+
admitted.push(entry.jobId);
|
|
857
|
+
for (const file of entry.changedPaths)
|
|
858
|
+
usedPaths.add(file);
|
|
859
|
+
for (const region of entry.changedRegions)
|
|
860
|
+
usedRegions.add(region);
|
|
861
|
+
highRiskCount = nextHighRiskCount;
|
|
862
|
+
}
|
|
863
|
+
return {
|
|
864
|
+
kind: FRONTIER_SWARM_MERGE_ADMISSION_KIND,
|
|
865
|
+
version: FRONTIER_SWARM_MERGE_ADMISSION_VERSION,
|
|
866
|
+
id: input.id ?? 'swarm-merge-admission:' + stableHash([input.index.id, admitted, deferred, generatedAt]),
|
|
867
|
+
mergeIndexId: input.index.id,
|
|
868
|
+
generatedAt,
|
|
869
|
+
admitted,
|
|
870
|
+
deferred,
|
|
871
|
+
budget: {
|
|
872
|
+
maxReady,
|
|
873
|
+
...(maxChangedPaths !== undefined ? { maxChangedPaths } : {}),
|
|
874
|
+
...(maxChangedRegions !== undefined ? { maxChangedRegions } : {}),
|
|
875
|
+
...(maxHighRisk !== undefined ? { maxHighRisk } : {}),
|
|
876
|
+
allowRisks
|
|
877
|
+
},
|
|
878
|
+
summary: {
|
|
879
|
+
admittedCount: admitted.length,
|
|
880
|
+
deferredCount: deferred.length,
|
|
881
|
+
changedPathCount: usedPaths.size,
|
|
882
|
+
changedRegionCount: usedRegions.size,
|
|
883
|
+
highRiskCount
|
|
884
|
+
},
|
|
885
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
export function createSwarmContextPack(input = {}) {
|
|
889
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
890
|
+
const task = input.job?.task ?? (input.task ? isSwarmTask(input.task) ? input.task : normalizeTask(input.task) : undefined);
|
|
891
|
+
const files = uniqueStrings([
|
|
892
|
+
...(input.files ?? []),
|
|
893
|
+
...(input.job?.task.sourceRefs ?? []),
|
|
894
|
+
...(input.job?.task.targetRefs ?? []),
|
|
895
|
+
...(task?.sourceRefs ?? []),
|
|
896
|
+
...(task?.targetRefs ?? [])
|
|
897
|
+
]);
|
|
898
|
+
const apiMap = Object.fromEntries(Object.entries(input.apiMap ?? {}).map(([key, values]) => [key, uniqueStrings(values)]));
|
|
899
|
+
const commands = normalizeCommands([
|
|
900
|
+
...(input.commands ?? []),
|
|
901
|
+
...(input.oracleCommands ?? []),
|
|
902
|
+
...(input.job?.verification ?? [])
|
|
903
|
+
]);
|
|
904
|
+
const expectedEvidence = uniqueStrings([
|
|
905
|
+
...(input.expectedEvidence ?? []),
|
|
906
|
+
...(input.job?.evidencePrefix ? [joinPathParts(input.job.evidencePrefix, 'evidence.json')] : [])
|
|
907
|
+
]);
|
|
908
|
+
return {
|
|
909
|
+
kind: FRONTIER_SWARM_CONTEXT_PACK_KIND,
|
|
910
|
+
version: FRONTIER_SWARM_CONTEXT_PACK_VERSION,
|
|
911
|
+
id: input.id ?? 'swarm-context-pack:' + stableHash([input.job?.id, task?.id, files, apiMap, generatedAt]),
|
|
912
|
+
...(input.job ? { jobId: input.job.id } : {}),
|
|
913
|
+
...(task ? { taskId: task.id } : {}),
|
|
914
|
+
...(input.job?.lane ?? task?.lane ? { lane: input.job?.lane ?? task?.lane } : {}),
|
|
915
|
+
title: input.title ?? input.job?.title ?? task?.title ?? 'Swarm Context Pack',
|
|
916
|
+
generatedAt,
|
|
917
|
+
files,
|
|
918
|
+
apiMap,
|
|
919
|
+
knownFailures: uniqueStrings(input.knownFailures ?? []),
|
|
920
|
+
commands,
|
|
921
|
+
oracleCommands: commands,
|
|
922
|
+
...(input.evidenceSchema !== undefined ? { evidenceSchema: toJsonValue(input.evidenceSchema) } : {}),
|
|
923
|
+
expectedEvidence,
|
|
924
|
+
exclusions: uniqueStrings(input.exclusions ?? []),
|
|
925
|
+
avoidInvestigating: uniqueStrings(input.avoidInvestigating ?? []),
|
|
926
|
+
playbookIds: uniqueStrings(input.playbookIds ?? []),
|
|
927
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
export function createSwarmOracleCorpus(input = {}) {
|
|
931
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
932
|
+
const artifacts = (input.artifacts ?? []).map(normalizeOracleArtifact).sort((left, right) => left.id.localeCompare(right.id));
|
|
933
|
+
const byKind = groupArtifactIdsBy(artifacts, (artifact) => [artifact.kind]);
|
|
934
|
+
const byTag = groupArtifactIdsBy(artifacts, (artifact) => artifact.tags);
|
|
935
|
+
return {
|
|
936
|
+
kind: FRONTIER_SWARM_ORACLE_CORPUS_KIND,
|
|
937
|
+
version: FRONTIER_SWARM_ORACLE_CORPUS_VERSION,
|
|
938
|
+
id: input.id ?? 'swarm-oracle-corpus:' + stableHash([artifacts, generatedAt]),
|
|
939
|
+
title: input.title ?? titleFromId(input.id ?? 'oracle corpus'),
|
|
940
|
+
generatedAt,
|
|
941
|
+
artifacts,
|
|
942
|
+
byKind,
|
|
943
|
+
byTag,
|
|
944
|
+
summary: {
|
|
945
|
+
artifactCount: artifacts.length,
|
|
946
|
+
kindCount: Object.keys(byKind).length,
|
|
947
|
+
tagCount: Object.keys(byTag).length
|
|
948
|
+
},
|
|
949
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
export function createSwarmLanePlaybook(input) {
|
|
953
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
954
|
+
const successful = (input.successfulBundles ?? []).filter((bundle) => bundle.status === 'completed' || bundle.status === 'verified' || bundle.autoMergeable);
|
|
955
|
+
const hotPaths = createSwarmHotspotReport({ bundles: successful, threshold: 2, generatedAt }).entries
|
|
956
|
+
.filter((entry) => entry.touchCount >= 2)
|
|
957
|
+
.map((entry) => entry.path);
|
|
958
|
+
return {
|
|
959
|
+
kind: FRONTIER_SWARM_LANE_PLAYBOOK_KIND,
|
|
960
|
+
version: FRONTIER_SWARM_LANE_PLAYBOOK_VERSION,
|
|
961
|
+
id: input.id ?? 'swarm-lane-playbook:' + stableHash([input.lane, successful.map((bundle) => bundle.jobId), input.notes, generatedAt]),
|
|
962
|
+
lane: normalizeId(input.lane, 'playbook lane'),
|
|
963
|
+
title: input.title ?? `${titleFromId(input.lane)} Playbook`,
|
|
964
|
+
generatedAt,
|
|
965
|
+
notes: uniqueStrings(input.notes ?? []),
|
|
966
|
+
commands: normalizeCommands(input.commands ?? []),
|
|
967
|
+
avoidInvestigating: uniqueStrings(input.avoidInvestigating ?? []),
|
|
968
|
+
evidencePatterns: uniqueStrings(input.evidencePatterns ?? successful.flatMap((bundle) => bundle.evidencePaths)),
|
|
969
|
+
successfulJobIds: uniqueStrings(successful.map((bundle) => bundle.jobId)),
|
|
970
|
+
hotPaths,
|
|
971
|
+
changedRegions: uniqueStrings(successful.flatMap((bundle) => bundle.changedRegions)),
|
|
972
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
export function createSwarmPatchStackPlan(input) {
|
|
976
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
977
|
+
const maxStackSize = Math.max(1, Math.floor(input.maxStackSize ?? 8));
|
|
978
|
+
const groups = new Map();
|
|
979
|
+
for (const entry of input.index.entries) {
|
|
980
|
+
const key = patchStackKey(entry);
|
|
981
|
+
groups.set(key, [...(groups.get(key) ?? []), entry]);
|
|
982
|
+
}
|
|
983
|
+
const stacks = [];
|
|
984
|
+
for (const [key, entries] of Array.from(groups.entries()).sort((left, right) => left[0].localeCompare(right[0]))) {
|
|
985
|
+
const sorted = [...entries].sort((left, right) => riskRank(left.riskLevel) - riskRank(right.riskLevel) || left.jobId.localeCompare(right.jobId));
|
|
986
|
+
for (let index = 0; index < sorted.length; index += maxStackSize) {
|
|
987
|
+
const slice = sorted.slice(index, index + maxStackSize);
|
|
988
|
+
const jobIds = slice.map((entry) => entry.jobId);
|
|
989
|
+
const conflicts = input.index.conflicts.filter((conflict) => conflict.jobIds.some((jobId) => jobIds.includes(jobId)));
|
|
990
|
+
stacks.push({
|
|
991
|
+
id: 'swarm-patch-stack:' + stableHash([input.index.id, key, index, jobIds]),
|
|
992
|
+
title: titleFromId(key),
|
|
993
|
+
...(slice[0]?.lane ? { lane: slice[0].lane } : {}),
|
|
994
|
+
jobIds,
|
|
995
|
+
changedPaths: uniqueStrings(slice.flatMap((entry) => entry.changedPaths)),
|
|
996
|
+
changedRegions: uniqueStrings(slice.flatMap((entry) => entry.changedRegions)),
|
|
997
|
+
riskLevels: uniqueStrings(slice.map((entry) => entry.riskLevel)),
|
|
998
|
+
dispositions: uniqueStrings(slice.map((entry) => entry.disposition)),
|
|
999
|
+
conflicts,
|
|
1000
|
+
gateHints: uniqueStrings(slice.flatMap((entry) => entry.evidencePaths.filter((file) => file.endsWith('.json') || file.endsWith('.jsonl'))))
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
return {
|
|
1005
|
+
kind: FRONTIER_SWARM_PATCH_STACK_PLAN_KIND,
|
|
1006
|
+
version: FRONTIER_SWARM_PATCH_STACK_PLAN_VERSION,
|
|
1007
|
+
id: input.id ?? 'swarm-patch-stack-plan:' + stableHash([input.index.id, stacks, generatedAt]),
|
|
1008
|
+
mergeIndexId: input.index.id,
|
|
1009
|
+
generatedAt,
|
|
1010
|
+
stacks,
|
|
1011
|
+
summary: {
|
|
1012
|
+
stackCount: stacks.length,
|
|
1013
|
+
jobCount: input.index.entries.length,
|
|
1014
|
+
conflictedStackCount: stacks.filter((stack) => stack.conflicts.length > 0).length
|
|
1015
|
+
},
|
|
1016
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
429
1019
|
export function resolveSwarmCompute(manifestInput, taskInput) {
|
|
430
1020
|
const compiled = compileSwarm(manifestInput);
|
|
431
1021
|
const task = isSwarmTask(taskInput) ? taskInput : normalizeTask(taskInput);
|
|
@@ -900,31 +1490,188 @@ function selectReviewers(pool, required, salt) {
|
|
|
900
1490
|
return sorted.slice(0, Math.min(required, sorted.length));
|
|
901
1491
|
}
|
|
902
1492
|
function conflictMap(results) {
|
|
903
|
-
const
|
|
904
|
-
for (
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
1493
|
+
const conflicts = new Map();
|
|
1494
|
+
for (let leftIndex = 0; leftIndex < results.length; leftIndex += 1) {
|
|
1495
|
+
for (let rightIndex = leftIndex + 1; rightIndex < results.length; rightIndex += 1) {
|
|
1496
|
+
const left = results[leftIndex];
|
|
1497
|
+
const right = results[rightIndex];
|
|
1498
|
+
if (!left || !right || pairConflictKeys(left, right).length === 0)
|
|
1499
|
+
continue;
|
|
1500
|
+
const leftConflicts = conflicts.get(left.jobId) ?? new Set();
|
|
1501
|
+
const rightConflicts = conflicts.get(right.jobId) ?? new Set();
|
|
1502
|
+
leftConflicts.add(right.jobId);
|
|
1503
|
+
rightConflicts.add(left.jobId);
|
|
1504
|
+
conflicts.set(left.jobId, leftConflicts);
|
|
1505
|
+
conflicts.set(right.jobId, rightConflicts);
|
|
912
1506
|
}
|
|
913
1507
|
}
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1508
|
+
return conflicts;
|
|
1509
|
+
}
|
|
1510
|
+
function queueOverlayStatusFromBundle(bundle) {
|
|
1511
|
+
if (bundle.staleAgainstHead || bundle.disposition === 'stale-against-head')
|
|
1512
|
+
return 'stale-against-head';
|
|
1513
|
+
if (bundle.disposition === 'rejected' || bundle.disposition === 'blocked' || bundle.status === 'failed' || bundle.commandsFailed.length > 0) {
|
|
1514
|
+
return 'failed-evidence';
|
|
1515
|
+
}
|
|
1516
|
+
if (bundle.disposition === 'auto-mergeable' && bundle.autoMergeable)
|
|
1517
|
+
return 'ready-to-apply';
|
|
1518
|
+
if (bundle.disposition === 'needs-port')
|
|
1519
|
+
return 'needs-human-port';
|
|
1520
|
+
if (bundle.disposition === 'discovery-only')
|
|
1521
|
+
return 'discovery-only';
|
|
1522
|
+
if (bundle.mergeReadiness === 'blocked')
|
|
1523
|
+
return 'blocked';
|
|
1524
|
+
if (bundle.mergeReadiness === 'rejected')
|
|
1525
|
+
return 'rejected';
|
|
1526
|
+
return 'unknown';
|
|
1527
|
+
}
|
|
1528
|
+
function queueOverlayStatusFromResult(result) {
|
|
1529
|
+
if (result.mergeDisposition === 'stale-against-head')
|
|
1530
|
+
return 'stale-against-head';
|
|
1531
|
+
if (result.status === 'failed' || result.exitCode !== undefined && result.exitCode !== 0 || result.ownershipViolations.length > 0)
|
|
1532
|
+
return 'failed-evidence';
|
|
1533
|
+
if (result.mergeDisposition === 'auto-mergeable')
|
|
1534
|
+
return 'ready-to-apply';
|
|
1535
|
+
if (result.mergeDisposition === 'needs-port')
|
|
1536
|
+
return 'needs-human-port';
|
|
1537
|
+
if (result.mergeDisposition === 'discovery-only')
|
|
1538
|
+
return 'discovery-only';
|
|
1539
|
+
if (result.status === 'blocked')
|
|
1540
|
+
return 'blocked';
|
|
1541
|
+
return 'unknown';
|
|
1542
|
+
}
|
|
1543
|
+
function queueJobStatusFromOverlay(entry) {
|
|
1544
|
+
if (entry.status === 'ready-to-apply' || entry.status === 'discovery-only')
|
|
1545
|
+
return 'completed';
|
|
1546
|
+
if (entry.status === 'needs-human-port')
|
|
1547
|
+
return 'blocked';
|
|
1548
|
+
if (entry.status === 'failed-evidence' || entry.status === 'rejected' || entry.status === 'stale-against-head')
|
|
1549
|
+
return 'failed';
|
|
1550
|
+
if (entry.status === 'blocked')
|
|
1551
|
+
return 'blocked';
|
|
1552
|
+
return 'completed';
|
|
1553
|
+
}
|
|
1554
|
+
function groupOverlayEntries(entries) {
|
|
1555
|
+
const out = {};
|
|
1556
|
+
for (const entry of entries)
|
|
1557
|
+
out[entry.queueItemId] = [...(out[entry.queueItemId] ?? []), entry];
|
|
1558
|
+
for (const key of Object.keys(out)) {
|
|
1559
|
+
out[key] = [...(out[key] ?? [])].sort((left, right) => right.generatedAt - left.generatedAt || left.jobId.localeCompare(right.jobId));
|
|
1560
|
+
}
|
|
1561
|
+
return out;
|
|
1562
|
+
}
|
|
1563
|
+
function mergeIndexConflictKeys(bundle) {
|
|
1564
|
+
return bundle.changedRegions.length
|
|
1565
|
+
? bundle.changedRegions.map((region) => `region:${region}`).sort()
|
|
1566
|
+
: bundle.changedPaths.map((file) => `path:${file}`).sort();
|
|
1567
|
+
}
|
|
1568
|
+
function createMergeIndexConflicts(entries) {
|
|
1569
|
+
const conflicts = [];
|
|
1570
|
+
for (let leftIndex = 0; leftIndex < entries.length; leftIndex += 1) {
|
|
1571
|
+
for (let rightIndex = leftIndex + 1; rightIndex < entries.length; rightIndex += 1) {
|
|
1572
|
+
const left = entries[leftIndex];
|
|
1573
|
+
const right = entries[rightIndex];
|
|
1574
|
+
if (!left || !right)
|
|
1575
|
+
continue;
|
|
1576
|
+
const keys = pairConflictKeys(left, right);
|
|
1577
|
+
for (const key of keys) {
|
|
1578
|
+
const kind = key.startsWith('region:') ? 'region' : 'path';
|
|
1579
|
+
const value = key.slice(key.indexOf(':') + 1);
|
|
1580
|
+
conflicts.push({
|
|
1581
|
+
jobIds: [left.jobId, right.jobId].sort(),
|
|
1582
|
+
key,
|
|
1583
|
+
kind,
|
|
1584
|
+
...(kind === 'region' ? { region: value } : { path: value })
|
|
1585
|
+
});
|
|
923
1586
|
}
|
|
924
|
-
conflicts.set(jobId, set);
|
|
925
1587
|
}
|
|
926
1588
|
}
|
|
927
|
-
|
|
1589
|
+
const deduped = new Map();
|
|
1590
|
+
for (const conflict of conflicts)
|
|
1591
|
+
deduped.set(`${conflict.key}:${conflict.jobIds.join(',')}`, conflict);
|
|
1592
|
+
return Array.from(deduped.values()).sort((left, right) => left.key.localeCompare(right.key) || left.jobIds.join(',').localeCompare(right.jobIds.join(',')));
|
|
1593
|
+
}
|
|
1594
|
+
function pairConflictKeys(left, right) {
|
|
1595
|
+
if (left.changedRegions.length > 0 && right.changedRegions.length > 0) {
|
|
1596
|
+
const rightRegions = new Set(right.changedRegions);
|
|
1597
|
+
return left.changedRegions.filter((region) => rightRegions.has(region)).map((region) => `region:${region}`).sort();
|
|
1598
|
+
}
|
|
1599
|
+
const rightPaths = new Set(right.changedPaths);
|
|
1600
|
+
return left.changedPaths.filter((file) => rightPaths.has(file)).map((file) => `path:${file}`).sort();
|
|
1601
|
+
}
|
|
1602
|
+
function groupJobIdsBy(items, key) {
|
|
1603
|
+
const out = {};
|
|
1604
|
+
for (const item of items)
|
|
1605
|
+
out[key(item)] = uniqueStrings([...(out[key(item)] ?? []), item.jobId]);
|
|
1606
|
+
return out;
|
|
1607
|
+
}
|
|
1608
|
+
function groupJobIdsByMany(items, key) {
|
|
1609
|
+
const out = {};
|
|
1610
|
+
for (const item of items) {
|
|
1611
|
+
for (const value of key(item))
|
|
1612
|
+
out[value] = uniqueStrings([...(out[value] ?? []), item.jobId]);
|
|
1613
|
+
}
|
|
1614
|
+
return out;
|
|
1615
|
+
}
|
|
1616
|
+
function suggestedModuleId(file) {
|
|
1617
|
+
const base = file.split('/').pop()?.replace(/\.[^.]+$/, '') ?? file;
|
|
1618
|
+
return slug(base).replace(/-/g, '.');
|
|
1619
|
+
}
|
|
1620
|
+
function reviewerLaneReasons(entry) {
|
|
1621
|
+
const reasons = [];
|
|
1622
|
+
if (entry.conflictingJobIds.length)
|
|
1623
|
+
reasons.push('conflicting-changes');
|
|
1624
|
+
if (entry.riskLevel === 'high')
|
|
1625
|
+
reasons.push('high-risk');
|
|
1626
|
+
if (entry.disposition !== 'auto-mergeable')
|
|
1627
|
+
reasons.push(entry.disposition);
|
|
1628
|
+
if (!entry.autoMergeable)
|
|
1629
|
+
reasons.push('not-auto-mergeable');
|
|
1630
|
+
if (entry.staleAgainstHead)
|
|
1631
|
+
reasons.push('stale-against-head');
|
|
1632
|
+
return uniqueStrings(reasons);
|
|
1633
|
+
}
|
|
1634
|
+
function hashBucket(value, buckets) {
|
|
1635
|
+
const hex = stableHash(value).split(':')[1] ?? '0';
|
|
1636
|
+
return parseInt(hex, 16) % Math.max(1, buckets);
|
|
1637
|
+
}
|
|
1638
|
+
function normalizeOracleArtifact(input) {
|
|
1639
|
+
return {
|
|
1640
|
+
id: normalizeId(input.id, 'oracle artifact id'),
|
|
1641
|
+
path: normalizeId(input.path, 'oracle artifact path'),
|
|
1642
|
+
kind: input.kind ?? 'oracle',
|
|
1643
|
+
...(input.command ? { command: typeof input.command === 'string' ? normalizeCommands([input.command])[0] : normalizeCommands([input.command])[0] } : {}),
|
|
1644
|
+
...(input.hash ? { hash: input.hash } : {}),
|
|
1645
|
+
...(input.sourceRef ? { sourceRef: input.sourceRef } : {}),
|
|
1646
|
+
tags: uniqueStrings(input.tags ?? []),
|
|
1647
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
1648
|
+
};
|
|
1649
|
+
}
|
|
1650
|
+
function groupArtifactIdsBy(artifacts, key) {
|
|
1651
|
+
const out = {};
|
|
1652
|
+
for (const artifact of artifacts) {
|
|
1653
|
+
for (const value of key(artifact))
|
|
1654
|
+
out[value] = uniqueStrings([...(out[value] ?? []), artifact.id]);
|
|
1655
|
+
}
|
|
1656
|
+
return out;
|
|
1657
|
+
}
|
|
1658
|
+
function patchStackKey(entry) {
|
|
1659
|
+
const lane = entry.lane ?? 'unassigned';
|
|
1660
|
+
if (entry.changedRegions.length)
|
|
1661
|
+
return `${lane}:${entry.changedRegions[0]}`;
|
|
1662
|
+
const firstPath = entry.changedPaths[0] ?? 'evidence-only';
|
|
1663
|
+
return `${lane}:${firstPath.split('/').slice(0, 2).join('/') || firstPath}`;
|
|
1664
|
+
}
|
|
1665
|
+
function riskRank(risk) {
|
|
1666
|
+
if (risk === 'low')
|
|
1667
|
+
return 0;
|
|
1668
|
+
if (risk === 'medium')
|
|
1669
|
+
return 1;
|
|
1670
|
+
if (risk === 'unknown')
|
|
1671
|
+
return 2;
|
|
1672
|
+
if (risk === 'high')
|
|
1673
|
+
return 3;
|
|
1674
|
+
return 4;
|
|
928
1675
|
}
|
|
929
1676
|
function groupMergeReadyJobs(ready, results) {
|
|
930
1677
|
const byJob = new Map(results.map((result) => [result.jobId, result]));
|