@hcrosse/opencode-pr-tracker 0.4.3 → 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 +1 -1
- package/dist/server.js +225 -156
- package/dist/server.js.map +9 -9
- package/dist/tui.js +11 -5
- package/dist/tui.js.map +3 -3
- package/package.json +1 -1
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
|
-
|
|
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
|
|
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
|
|
618
|
-
const
|
|
619
|
-
|
|
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
|
|
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
|
|
822
|
+
import { Deferred, Effect as Effect6, Exit, Option as Option10 } from "effect";
|
|
692
823
|
|
|
693
824
|
class FetchQueue {
|
|
694
825
|
running = false;
|
|
695
|
-
queued =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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, ...
|
|
869
|
+
const waiting = [done, ...Option10.toArray(Option10.map(this.queued, (queued) => queued.done))];
|
|
739
870
|
this.running = false;
|
|
740
|
-
this.queued =
|
|
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
|
|
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
|
|
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 =
|
|
755
|
-
return merged ?
|
|
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:
|
|
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:
|
|
895
|
+
dueAt: Option12.map(nextRefresh(status), (delay) => now + Duration3.toMillis(delay)),
|
|
765
896
|
membership,
|
|
766
897
|
status
|
|
767
898
|
};
|
|
768
899
|
}
|
|
769
|
-
var
|
|
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
|
|
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
|
|
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 =
|
|
807
|
-
if (
|
|
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
|
|
987
|
+
class PullRequestUnavailable extends Schema12.TaggedError()("PullRequestUnavailable", { diagnostic: Diagnostic, url: Schema12.String }) {
|
|
819
988
|
}
|
|
820
989
|
|
|
821
|
-
class StackIncomplete extends
|
|
822
|
-
url:
|
|
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
|
|
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 :
|
|
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 (
|
|
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
|
|
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
|
|
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) =>
|
|
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(
|
|
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) =>
|
|
952
|
-
var notYetKnown = (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,
|
|
@@ -1340,4 +1409,4 @@ export {
|
|
|
1340
1409
|
server_default as default
|
|
1341
1410
|
};
|
|
1342
1411
|
|
|
1343
|
-
//# debugId=
|
|
1412
|
+
//# debugId=3A0BF0214CF1B17464756E2164756E21
|