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
19/**
20 * Legacy single-PR link. Still emitted as the thread's derived current pull
21 * request (see `@t3tools/shared/threadPullRequests`) so clients from before
22 * `pullRequests` keep working independently of their release schedule.
23 */
24export const ThreadLinkedPullRequest = Schema.Struct({
25 projectId: ProjectId,
26 repository: TrimmedNonEmptyString,
27 number: PositiveInt,
28 url: TrimmedNonEmptyString,
29});
30export type ThreadLinkedPullRequest = typeof ThreadLinkedPullRequest.Type;
31
32/** Who created a thread ↔ pull request link. `stack-dismissed` is a tombstone
33 * for a native-stack member the user unlinked, so the sync reactor does not
34 * re-add it; clients hide it. */
35export const ThreadPullRequestLinkSource = Schema.Literals([
36 "manual",
37 "created",
38 "agent",
39 "stack",
40 "stack-dismissed",
41]);
42export type ThreadPullRequestLinkSource = typeof ThreadPullRequestLinkSource.Type;
43
44/**
45 * Host state persisted on a link by the sync reactor; null until first sync. The overview
46 * fields are optional: a host whose cheap read lacks them leaves them out, and snapshots
47 * written before they existed still decode.
48 */
49export const ThreadPullRequestSnapshot = Schema.Struct({
50 state: PullRequestState,
51 title: TrimmedNonEmptyString,
52 headBranch: TrimmedNonEmptyString,
53 baseBranch: TrimmedNonEmptyString,
54 isDraft: Schema.Boolean,
55 updatedAt: Schema.NullOr(IsoDateTime),
56 syncedAt: IsoDateTime,
57 closedAt: Schema.optional(Schema.NullOr(Schema.String)),
58 mergedAt: Schema.optional(Schema.NullOr(Schema.String)),
59 author: Schema.optional(Schema.NullOr(PullRequestActor)),
60 additions: Schema.optional(NonNegativeInt),
61 deletions: Schema.optional(NonNegativeInt),
62 changedFiles: Schema.optional(NonNegativeInt),
63 reviewDecision: Schema.optional(Schema.NullOr(PullRequestReviewDecision)),
64 checksState: Schema.optional(Schema.NullOr(PullRequestChecksState)),
65 mergeability: Schema.optional(PullRequestMergeability),
66});
67export type ThreadPullRequestSnapshot = typeof ThreadPullRequestSnapshot.Type;
68
69export const ThreadPullRequestStackLayer = Schema.Struct({
70 number: PositiveInt,
71 headBranch: TrimmedNonEmptyString,
72 state: PullRequestState,
73});
74export type ThreadPullRequestStackLayer = typeof ThreadPullRequestStackLayer.Type;
75
76/** A host-native stack the pull request belongs to. Layers run bottom to top. */
77export const ThreadPullRequestStack = Schema.Struct({
78 kind: Schema.Literal("native"),
79 id: TrimmedNonEmptyString,
80 number: PositiveInt,
81 url: TrimmedNonEmptyString,
82 base: TrimmedNonEmptyString,
83 layers: Schema.Array(ThreadPullRequestStackLayer),
84});
85export type ThreadPullRequestStack = typeof ThreadPullRequestStack.Type;
86
87/** Identity of a pull request as a thread link sees it: host-level, so the
88 * same PR linked from two projects (or two environments) compares equal. */
89export const ThreadPullRequestKey = Schema.Struct({
90 host: TrimmedNonEmptyString,
91 repository: TrimmedNonEmptyString,
92 number: PositiveInt,
93});
94export type ThreadPullRequestKey = typeof ThreadPullRequestKey.Type;
95
96/**
97 * Present while the server watches the pull request for its thread. The server wakes the
98 * thread's agent when checks finish on the head commit, someone else comments, or the branch
99 * starts to conflict. The other fields record what the agent was last told, so each change is
100 * reported once.
101 */
102export const ThreadPullRequestWatch = Schema.Struct({
103 startedAt: IsoDateTime,
104 /** Head commit at the last pass; null where the host does not report one. */
105 headSha: Schema.NullOr(TrimmedNonEmptyString),
106 /** Failed checks on that commit the agent was told about; a rerun that fails again is news. */
107 failedChecks: Schema.Array(TrimmedNonEmptyString),
108 /** The agent was told the required checks on that commit passed. */
109 passed: Schema.Boolean,
110 /**
111 * Names in the passing gate on that commit, so a required check that first shows up already
112 * passed is news. Empty on watches saved before this field existed.
113 */
114 passedChecks: Schema.Array(TrimmedNonEmptyString).pipe(
115 Schema.withDecodingDefault(Effect.succeed([])),
116 ),
117 /** Remarks from others created up to this host time were reported. */
118 remarksThrough: IsoDateTime,
119 /** Remarks created exactly at `remarksThrough` that were reported, so a late one still counts. */
120 remarkIds: Schema.Array(TrimmedNonEmptyString),
121 conflicting: Schema.Boolean,
122 /** Comment-only wakes in a row. Watching stops at a limit, so bots cannot loop it. */
123 wakes: NonNegativeInt,
124});
125export type ThreadPullRequestWatch = typeof ThreadPullRequestWatch.Type;
126
127export const ThreadPullRequestLink = Schema.Struct({
128 ...ThreadPullRequestKey.fields,
129 url: TrimmedNonEmptyString,
130 source: ThreadPullRequestLinkSource,
131 linkedAt: IsoDateTime,
132 snapshot: Schema.NullOr(ThreadPullRequestSnapshot),
133 stack: Schema.NullOr(ThreadPullRequestStack),
134 watch: Schema.optional(ThreadPullRequestWatch),
135});
136export type ThreadPullRequestLink = typeof ThreadPullRequestLink.Type;