@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/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 byPath = new Map();
904
- for (const result of results) {
905
- const keys = result.changedRegions.length
906
- ? result.changedRegions.map((region) => `region:${region}`)
907
- : result.changedPaths.map((file) => `file:${file}`);
908
- for (const key of keys) {
909
- const list = byPath.get(key) ?? [];
910
- list.push(result.jobId);
911
- byPath.set(key, list);
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
- const conflicts = new Map();
915
- for (const jobIds of byPath.values()) {
916
- if (jobIds.length < 2)
917
- continue;
918
- for (const jobId of jobIds) {
919
- const set = conflicts.get(jobId) ?? new Set();
920
- for (const other of jobIds) {
921
- if (other !== jobId)
922
- set.add(other);
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
- return conflicts;
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]));