1import * as Effect from "effect/Effect";
2import * as Schema from "effect/Schema";
3
4import {
5 IsoDateTime,
6 NonNegativeInt,
7 PositiveInt,
8 ProjectId,
9 TrimmedNonEmptyString,
10} from "./baseSchemas.ts";
11import {
12 PullRequestActor,
13 PullRequestChecksState,
14 PullRequestMergeability,
15 PullRequestReviewDecision,
16 PullRequestState,
17} from "./pullRequest.ts";
18
19export const ThreadLinkedPullRequest = Schema.Struct({
20 projectId: ProjectId,
21 repository: TrimmedNonEmptyString,
22 number: PositiveInt,
23 url: TrimmedNonEmptyString,
24});
25export type ThreadLinkedPullRequest = typeof ThreadLinkedPullRequest.Type;
26
27/** stack-dismissed hides an unlinked native member and prevents sync from re-adding it. */
28export const ThreadPullRequestLinkSource = Schema.Literals([
29 "manual",
30 "created",
31 "agent",
32 "stack",
33 "stack-dismissed",
34]);
35export type ThreadPullRequestLinkSource = typeof ThreadPullRequestLinkSource.Type;
36
37export const ThreadPullRequestSnapshot = Schema.Struct({
38 state: PullRequestState,
39 title: TrimmedNonEmptyString,
40 headBranch: TrimmedNonEmptyString,
41 baseBranch: TrimmedNonEmptyString,
42 isDraft: Schema.Boolean,
43 updatedAt: Schema.NullOr(IsoDateTime),
44 syncedAt: IsoDateTime,
45 closedAt: Schema.optional(Schema.NullOr(Schema.String)),
46 mergedAt: Schema.optional(Schema.NullOr(Schema.String)),
47 author: Schema.optional(Schema.NullOr(PullRequestActor)),
48 additions: Schema.optional(NonNegativeInt),
49 deletions: Schema.optional(NonNegativeInt),
50 changedFiles: Schema.optional(NonNegativeInt),
51 reviewDecision: Schema.optional(Schema.NullOr(PullRequestReviewDecision)),
52 checksState: Schema.optional(Schema.NullOr(PullRequestChecksState)),
53 mergeability: Schema.optional(PullRequestMergeability),
54});
55export type ThreadPullRequestSnapshot = typeof ThreadPullRequestSnapshot.Type;
56
57export const ThreadPullRequestStackLayer = Schema.Struct({
58 number: PositiveInt,
59 headBranch: TrimmedNonEmptyString,
60 state: PullRequestState,
61});
62export type ThreadPullRequestStackLayer = typeof ThreadPullRequestStackLayer.Type;
63
64/** Layers run bottom to top. */
65export const ThreadPullRequestStack = Schema.Struct({
66 kind: Schema.Literal("native"),
67 id: TrimmedNonEmptyString,
68 number: PositiveInt,
69 url: TrimmedNonEmptyString,
70 base: TrimmedNonEmptyString,
71 layers: Schema.Array(ThreadPullRequestStackLayer),
72});
73export type ThreadPullRequestStack = typeof ThreadPullRequestStack.Type;
74
75export const ThreadPullRequestKey = Schema.Struct({
76 host: TrimmedNonEmptyString,
77 repository: TrimmedNonEmptyString,
78 number: PositiveInt,
79});
80export type ThreadPullRequestKey = typeof ThreadPullRequestKey.Type;
81
82export const ThreadPullRequestWatch = Schema.Struct({
83 startedAt: IsoDateTime,
84 headSha: Schema.NullOr(TrimmedNonEmptyString),
85 failedChecks: Schema.Array(TrimmedNonEmptyString),
86 passed: Schema.Boolean,
87 passedChecks: Schema.Array(TrimmedNonEmptyString).pipe(
88 Schema.withDecodingDefault(Effect.succeed([])),
89 ),
90 /** Checks still pending when required ones passed early, owed one wake once all settle. */
91 owedChecks: Schema.optional(Schema.Array(TrimmedNonEmptyString)),
92 /** Approval holds keyed by workflow run URL, or check name when no run URL is known. */
93 awaitingApproval: Schema.optional(Schema.Array(TrimmedNonEmptyString)),
94 /** The agent was told checks await approval on this head, so later ones stay quiet. */
95 approvalAnnounced: Schema.optional(Schema.Boolean),
96 remarksThrough: IsoDateTime,
97 /** Reported remarks at remarksThrough, so late arrivals at that timestamp still count. */
98 remarkIds: Schema.Array(TrimmedNonEmptyString),
99 conflicting: Schema.Boolean,
100 /** Consecutive comment-only wakes; limited to prevent bot loops. */
101 wakes: NonNegativeInt,
102 /**
103 * When the head was first read without any check, until a check shows up or the agent is told
104 * none did. Null once either happened; absent on watches saved before it existed.
105 */
106 checksAwaitedSince: Schema.optional(Schema.NullOr(IsoDateTime)),
107});
108export type ThreadPullRequestWatch = typeof ThreadPullRequestWatch.Type;
109
110export const ThreadPullRequestLink = Schema.Struct({
111 ...ThreadPullRequestKey.fields,
112 url: TrimmedNonEmptyString,
113 /** An actual browser or clone URL; older agent display URLs may be synthesized. */
114 repositoryWebUrl: Schema.optional(TrimmedNonEmptyString),
115 source: ThreadPullRequestLinkSource,
116 linkedAt: IsoDateTime,
117 snapshot: Schema.NullOr(ThreadPullRequestSnapshot),
118 stack: Schema.NullOr(ThreadPullRequestStack),
119 watch: Schema.optional(ThreadPullRequestWatch),
120 /** Resume from the ended watch to avoid duplicate notifications. */
121 endedWatch: Schema.optional(ThreadPullRequestWatch),
122});
123export type ThreadPullRequestLink = typeof ThreadPullRequestLink.Type;