@hcrosse/opencode-pr-tracker 0.4.2 → 0.4.4

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 CHANGED
@@ -52,7 +52,7 @@ Agents get three tools in the `pr` namespace: `pr.list`, `pr.attach` and `pr.det
52
52
 
53
53
  Each attached pull request shows its repository, number, status, and title. Open the sidebar with ctrl+x b if your terminal is narrow enough to hide it. Click a row to open its pull request. With more than two pull requests attached, click the **Pull requests** heading to collapse or expand the list.
54
54
 
55
- Stack members appear together in Stack order, joined by `┌─`, `├─` and `└─`. `├┄ 2 PRs not attached` marks Stack members between attached ones. Other pull requests use `•`.
55
+ Stack members appear together in Stack order, including pull requests linked into a Stack after they were attached, joined by `┌─`, `├─` and `└─`. `├┄ 2 PRs not attached` marks Stack members between attached ones. Other pull requests use `•`.
56
56
 
57
57
  | Status | Appearance |
58
58
  | --------------- | ---------------------- |
package/dist/server.js CHANGED
@@ -607,19 +607,38 @@ class AmbiguousPullRequestNumber extends Schema8.TaggedError()("AmbiguousPullReq
607
607
  function isMember(stack, attachment) {
608
608
  return stack.some((ref) => samePullRequest(ref, attachment.ref));
609
609
  }
610
- function attach(tracking, stack, now) {
610
+ var differs = (before, after) => after.length !== before.length || after.some((attachment, index) => attachment !== before[index]);
611
+ function groupOne(tracking, stack) {
611
612
  const members = Arr4.dedupeWith(stack, samePullRequest);
613
+ const insertAt = tracking.findIndex((attachment) => isMember(members, attachment));
614
+ if (insertAt === -1)
615
+ return tracking;
612
616
  const others = tracking.filter((attachment) => !isMember(members, attachment));
613
- const requested = others.length + members.length;
617
+ const placed = members.flatMap((ref) => Option7.toArray(Arr4.findFirst(tracking, (attachment) => samePullRequest(attachment.ref, ref))));
618
+ return [...others.slice(0, insertAt), ...placed, ...others.slice(insertAt)];
619
+ }
620
+ function group(tracking, stacks) {
621
+ const grouped = new Set;
622
+ let next = tracking;
623
+ for (const stack of stacks) {
624
+ if (stack.some((ref) => grouped.has(ref.url)))
625
+ continue;
626
+ for (const ref of stack)
627
+ grouped.add(ref.url);
628
+ next = groupOne(next, stack);
629
+ }
630
+ return { changed: differs(tracking, next), tracking: next };
631
+ }
632
+ function attach(tracking, stack, now) {
633
+ const members = Arr4.dedupeWith(stack, samePullRequest);
634
+ const missing = members.filter((ref) => !tracking.some((attachment) => samePullRequest(attachment.ref, ref)));
635
+ const requested = tracking.length + missing.length;
614
636
  if (requested > maximumAttachments) {
615
637
  return Result5.fail(new AttachmentLimitReached({ limit: maximumAttachments, requested }));
616
638
  }
617
- const position = tracking.findIndex((attachment) => isMember(members, attachment));
618
- const insertAt = position === -1 ? others.length : position;
619
- const placed = members.map((ref) => Option7.getOrElse(Arr4.findFirst(tracking, (attachment) => samePullRequest(attachment.ref, ref)), () => new Attachment({ attachedAt: now, ref })));
620
- const next = [...others.slice(0, insertAt), ...placed, ...others.slice(insertAt)];
621
- const changed = next.length !== tracking.length || next.some((attachment, index) => attachment !== tracking[index]);
622
- return Result5.succeed({ changed, tracking: next });
639
+ const appended = [...tracking, ...missing.map((ref) => new Attachment({ attachedAt: now, ref }))];
640
+ const next = group(appended, [members]).tracking;
641
+ return Result5.succeed({ changed: differs(tracking, next), tracking: next });
623
642
  }
624
643
  function detach(tracking, ref) {
625
644
  const next = tracking.filter((attachment) => !samePullRequest(attachment.ref, ref));
@@ -685,14 +704,126 @@ function layer4(storage) {
685
704
  }
686
705
 
687
706
  // src/application/Monitor.ts
688
- import { Context as Context6, Effect as Effect10, Layer as Layer6, Option as Option14, PubSub, Ref as Ref2, Result as Result7, Stream } from "effect";
707
+ import { Context as Context6, Effect as Effect10, Layer as Layer6, Option as Option15, PubSub, Ref as Ref2, Result as Result7, Stream } from "effect";
708
+
709
+ // src/domain/StackLayout.ts
710
+ import { Array as Arr6, Option as Option9, Schema as Schema11 } from "effect";
711
+ var Membership = Schema11.Union([
712
+ Schema11.TaggedStruct("Standalone", {}),
713
+ Schema11.TaggedStruct("Stack", {
714
+ id: Schema11.String,
715
+ members: Schema11.NonEmptyArray(PullRequestRef)
716
+ })
717
+ ]);
718
+ var stackOf = (entry) => Option9.filter(entry.membership, (membership) => membership._tag === "Stack");
719
+ var urlsOf = (stack) => stack.members.map((member) => member.url);
720
+ function reportsById(entries) {
721
+ const byId = new Map;
722
+ for (const [index, entry] of entries.entries()) {
723
+ for (const stack of Option9.toArray(stackOf(entry))) {
724
+ byId.set(stack.id, [...byId.get(stack.id) ?? [], { index, stack, url: entry.ref.url }]);
725
+ }
726
+ }
727
+ return byId;
728
+ }
729
+ function claims(byId) {
730
+ const claimed = new Map;
731
+ for (const [id, reports] of byId) {
732
+ for (const url of reports.flatMap((report) => urlsOf(report.stack))) {
733
+ claimed.set(url, new Set([...claimed.get(url) ?? [], id]));
734
+ }
735
+ }
736
+ return claimed;
737
+ }
738
+ function agrees(reports, entries, claimed) {
739
+ const members = Option9.match(Arr6.head(reports), {
740
+ onNone: () => [],
741
+ onSome: (report) => urlsOf(report.stack)
742
+ });
743
+ const listed = new Set(members);
744
+ const attachedMembers = entries.filter((entry) => listed.has(entry.ref.url));
745
+ return listed.size === members.length && reports.every((report) => urlsOf(report.stack).join(`
746
+ `) === members.join(`
747
+ `)) && members.every((url) => (claimed.get(url) ?? new Set).size === 1) && reports.every((report) => listed.has(report.url)) && attachedMembers.length === reports.length;
748
+ }
749
+ function placements(reports) {
750
+ const placed = reports.map((report) => ({
751
+ index: report.index,
752
+ position: urlsOf(report.stack).indexOf(report.url),
753
+ size: report.stack.members.length
754
+ }));
755
+ const adjacent = Arr6.zipWith(placed, placed.slice(1), (before, after) => after.index === before.index + 1 && after.position > before.position);
756
+ const ordered = placed.every((member) => member.position >= 0) && adjacent.every(Boolean);
757
+ return ordered ? Option9.some(placed) : Option9.none();
758
+ }
759
+ function agreedReports(entries) {
760
+ const byId = reportsById(entries);
761
+ const claimed = claims(byId);
762
+ return [...byId.values()].filter((reports) => agrees(reports, entries, claimed));
763
+ }
764
+ function agreedStacks(entries) {
765
+ return agreedReports(entries).flatMap((reports) => Option9.toArray(Option9.map(Arr6.head(reports), (report) => report.stack)));
766
+ }
767
+ function consistentStacks(entries) {
768
+ const places = new Map;
769
+ for (const reports of agreedReports(entries)) {
770
+ const placed = placements(reports);
771
+ for (const group of Option9.toArray(placed)) {
772
+ for (const [step, current] of group.entries()) {
773
+ places.set(current.index, {
774
+ attached: group.length,
775
+ current,
776
+ first: step === 0,
777
+ last: step === group.length - 1,
778
+ previous: Arr6.get(group, step - 1)
779
+ });
780
+ }
781
+ }
782
+ }
783
+ return places;
784
+ }
785
+ function marker(place) {
786
+ const { current } = place;
787
+ if (place.attached === 1 && current.size > 1)
788
+ return "middle";
789
+ if (place.first && current.position === 0)
790
+ return "first";
791
+ if (place.last && current.position === current.size - 1)
792
+ return "last";
793
+ return "middle";
794
+ }
795
+ function connector(place) {
796
+ if (!place.last)
797
+ return "continues";
798
+ return place.current.position < place.current.size - 1 ? "open" : "none";
799
+ }
800
+ function stackRows(entry, place) {
801
+ const skipped = Option9.match(place.previous, {
802
+ onNone: () => 0,
803
+ onSome: (before) => place.current.position - before.position - 1
804
+ });
805
+ const row = {
806
+ _tag: "PullRequest",
807
+ connector: connector(place),
808
+ entry,
809
+ marker: marker(place)
810
+ };
811
+ return skipped > 0 ? [{ _tag: "Gap", count: skipped }, row] : [row];
812
+ }
813
+ function layout(entries) {
814
+ const places = consistentStacks(entries);
815
+ return entries.flatMap((entry, index) => Option9.match(Option9.fromNullishOr(places.get(index)), {
816
+ onNone: () => [{ _tag: "PullRequest", connector: "none", entry, marker: "bullet" }],
817
+ onSome: (place) => stackRows(entry, place)
818
+ }));
819
+ }
689
820
 
690
821
  // src/application/FetchQueue.ts
691
- import { Deferred, Effect as Effect6, Exit, Option as Option9 } from "effect";
822
+ import { Deferred, Effect as Effect6, Exit, Option as Option10 } from "effect";
692
823
 
693
824
  class FetchQueue {
694
825
  running = false;
695
- queued = Option9.none();
826
+ queued = Option10.none();
696
827
  fetchNow;
697
828
  scope;
698
829
  constructor(fetchNow, scope) {
@@ -711,33 +842,33 @@ class FetchQueue {
711
842
  });
712
843
  }
713
844
  join(refs) {
714
- const next = Option9.getOrElse(this.queued, () => ({
845
+ const next = Option10.getOrElse(this.queued, () => ({
715
846
  done: Deferred.makeUnsafe(),
716
847
  refs: new Map
717
848
  }));
718
849
  for (const ref of refs)
719
850
  next.refs.set(ref.url, ref);
720
- this.queued = Option9.some(next);
851
+ this.queued = Option10.some(next);
721
852
  return next.done;
722
853
  }
723
854
  drain(refs, done) {
724
855
  return this.fetchNow(refs).pipe(Effect6.andThen(Deferred.succeed(done, true)), Effect6.andThen(Effect6.suspend(() => this.next())), Effect6.onExit((exit) => Exit.isSuccess(exit) ? Effect6.void : Effect6.suspend(() => this.reset(done, exit.cause))));
725
856
  }
726
857
  next() {
727
- return Option9.match(this.queued, {
858
+ return Option10.match(this.queued, {
728
859
  onNone: () => Effect6.sync(() => {
729
860
  this.running = false;
730
861
  }),
731
862
  onSome: (queued) => {
732
- this.queued = Option9.none();
863
+ this.queued = Option10.none();
733
864
  return this.drain([...queued.refs.values()], queued.done);
734
865
  }
735
866
  });
736
867
  }
737
868
  reset(done, cause) {
738
- const waiting = [done, ...Option9.toArray(Option9.map(this.queued, (queued) => queued.done))];
869
+ const waiting = [done, ...Option10.toArray(Option10.map(this.queued, (queued) => queued.done))];
739
870
  this.running = false;
740
- this.queued = Option9.none();
871
+ this.queued = Option10.none();
741
872
  return Effect6.forEach(waiting, (deferred) => Deferred.failCause(deferred, cause), {
742
873
  discard: true
743
874
  });
@@ -745,28 +876,66 @@ class FetchQueue {
745
876
  }
746
877
 
747
878
  // src/application/Known.ts
748
- import { Duration as Duration3, Option as Option11 } from "effect";
879
+ import { Duration as Duration3, Option as Option12 } from "effect";
749
880
 
750
881
  // src/domain/RefreshPolicy.ts
751
- import { Duration as Duration2, Option as Option10 } from "effect";
882
+ import { Duration as Duration2, Option as Option11 } from "effect";
752
883
  var refreshInterval = Duration2.seconds(15);
753
884
  function nextRefresh(status) {
754
- const merged = (status._tag === "Fresh" || status._tag === "Stale") && status.snapshot.state._tag === "Merged";
755
- return merged ? Option10.none() : Option10.some(refreshInterval);
885
+ const merged = status._tag === "Fresh" && status.snapshot.state._tag === "Merged";
886
+ return merged ? Option11.none() : Option11.some(refreshInterval);
756
887
  }
757
888
 
758
889
  // src/application/Known.ts
759
- var unknown = { dueAt: Option11.some(0), membership: Option11.none(), status: pending };
890
+ var unknown = { dueAt: Option12.some(0), membership: Option12.none(), status: pending };
760
891
  function afterRefresh(previous, result, now) {
761
892
  const status = result._tag === "Reported" ? succeeded(result.report.snapshot) : failed(previous.status, result.diagnostic, now);
762
893
  const membership = result._tag === "Reported" ? result.report.membership : previous.membership;
763
894
  return {
764
- dueAt: Option11.map(nextRefresh(status), (delay) => now + Duration3.toMillis(delay)),
895
+ dueAt: Option12.map(nextRefresh(status), (delay) => now + Duration3.toMillis(delay)),
765
896
  membership,
766
897
  status
767
898
  };
768
899
  }
769
- var isDue = (known, now, ref) => Option11.match((known.get(ref.url) ?? unknown).dueAt, {
900
+ var membersOf = (membership) => Option12.match(membership, {
901
+ onNone: () => [],
902
+ onSome: (known) => known._tag === "Stack" ? known.members : []
903
+ });
904
+ var keyOf2 = (membership) => Option12.match(membership, {
905
+ onNone: () => "",
906
+ onSome: (known) => known._tag === "Stack" ? [known.id, ...known.members.map((member) => member.url)].join(`
907
+ `) : "standalone"
908
+ });
909
+ var sameMembership = (left, right) => keyOf2(left) === keyOf2(right);
910
+ var outdated = (known, reported) => Option12.exists(known, (entry) => Option12.isNone(entry.dueAt) && !sameMembership(entry.membership, reported));
911
+ function contradicted(current, results) {
912
+ const urls = new Set;
913
+ for (const [url, result] of results) {
914
+ if (result._tag !== "Reported")
915
+ continue;
916
+ const reported = result.report.membership;
917
+ const previous = (current.get(url) ?? unknown).membership;
918
+ if (sameMembership(previous, reported))
919
+ continue;
920
+ for (const member of [...membersOf(previous), ...membersOf(reported)]) {
921
+ const known = Option12.fromNullishOr(current.get(member.url));
922
+ if (!results.has(member.url) && outdated(known, reported))
923
+ urls.add(member.url);
924
+ }
925
+ }
926
+ return urls;
927
+ }
928
+ function recorded(current, results, now) {
929
+ const next = new Map(current);
930
+ for (const [url, result] of results)
931
+ next.set(url, afterRefresh(current.get(url) ?? unknown, result, now));
932
+ for (const url of contradicted(current, results)) {
933
+ const { membership, status } = current.get(url) ?? unknown;
934
+ next.set(url, { dueAt: Option12.some(now), membership, status });
935
+ }
936
+ return next;
937
+ }
938
+ var isDue = (known, now, ref) => Option12.match((known.get(ref.url) ?? unknown).dueAt, {
770
939
  onNone: () => false,
771
940
  onSome: (at) => at <= now
772
941
  });
@@ -779,10 +948,10 @@ import { Clock, Effect as Effect7 } from "effect";
779
948
  var currentMillis = Effect7.map(Clock.currentTimeMillis, Math.floor);
780
949
 
781
950
  // src/application/Tracker.ts
782
- import { Array as Arr6, Context as Context5, Effect as Effect9, Layer as Layer5, Option as Option13, Schema as Schema11 } from "effect";
951
+ import { Array as Arr7, Context as Context5, Effect as Effect9, Layer as Layer5, Option as Option14, Schema as Schema12 } from "effect";
783
952
 
784
953
  // src/application/SessionLocks.ts
785
- import { Effect as Effect8, Option as Option12, Semaphore } from "effect";
954
+ import { Effect as Effect8, Option as Option13, Semaphore } from "effect";
786
955
 
787
956
  class SessionLocks {
788
957
  locks = new Map;
@@ -803,8 +972,8 @@ class SessionLocks {
803
972
  return semaphore;
804
973
  }
805
974
  leave(sessionID) {
806
- const current = Option12.fromUndefinedOr(this.locks.get(sessionID));
807
- if (Option12.isNone(current))
975
+ const current = Option13.fromUndefinedOr(this.locks.get(sessionID));
976
+ if (Option13.isNone(current))
808
977
  return;
809
978
  const { semaphore, users } = current.value;
810
979
  if (users === 1)
@@ -815,11 +984,11 @@ class SessionLocks {
815
984
  }
816
985
 
817
986
  // src/application/Tracker.ts
818
- class PullRequestUnavailable extends Schema11.TaggedError()("PullRequestUnavailable", { diagnostic: Diagnostic, url: Schema11.String }) {
987
+ class PullRequestUnavailable extends Schema12.TaggedError()("PullRequestUnavailable", { diagnostic: Diagnostic, url: Schema12.String }) {
819
988
  }
820
989
 
821
- class StackIncomplete extends Schema11.TaggedError()("StackIncomplete", {
822
- url: Schema11.String
990
+ class StackIncomplete extends Schema12.TaggedError()("StackIncomplete", {
991
+ url: Schema12.String
823
992
  }) {
824
993
  }
825
994
 
@@ -829,11 +998,11 @@ function discovered(ref, result) {
829
998
  if (result._tag === "Failed")
830
999
  return Effect9.fail(new PullRequestUnavailable({ diagnostic: result.diagnostic, url: ref.url }));
831
1000
  const { report } = result;
832
- return Option13.match(report.membership, {
1001
+ return Option14.match(report.membership, {
833
1002
  onNone: () => Effect9.fail(new StackIncomplete({ url: ref.url })),
834
1003
  onSome: (membership) => Effect9.succeed({
835
1004
  report,
836
- stack: membership._tag === "Stack" ? membership.members : Arr6.of(ref)
1005
+ stack: membership._tag === "Stack" ? membership.members : Arr7.of(ref)
837
1006
  })
838
1007
  });
839
1008
  }
@@ -860,10 +1029,16 @@ var attachTo = Effect9.fn("Tracker.attach")(function* (services, sessionID, targ
860
1029
  var detachFrom = Effect9.fn("Tracker.detach")(function* ({ repository }, sessionID, input) {
861
1030
  const current = yield* repository.load(sessionID);
862
1031
  const removal = input._tag === "Reference" ? detach(current, input.ref) : yield* Effect9.fromResult(detachNumber(current, input.number));
863
- if (Option13.isSome(removal.removed))
1032
+ if (Option14.isSome(removal.removed))
864
1033
  yield* repository.save(sessionID, removal.tracking);
865
1034
  return removal;
866
1035
  });
1036
+ var regroupIn = Effect9.fn("Tracker.regroup")(function* ({ repository }, sessionID, stacks) {
1037
+ const change = group(yield* repository.load(sessionID), stacks);
1038
+ if (change.changed)
1039
+ yield* repository.save(sessionID, change.tracking);
1040
+ return change.tracking;
1041
+ });
867
1042
  var layer5 = Layer5.effect(Tracker, Effect9.gen(function* () {
868
1043
  const services = { github: yield* GitHub, repository: yield* TrackingRepository };
869
1044
  const locks = new SessionLocks;
@@ -871,7 +1046,8 @@ var layer5 = Layer5.effect(Tracker, Effect9.gen(function* () {
871
1046
  attach: (sessionID, input, directory) => locks.run(sessionID, attachTo(services, sessionID, { directory, input })),
872
1047
  detach: (sessionID, input) => locks.run(sessionID, detachFrom(services, sessionID, input)),
873
1048
  forget: (sessionID) => locks.run(sessionID, services.repository.remove(sessionID)),
874
- list: (sessionID) => services.repository.load(sessionID)
1049
+ list: (sessionID) => services.repository.load(sessionID),
1050
+ regroup: (sessionID, stacks) => locks.run(sessionID, regroupIn(services, sessionID, stacks))
875
1051
  });
876
1052
  }));
877
1053
 
@@ -887,16 +1063,11 @@ function entryOf(known, attachment) {
887
1063
  status: current.status
888
1064
  };
889
1065
  }
890
- var urlsOf = (tracking) => tracking.map((attachment) => attachment.ref.url);
1066
+ var urlsOf2 = (tracking) => tracking.map((attachment) => attachment.ref.url);
891
1067
  function remember(cache, results) {
892
1068
  return Effect10.gen(function* () {
893
1069
  const now = yield* currentMillis;
894
- yield* Ref2.update(cache.known, (current) => {
895
- const next = new Map(current);
896
- for (const [url, result] of results)
897
- next.set(url, afterRefresh(current.get(url) ?? unknown, result, now));
898
- return next;
899
- });
1070
+ yield* Ref2.update(cache.known, (current) => recorded(current, results, now));
900
1071
  });
901
1072
  }
902
1073
  function update(cache, refs) {
@@ -908,8 +1079,14 @@ function update(cache, refs) {
908
1079
  }
909
1080
  function viewOf(state, sessionID) {
910
1081
  return Effect10.gen(function* () {
911
- const tracking = yield* state.tracker.list(sessionID);
1082
+ const stored = yield* state.tracker.list(sessionID);
912
1083
  const current = yield* Ref2.get(state.known);
1084
+ const entries = stored.map((attachment) => entryOf(current, attachment));
1085
+ const stacks = agreedStacks(entries).map((stack) => stack.members);
1086
+ if (!group(stored, stacks).changed)
1087
+ return { entries, sessionID };
1088
+ const regrouped = Effect10.orElseSucceed(state.tracker.regroup(sessionID, stacks), () => stored);
1089
+ const tracking = yield* regrouped;
913
1090
  return { entries: tracking.map((attachment) => entryOf(current, attachment)), sessionID };
914
1091
  });
915
1092
  }
@@ -931,7 +1108,7 @@ function poll(state) {
931
1108
  return;
932
1109
  yield* state.fetch(due);
933
1110
  const dueUrls = new Set(due.map((ref) => ref.url));
934
- const affected = sessions.filter((_, index) => urlsOf(trackings[index] ?? []).some((url) => dueUrls.has(url)));
1111
+ const affected = sessions.filter((_, index) => urlsOf2(trackings[index] ?? []).some((url) => dueUrls.has(url)));
935
1112
  yield* Effect10.forEach(affected, (sessionID) => publish(state, sessionID), {
936
1113
  discard: true
937
1114
  });
@@ -942,14 +1119,14 @@ function fetchAndShow(state, sessionID, select) {
942
1119
  yield* use(state, sessionID);
943
1120
  const tracking = yield* state.tracker.list(sessionID);
944
1121
  const known = yield* Ref2.get(state.known);
945
- yield* state.fetch(tracking.map((attachment) => attachment.ref).filter((ref) => select(Option14.fromNullishOr(known.get(ref.url)))));
1122
+ yield* state.fetch(tracking.map((attachment) => attachment.ref).filter((ref) => select(Option15.fromNullishOr(known.get(ref.url)))));
946
1123
  const view = yield* viewOf(state, sessionID);
947
1124
  yield* PubSub.publish(state.published, view);
948
1125
  return view;
949
1126
  });
950
1127
  }
951
- var refreshable = (known) => Option14.isSome(Option14.getOrElse(known, () => unknown).dueAt);
952
- var notYetKnown = (known) => Option14.isNone(known);
1128
+ var refreshable = (known) => Option15.isSome(Option15.getOrElse(known, () => unknown).dueAt);
1129
+ var notYetKnown = (known) => Option15.isNone(known);
953
1130
  var layer6 = Layer6.effect(Monitor, Effect10.gen(function* () {
954
1131
  const cache = {
955
1132
  github: yield* GitHub,
@@ -978,114 +1155,6 @@ var layer6 = Layer6.effect(Monitor, Effect10.gen(function* () {
978
1155
  // src/rpc.ts
979
1156
  import { Rpc } from "@opencode/plugin/rpc";
980
1157
  import { Schema as Schema13 } from "effect";
981
-
982
- // src/domain/StackLayout.ts
983
- import { Array as Arr7, Option as Option15, Schema as Schema12 } from "effect";
984
- var Membership = Schema12.Union([
985
- Schema12.TaggedStruct("Standalone", {}),
986
- Schema12.TaggedStruct("Stack", {
987
- id: Schema12.String,
988
- members: Schema12.NonEmptyArray(PullRequestRef)
989
- })
990
- ]);
991
- var stackOf = (entry) => Option15.filter(entry.membership, (membership) => membership._tag === "Stack");
992
- var urlsOf2 = (stack) => stack.members.map((member) => member.url);
993
- function reportsById(entries) {
994
- const byId = new Map;
995
- for (const [index, entry] of entries.entries()) {
996
- for (const stack of Option15.toArray(stackOf(entry))) {
997
- byId.set(stack.id, [...byId.get(stack.id) ?? [], { index, stack, url: entry.ref.url }]);
998
- }
999
- }
1000
- return byId;
1001
- }
1002
- function claims(byId) {
1003
- const claimed = new Map;
1004
- for (const [id, reports] of byId) {
1005
- for (const url of reports.flatMap((report) => urlsOf2(report.stack))) {
1006
- claimed.set(url, new Set([...claimed.get(url) ?? [], id]));
1007
- }
1008
- }
1009
- return claimed;
1010
- }
1011
- function agrees(reports, entries, claimed) {
1012
- const members = Option15.match(Arr7.head(reports), {
1013
- onNone: () => [],
1014
- onSome: (report) => urlsOf2(report.stack)
1015
- });
1016
- const listed = new Set(members);
1017
- const attachedMembers = entries.filter((entry) => listed.has(entry.ref.url));
1018
- return listed.size === members.length && reports.every((report) => urlsOf2(report.stack).join(`
1019
- `) === members.join(`
1020
- `)) && members.every((url) => (claimed.get(url) ?? new Set).size === 1) && attachedMembers.length === reports.length;
1021
- }
1022
- function placements(reports) {
1023
- const placed = reports.map((report) => ({
1024
- index: report.index,
1025
- position: urlsOf2(report.stack).indexOf(report.url),
1026
- size: report.stack.members.length
1027
- }));
1028
- const adjacent = Arr7.zipWith(placed, placed.slice(1), (before, after) => after.index === before.index + 1 && after.position > before.position);
1029
- const ordered = placed.every((member) => member.position >= 0) && adjacent.every(Boolean);
1030
- return ordered ? Option15.some(placed) : Option15.none();
1031
- }
1032
- function consistentStacks(entries) {
1033
- const byId = reportsById(entries);
1034
- const claimed = claims(byId);
1035
- const places = new Map;
1036
- for (const reports of byId.values()) {
1037
- const placed = agrees(reports, entries, claimed) ? placements(reports) : Option15.none();
1038
- for (const group of Option15.toArray(placed)) {
1039
- for (const [step, current] of group.entries()) {
1040
- places.set(current.index, {
1041
- attached: group.length,
1042
- current,
1043
- first: step === 0,
1044
- last: step === group.length - 1,
1045
- previous: Arr7.get(group, step - 1)
1046
- });
1047
- }
1048
- }
1049
- }
1050
- return places;
1051
- }
1052
- function marker(place) {
1053
- const { current } = place;
1054
- if (place.attached === 1 && current.size > 1)
1055
- return "middle";
1056
- if (place.first && current.position === 0)
1057
- return "first";
1058
- if (place.last && current.position === current.size - 1)
1059
- return "last";
1060
- return "middle";
1061
- }
1062
- function connector(place) {
1063
- if (!place.last)
1064
- return "continues";
1065
- return place.current.position < place.current.size - 1 ? "open" : "none";
1066
- }
1067
- function stackRows(entry, place) {
1068
- const skipped = Option15.match(place.previous, {
1069
- onNone: () => 0,
1070
- onSome: (before) => place.current.position - before.position - 1
1071
- });
1072
- const row = {
1073
- _tag: "PullRequest",
1074
- connector: connector(place),
1075
- entry,
1076
- marker: marker(place)
1077
- };
1078
- return skipped > 0 ? [{ _tag: "Gap", count: skipped }, row] : [row];
1079
- }
1080
- function layout(entries) {
1081
- const places = consistentStacks(entries);
1082
- return entries.flatMap((entry, index) => Option15.match(Option15.fromNullishOr(places.get(index)), {
1083
- onNone: () => [{ _tag: "PullRequest", connector: "none", entry, marker: "bullet" }],
1084
- onSome: (place) => stackRows(entry, place)
1085
- }));
1086
- }
1087
-
1088
- // src/rpc.ts
1089
1158
  var Layout = Schema13.Literals(["default", "compact"]);
1090
1159
  var EntryView = Schema13.Struct({
1091
1160
  attachedAt: Schema13.Int,
@@ -1271,7 +1340,7 @@ function appearance(status) {
1271
1340
  // src/server/Tools.ts
1272
1341
  var options = { codemode: true, namespace: "pr", pinned: true };
1273
1342
  var PullRequestArgument = Schema14.Struct({
1274
- pull_request: Schema14.Union([Schema14.String, Schema14.Int]).annotate({
1343
+ pull_request: Schema14.Union([Schema14.String, Schema14.Number]).annotate({
1275
1344
  description: "A pull request URL, such as github.com/owner/repository/pull/123, or a number in this repository"
1276
1345
  })
1277
1346
  });
@@ -1340,4 +1409,4 @@ export {
1340
1409
  server_default as default
1341
1410
  };
1342
1411
 
1343
- //# debugId=7CB75665EE3FC22164756E2164756E21
1412
+ //# debugId=3A0BF0214CF1B17464756E2164756E21