Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

1,738 linesCodeBlameRaw
1import type { ChecksApi } from "./checks";
2import type { CodeownersReport, PullCodeOwners } from "./codeowners";
3import type { G1tEvent } from "./events";
4import type { User, Viewer } from "./identity";
5import type { PullBranchUpdate, RepoPath, UpdateMethod } from "./repos";
6import type { MergeMethod } from "./rules";
7import type { Result } from "./result";
8import type { MergeRules, RulesApi } from "./rules";
9
10/**
11 * The filter on lists of work items and pull requests. An open pull
12 * request is a draft or one ready for review; a closed one was merged or
13 * closed without merging.
14 */
15export type State = "open" | "closed";
16
17/** Why a work item was closed. */
18export type CloseReason = "completed" | "not_planned";
19
20/**
21 * How a work item is linked to a repository: where it was filed (`origin`,
22 * which keeps an old issue's number), where the work is meant to land
23 * (`target`), or where work for it landed or is under way (`touched`).
24 */
25export type LinkRole = "origin" | "target" | "touched";
26
27/** A repository a work item is linked to. */
28export type ItemRepo = {
29 repoId: string;
30 /** `owner/name`, as it is now, or as it was when the repository was deleted. */
31 path: string;
32 role: LinkRole;
33 /** For the origin of a work item moved from a repository's issues: its number there. */
34 legacyNumber: number | null;
35 /** A pull request or commit that closes it landed in this repository. */
36 landed: boolean;
37};
38
39/** What resolved a work item in one repository: a pull request merged, or a commit pushed. */
40export type Resolution = {
41 /** `owner/name`. */
42 repo: string;
43 pull: number | null;
44 commit: string | null;
45 /** Who pushed that commit, by username. */
46 pushedBy: string | null;
47};
48
49/**
50 * Any tracked work of a workspace: a feature, a bug, a chore. Numbered in
51 * the workspace with its new pull requests, and linked to the repositories
52 * it concerns. It closes once what closes it has landed in every target,
53 * or, with no targets, once anything that closes it lands. Mirrors
54 * `crates/contracts/src/work/items.rs`.
55 */
56export type WorkItem = {
57 /** `wki_…`; one moved from an issue keeps its `iss_…`. */
58 id: string;
59 /** The workspace's slug. */
60 workspace: string;
61 /** Shown as `#12`; on the site at `/<workspace>/-/work/<number>`. */
62 number: number;
63 title: string;
64 /** Markdown. Also what an agent is given to work from. */
65 body: string;
66 labels: string[];
67 state: State;
68 /** Set when closed. */
69 reason: CloseReason | null;
70 /** What resolved it, in each repository it landed in, oldest first. */
71 resolutions: Resolution[];
72 /** Who opened it: a person, an integration, or g1t (`kind` `agent`) for one its agent filed while at work. */
73 author: User;
74 /**
75 * For one g1t's agent filed: the person it was working for. They may
76 * manage it as its author could. See `workOwner`.
77 */
78 requestedBy: User | null;
79 /** RFC 3339. */
80 createdAt: string;
81 /** RFC 3339. */
82 updatedAt: string;
83 /** RFC 3339. */
84 closedAt: string | null;
85 /** Pull requests made for it, in any state and repository. */
86 pullCount: number;
87 commentCount: number;
88 /** Usernames of the people it is assigned to. */
89 assignees: string[];
90 /** The numbers of the work items of the workspace that have to land before this one is worked on. */
91 blockedBy: number[];
92 /**
93 * The agent working on it now: the one behind its newest pull request
94 * that is still in progress in a fork, such as `g1t`.
95 */
96 agent: string | null;
97 /** The workspace milestone it is in, if any. */
98 milestone?: MilestoneRef | null;
99 /** The repositories it is linked to: its origin first, then targets, then those it touched. */
100 repos: ItemRepo[];
101};
102
103/** The targets of a work item that have not landed yet, by path. */
104export function missingTargets(repos: ItemRepo[]): string[] {
105 return repos.filter((repo) => repo.role === "target" && !repo.landed).map((repo) => repo.path);
106}
107
108/** A work item and everything on it. */
109export type WorkItemDetail = {
110 item: WorkItem;
111 /** Every pull request made for it, in the repositories the viewer may read, oldest first. */
112 pulls: Pull[];
113 comments: Comment[];
114 /** How people (and g1t) reacted to its description. */
115 reactions: ReactionGroup[];
116};
117
118/** What `#N` names (`resolveRef`): see `refs.ts` for the order it is read in. */
119export type RefTarget =
120 | { kind: "work_item"; workspace: string; number: number; itemId: string }
121 | { kind: "pull"; repo: string; number: number }
122 | { kind: "none" };
123
124/**
125 * What a comment, a reaction or a set of labels is on: a pull request of a
126 * repository, or a work item of a workspace.
127 */
128export type Thread = { repo: RepoPath; number: number } | { workspace: string; number: number };
129
130/** The thread's fields as the work service reads them. */
131export function threadArgs(thread: Thread): { repo: RepoPath | null; workspace: string; number: number } {
132 return "repo" in thread
133 ? { repo: thread.repo, workspace: "", number: thread.number }
134 : { repo: null, workspace: thread.workspace, number: thread.number };
135}
136
137/**
138 * A label of a workspace: a name, a color and what it means. Work items and
139 * the pull requests of its repositories carry labels by name; names are
140 * lowercase.
141 */
142export type Label = {
143 name: string;
144 /** Six hex digits, without `#`. */
145 color: string;
146 description: string;
147 /** How many work items carry it, open or closed. */
148 items: number;
149 /** How many pull requests carry it, in any state. */
150 pulls: number;
151};
152
153/** A work item a commit resolved, as the commit page lists it. */
154export type ClosedItem = { workspace: string; number: number; title: string; closed: boolean };
155
156/** A milestone, as a work item or pull request names it. */
157export type MilestoneRef = { number: number; title: string };
158
159/**
160 * A goal of a workspace, with an optional due date, that work items and
161 * pull requests are gathered under. Its progress is how many of them are
162 * closed.
163 */
164export type Milestone = {
165 /** Numbered from 1 in each workspace, apart from work items. */
166 number: number;
167 title: string;
168 /** Markdown. */
169 description: string;
170 /** `YYYY-MM-DD`. */
171 dueOn: string | null;
172 state: State;
173 /** Open work items and pull requests in it. */
174 openItems: number;
175 /** Closed work items, and merged or closed pull requests, in it. */
176 closedItems: number;
177 createdAt: string;
178 updatedAt: string;
179 closedAt: string | null;
180 /** For one moved from a repository when work items moved to the workspace: its id. */
181 originRepoId?: string;
182};
183
184/** One milestone and what is in it that the viewer may see, newest first. */
185export type MilestoneDetail = { milestone: Milestone; items: WorkItem[]; pulls: Pull[] };
186
187/** How `setLabels` changes an item's labels. */
188export type LabelChange = "set" | "add" | "remove";
189
190/**
191 * Whose a work item or a pull request is to answer for: whoever asked g1t for
192 * it, or its author. They may change, close and steer it, are never asked to
193 * review it and cannot approve it, and see it as theirs. Mirrors
194 * `Pull::owner` in the Rust contracts.
195 */
196export function workOwner(item: Pick<Pull, "author" | "requestedBy">): User {
197 return item.requestedBy ?? item.author;
198}
199
200/** `draft` is still being worked on; `open` is ready for review. */
201export type PullStatus = "draft" | "open" | "merged" | "closed";
202
203/** Where the agent runs: on g1t's sandboxes, or in someone's own session. */
204export type Runtime = "hosted" | "external";
205
206/**
207 * A proposed change. It is made either in a fork created for it, which is
208 * how agents work, on a branch pushed to the repository itself, or on a
209 * branch of someone's fork of it (`headRepo`).
210 */
211export type Pull = {
212 id: string;
213 repoId: string;
214 /** Shown as `#12`. New ones are numbered in the workspace's sequence, which work items share. */
215 number: number;
216 /** The work item this is for, if any, by number in the workspace. */
217 item: number | null;
218 title: string;
219 /** Markdown: what changed and why. Set when marked ready. */
220 body: string | null;
221 /** A label for the agent doing the work, e.g. `claude-code`. */
222 agent: string;
223 runtime: Runtime;
224 status: PullStatus;
225 /** The fork holding the change, unless it is on a branch. */
226 fork: RepoPath | null;
227 /** The fork's repository id. */
228 forkRepoId: string | null;
229 /**
230 * The branch holding the change, unless it is in a working copy (`fork`):
231 * of `headRepo` when set, else of the repository.
232 */
233 branch: string | null;
234 /**
235 * A fork of the repository (its `parentId`, directly or through other
236 * forks) whose `branch` holds the change, when it was opened from one.
237 * Null for a branch of the repository itself and for a working copy.
238 */
239 headRepo?: RepoPath | null;
240 /** `headRepo`'s id. */
241 headRepoId?: string | null;
242 headCommit: string | null;
243 /**
244 * For a merged pull request, what the branch pointed to before the merge.
245 * Comparing against it shows what the pull request changed.
246 */
247 mergeBase: string | null;
248 /** Username of whoever merged it. */
249 mergedBy: string | null;
250 /** RFC 3339. */
251 mergedAt: string | null;
252 /** How it merged, once merged. */
253 mergeMethod?: MergeMethod | null;
254 /** Where its merge left the base: the merge or squash commit, the last rebased one, or its head. */
255 mergeCommit?: string | null;
256 /** Merge it on its own once everything it needs is met, as a person asked. */
257 autoMerge?: AutoMerge | null;
258 /**
259 * The work items it closes when it merges into the default branch, by
260 * number: its own and those its title and description name after a
261 * closing keyword (`Fixes #12`). Merging marks this repository's link to
262 * each as landed.
263 */
264 closes?: number[];
265 /** The work items it is part of (`Part of #12`, `Refs #12`): linked, never closed by it. */
266 parts?: number[];
267 /** Its branch was deleted after it merged or closed, and can be restored at `headCommit`. */
268 headBranchDeleted?: boolean;
269 /**
270 * Set on a pull request closed because another one for the same work item
271 * was merged: that one's number.
272 */
273 supersededBy: number | null;
274 /**
275 * Where the latest run of the work item's acceptance checks stands, if
276 * there has been one against the current head.
277 */
278 checkStatus: CheckStatus | null;
279 /** The files it changes, as of its latest push. */
280 files: ChangedFile[];
281 /** Usernames of the people it is assigned to. */
282 assignees: string[];
283 /**
284 * Those whose review was asked for: usernames, and `g1t` when a g1t
285 * agent was asked.
286 */
287 reviewers: string[];
288 /**
289 * Teams whose review was asked for, as `workspace/slug`. A team stays here
290 * after review assignment picks people from it, who are in `reviewers`.
291 */
292 teamReviewers?: string[];
293 /** The labels it carries, by name. */
294 labels?: string[];
295 /** The milestone it is in, if any. */
296 milestone?: MilestoneRef | null;
297 /**
298 * The branch it merges into. Lists and `getPull` name it; null only in
299 * what services pass between themselves, for the default branch.
300 */
301 base?: string | null;
302 /** Who opened it: a person, or g1t (`kind` `agent`, username `g1t`) for a change g1t made. */
303 author: User;
304 /**
305 * For a change g1t made: the person who asked for it, by assigning a work item
306 * or handing g1t the work. They answer for it as its author would. See
307 * `workOwner`.
308 */
309 requestedBy: User | null;
310 /** RFC 3339. */
311 createdAt: string;
312 /** RFC 3339. */
313 updatedAt: string;
314 /** Who last moved its head (a user id), and when; absent until a push after rulesets arrived. */
315 headPushedBy?: string;
316 headPushedAt?: string;
317};
318
319/** One file a pull request changes, and by how much. */
320export type ChangedFile = { path: string; additions: number; deletions: number };
321
322/**
323 * Another pull request in progress that changes some of the same files. Two
324 * for the same work item are alternatives; two for different ones are
325 * heading for a conflict.
326 */
327export type Overlap = {
328 number: number;
329 title: string;
330 /** The number of the work item the other pull request is for. */
331 item: number | null;
332 /** The files both change. */
333 paths: string[];
334};
335
336/**
337 * On a pull request, `failed` means the merge queue took it out, until its
338 * head moves. The other states are from runs of commands written on work items,
339 * which g1t no longer runs.
340 */
341export type CheckStatus = "queued" | "running" | "passed" | "failed" | "errored";
342
343/** How one command went, in a run recorded before checks were workflows. */
344export type CheckResult = {
345 command: string;
346 passed: boolean;
347 /** Null when the command was stopped for taking too long. */
348 exitCode: number | null;
349 /** What the command printed; the end of it, when there was a lot. */
350 output: string;
351 durationMs: number;
352};
353
354/**
355 * A record against a pull request's head: the merge queue taking it out,
356 * with why, or an earlier run of commands written on its work item.
357 */
358export type CheckRun = {
359 id: string;
360 /** The commit that was checked. */
361 headCommit: string;
362 status: CheckStatus;
363 results: CheckResult[];
364 /** Why the checks could not be run, when `status` is `errored`. */
365 error: string | null;
366 /** RFC 3339. */
367 createdAt: string;
368 /** RFC 3339. */
369 finishedAt: string | null;
370};
371
372/** A reviewer's decision on a pull request. */
373export type Verdict = "approve" | "request_changes";
374
375/**
376 * A comment on a work item or a pull request. On a pull request it can sit on
377 * one line of the change, and it can carry a reviewer's verdict.
378 */
379export type Comment = {
380 id: string;
381 /**
382 * Something a person or an agent wrote, or something that happened: an
383 * assignment, a review asked for, a close.
384 */
385 kind: "comment" | "event";
386 author: User;
387 /**
388 * Markdown. For an event, what its author did, as the rest of a sentence
389 * that starts with their name: "assigned ana".
390 */
391 body: string;
392 /** The file commented on, for a comment on a line. */
393 path: string | null;
394 /** The line of that file, as numbered after the change. */
395 line: number | null;
396 verdict: Verdict | null;
397 /** RFC 3339. */
398 createdAt: string;
399 /** When its text was last edited, RFC 3339; absent if it never was. */
400 editedAt?: string;
401 /**
402 * Set when one of the workspace's agents wrote it, as itself. `author`
403 * is then the agent (its id, its handle as `username`, kind `agent`):
404 * show it by `agent.displayName` with an Agent badge, never as a person.
405 */
406 agent?: AgentRef;
407 /**
408 * Who the agent acted for, whose access capped it. Set with `agent`, and
409 * when g1t (`author`) wrote it, or did what an event notes, in a session
410 * someone started: show "on behalf of @them".
411 */
412 actingFor?: User;
413 /**
414 * An agent's review. Its `verdict` (none for a review that only
415 * comments) is shown but advisory: it never counts toward required
416 * approvals or code owners, and never blocks a merge.
417 */
418 advisory?: boolean;
419 /** How people (and g1t) reacted to it. Absent when nobody has. */
420 reactions?: ReactionGroup[];
421};
422
423/** An emoji reaction: the same eight as everywhere else people react to code. */
424export type ReactionContent = "+1" | "-1" | "laugh" | "hooray" | "confused" | "heart" | "rocket" | "eyes";
425
426/** The reactions, in the order they are offered, with the emoji each shows. */
427export const REACTIONS: { content: ReactionContent; emoji: string; label: string }[] = [
428 { content: "+1", emoji: "👍", label: "Thumbs up" },
429 { content: "-1", emoji: "👎", label: "Thumbs down" },
430 { content: "laugh", emoji: "😄", label: "Laugh" },
431 { content: "hooray", emoji: "🎉", label: "Hooray" },
432 { content: "confused", emoji: "😕", label: "Confused" },
433 { content: "heart", emoji: "❤️", label: "Heart" },
434 { content: "rocket", emoji: "🚀", label: "Rocket" },
435 { content: "eyes", emoji: "👀", label: "Eyes" },
436];
437
438/** Everyone who reacted one way to the same thing, first to react first; `g1t` for g1t. */
439export type ReactionGroup = {
440 content: ReactionContent;
441 count: number;
442 users: string[];
443};
444
445/**
446 * One of a workspace's own agents, as a comment or review it wrote before
447 * g1t's agents were removed shows it: Margo (@margo), with the face drawn
448 * from `avatarSeed`. Kept so those comments still render.
449 */
450export type AgentRef = {
451 id: string;
452 handle: string;
453 displayName: string;
454 avatarSeed: string;
455};
456
457export type NewComment = {
458 /** May be empty when approving. */
459 body: string;
460 path?: string;
461 line?: number;
462 verdict?: Verdict;
463};
464
465export type CheckReport = { results?: CheckResult[]; error?: string; skip?: boolean };
466
467export type SessionEntryKind = "prompt" | "message" | "tool_call" | "tool_result" | "note" | "skill";
468
469/** One step of an agent's session: the "why" behind a pull request's commits. */
470export type SessionEntry = {
471 seq: number;
472 kind: SessionEntryKind;
473 text: string;
474 /** For tool calls and results. */
475 tool: string | null;
476 /** The fork's head commit when this entry was recorded, if known. */
477 commit: string | null;
478 /** RFC 3339. */
479 at: string;
480 /**
481 * Its text is not shown to this viewer (`text` is empty): a prompt shows
482 * only to whoever typed it, and a tool's output to whoever ran it.
483 */
484 withheld?: boolean;
485};
486
487export type NewSessionEntry = Pick<SessionEntry, "kind" | "text"> &
488 Partial<Pick<SessionEntry, "tool" | "commit">>;
489
490export type PullDetail = {
491 pull: Pull;
492 /** The work item it is for, if any. */
493 item: WorkItem | null;
494 comments: Comment[];
495 /** How people (and g1t) reacted to its description. */
496 reactions: ReactionGroup[];
497 /** The latest record against its head: the merge queue taking it out. */
498 checks: CheckRun | null;
499 /** Other pull requests in progress that change the same files. */
500 overlaps: Overlap[];
501 /**
502 * Whether the branch it would merge into has moved on without it, so that
503 * it has to catch up before it can merge.
504 */
505 behind: boolean;
506 /**
507 * Where it stands on its way to being merged, for a pull request g1t is
508 * seeing through. Null on anyone else's.
509 */
510 lifecycle: Lifecycle | null;
511 /**
512 * A merge was asked for while it was behind: g1t is bringing it up to
513 * date and will then land it.
514 */
515 landing: boolean;
516 /** Why g1t stopped working on it, if it did. */
517 stalled: string | null;
518 /** What workflow runs said about its head commit, one per workflow. */
519 statuses?: CommitStatus[];
520 /**
521 * Whether it merges cleanly into the branch it targets, worked out ahead
522 * of time whenever either side moves.
523 */
524 mergeable?: Mergeable;
525 /** When `mergeable` is `conflicting`: the files that conflict. */
526 conflicts?: string[];
527 /** Earlier records like `checks`, newest first, without their output. */
528 earlierChecks?: CheckRun[];
529 /**
530 * The checks the default branch's protection requires, each as it stands
531 * on the head commit. Empty when none are required.
532 */
533 requiredChecks?: RequiredCheck[];
534 /**
535 * Who owns the files it changes (the CODEOWNERS file of the branch it
536 * merges into) and whose approval is still needed. Absent without one.
537 */
538 codeOwners?: PullCodeOwners | null;
539 /**
540 * The rules of the branch it merges into it does not meet yet, for whoever
541 * is looking: what refuses the merge, what they may bypass, and what rulesets
542 * in evaluate would refuse. Absent once it is closed or merged.
543 */
544 rules?: MergeRules | null;
545};
546
547/** Where a required check stands on a commit; `expected` when nothing has reported it yet. */
548export type RequiredState = "success" | "failure" | "pending" | "expected";
549
550/** One check a branch's protection requires, as it stands on a commit. */
551export type RequiredCheck = {
552 /** A workflow's name, such as `CI`, or another status's context. */
553 name: string;
554 state: RequiredState;
555 description: string | null;
556 /** Where to see more: the workflow run, for one a workflow reported. */
557 targetUrl: string | null;
558};
559
560/** A check name reported on a repository's commits lately, for choosing required checks. */
561export type SeenCheck = {
562 name: string;
563 /** The events it was reported for, such as `pull_request`; empty for a status that names none. */
564 events: string[];
565 /** RFC 3339. */
566 lastSeen: string;
567};
568
569/**
570 * A status context's check name: `CI / pull_request` is the `CI` check,
571 * reported for a `pull_request` event. A context that does not end in an
572 * event, such as `g1t / deploy`, is its own name. As the work service reads it.
573 */
574export function checkName(context: string): string {
575 const at = context.lastIndexOf(" / ");
576 if (at <= 0) return context;
577 return STATUS_EVENTS.has(context.slice(at + 3)) ? context.slice(0, at) : context;
578}
579
580const STATUS_EVENTS = new Set([
581 "push",
582 "pull_request",
583 "pull_request_target",
584 "pull_request_review",
585 "merge_group",
586 "workflow_dispatch",
587 "workflow_run",
588 "workflow_call",
589 "schedule",
590 "release",
591 "issues",
592 "issue_comment",
593 "repository_dispatch",
594]);
595
596/**
597 * Whether a pull request merges cleanly into the branch it targets:
598 * `unknown` when it was never worked out or could not be, `checking` while
599 * a probe merges the two.
600 */
601export type Mergeable = "clean" | "conflicting" | "unknown" | "checking";
602
603/** What a sandbox needs to find out whether a pull request merges cleanly. */
604export type MergecheckJob = {
605 pullId: string;
606 /** Lets the sandbox, and nothing else, report this probe. */
607 token: string;
608 repo: RepoPath;
609 number: number;
610 defaultBranch: string;
611 /** The default branch's commit to merge into. */
612 base: string;
613 /** The repository holding the change: its fork, or the repository. */
614 source: RepoPath;
615 /** The branch of `source` holding it. */
616 branch: string;
617 /** The change's commit. */
618 head: string;
619 /** Who the pull request is for (`workOwner`: whoever asked g1t for it, or its author), and so can read its source. */
620 author: User;
621};
622
623/** What a workflow run (or another tool) says about a commit. */
624export type CommitStatus = {
625 /** What reported it, such as `CI / push`. */
626 context: string;
627 state: "pending" | "success" | "failure" | "error";
628 description: string | null;
629 targetUrl: string | null;
630 updatedAt: string;
631 /** The integration that reported it: `actions`, `deployments`, `security`, `g1t` or `api`. */
632 source?: string;
633 /** Set on the status a check run stands as: the check run's id. */
634 checkRunId?: string;
635};
636
637/**
638 * A step on the way from an assigned work item to a pull request that is ready
639 * to merge. g1t takes each one without being asked: `working` (the agent is
640 * making the change), `checking`, `reviewing`, `revising` (the agent is
641 * addressing failed checks or a review), `catching_up` (merging in the
642 * branch it would land on), `answering` (woken to answer another agent),
643 * then `ready` for a person to merge. `needs_you`
644 * means g1t has stopped and a person decides what happens next.
645 */
646export type Stage =
647 | "working"
648 | "checking"
649 | "reviewing"
650 | "revising"
651 | "catching_up"
652 | "answering"
653 | "queued"
654 | "ready"
655 | "needs_you";
656
657export type Lifecycle = {
658 stage: Stage;
659 /** One sentence saying what is happening, or why it stopped. */
660 detail: string;
661 /** How many times the agent has been sent back to revise it. */
662 revisions: number;
663};
664
665/**
666 * How a repository wants its pull requests handled. A repository that has
667 * changed nothing has the defaults.
668 */
669export type RepoSettings = {
670 /**
671 * Land a g1t agent's pull request without a person once it is ready:
672 * checks passed and approved as the settings below require.
673 */
674 autoMerge: boolean;
675 /**
676 * The checks that must pass on a pull request's head before it may merge
677 * into the default branch, by name: a workflow's name (`CI`) or another
678 * status's context (`g1t / deploy`). The same for people and agents, and
679 * for the merge queue.
680 */
681 requiredChecks: string[];
682 /**
683 * Refuse to merge a pull request that does not contain the default
684 * branch's latest commits, so that what merges is what was checked. When
685 * off, merging one that is behind brings it up to date first.
686 */
687 requireUpToDate: boolean;
688 /**
689 * How many approving reviews a pull request needs before it may merge. A
690 * reviewer who has since asked for changes blocks it.
691 */
692 requiredApprovals: number;
693 /** Whether a g1t agent's approval counts towards `requiredApprovals`. */
694 countAgentApprovals: boolean;
695 /** Whether someone who may merge can bypass required checks that have not passed. */
696 allowIgnoringChecks: boolean;
697 /**
698 * Merge through a queue: pull requests are tested together with those
699 * ahead of them, and only a combination that passed reaches the default
700 * branch.
701 */
702 mergeQueue: boolean;
703 /**
704 * Refuse to merge until the code owners of every file it changes have
705 * approved, as many as each section asks. People only; `g1t` only where
706 * the file names `@g1t`.
707 */
708 requireCodeOwnerReview?: boolean;
709 /**
710 * Whether work items can be filed from it (as their origin): null for the
711 * default, which is on for a repository and off for a fork of one. Those
712 * filed from it before stay readable, and it can always be a target.
713 */
714 hasWorkItems?: boolean | null;
715 /** Whether work items can be filed from it now: `hasWorkItems`, or its default. */
716 workItemsEnabled?: boolean;
717 /** Which merge methods pull requests may use (at least one); rules can narrow them per branch. */
718 allowMergeCommit: boolean;
719 allowSquashMerge: boolean;
720 allowRebaseMerge: boolean;
721 allowFastForward: boolean;
722 /** The method offered first, and used when none is named. */
723 defaultMergeMethod: MergeMethod;
724 /** What merge and squash commits say unless their merger writes it. */
725 mergeCommitMessage: CommitMessageDefault;
726 squashCommitMessage: CommitMessageDefault;
727 /**
728 * For `custom`: templates whose `{title}`, `{number}`, `{body}`, `{branch}`,
729 * `{head_repo}`, `{base}`, `{author}`, `{commits}`, `{items}` and
730 * `{co_authors}` are filled in.
731 */
732 mergeCommitTemplate: string | null;
733 squashCommitTemplate: string | null;
734 /** Offer to bring a pull request up to date whenever its base has new commits. */
735 suggestUpdatingBranches: boolean;
736 /** Let people turn on auto-merge for a pull request. */
737 allowAutoMerge: boolean;
738 /** Delete a pull request's branch once it merges (restorable from it). */
739 deleteBranchOnMerge: boolean;
740 /** Commits made on g1t.sh carry a `Signed-off-by:` trailer for whoever made them. */
741 requireWebCommitSignOff: boolean;
742 /** Let anyone who can read it comment on its commits. */
743 allowCommitComments: boolean;
744 /**
745 * Land the work items a pull request names in this repository when it
746 * merges into the default branch (and those a commit pushed there names),
747 * closing those with nothing else left to land.
748 */
749 autoCloseItems: boolean;
750 /** Username of the member who last changed the settings, if anyone has. */
751 updatedBy: string | null;
752 /** RFC 3339. */
753 updatedAt: string | null;
754};
755
756/** What a merge or squash commit's message says by default (`pull_title_and_commits` is squash only). */
757export type CommitMessageDefault = "default" | "pull_title" | "pull_title_and_description" | "pull_title_and_commits" | "custom";
758
759/** A person's request that a pull request merge on its own once it may. */
760export type AutoMerge = {
761 method: MergeMethod;
762 commitTitle: string | null;
763 commitMessage: string | null;
764 /** Username of who asked. */
765 enabledBy: string;
766 /** RFC 3339. */
767 enabledAt: string;
768};
769
770/** A comment on a commit, or on one line (`path`, `line`) of a file it changed. */
771export type CommitComment = {
772 id: string;
773 repoId: string;
774 /** The commit's full hash. */
775 commit: string;
776 path: string | null;
777 line: number | null;
778 /** Username. */
779 author: string;
780 /** Markdown. */
781 body: string;
782 /** RFC 3339. */
783 createdAt: string;
784 updatedAt: string | null;
785};
786
787/** How a pull request may merge now, for whoever is looking (`mergeChoices`). */
788export type MergeChoices = {
789 /** The methods it may merge by: the repository's, as the rules for its base allow. In the order offered. */
790 methods: MergeMethod[];
791 /** The one offered first: the repository's default when it may be used, else the first of `methods`. */
792 defaultMethod: MergeMethod | null;
793 /** Each of `methods`, with its default commit message and whether it can be used now. */
794 options: MergeChoice[];
795 /** Its base has commits it does not have. */
796 behind: boolean;
797 /** It contains its base's head, so it can fast-forward as it is. */
798 canFastForward: boolean;
799 /** Offer to bring it up to date: it is behind, and the repository suggests (or requires) it. */
800 suggestUpdate: boolean;
801 /** The repository requires it to be up to date before it merges. */
802 requireUpToDate: boolean;
803 /** It merges through the merge queue, which lands it as tested whatever method is chosen. */
804 mergeQueue: boolean;
805 /** The viewer may turn on auto-merge for it. */
806 autoMergeAllowed: boolean;
807 /** Whether "delete the branch" starts ticked. */
808 deleteBranchDefault: boolean;
809 /** Its branch can be deleted once it merges (never a working copy's). */
810 canDeleteBranch: boolean;
811 /** Commits made merging it carry a `Signed-off-by:` trailer for the merger. */
812 signOff: boolean;
813};
814
815/** One way a pull request may merge. */
816export type MergeChoice = {
817 method: MergeMethod;
818 /** For `merge` and `squash`: the commit's default title and the rest of its message. */
819 commitTitle: string | null;
820 commitMessage: string | null;
821 /** Why it cannot merge this way now, if it cannot. */
822 unavailable: string | null;
823};
824
825/** How a pull request merges, as the merger chose. */
826export type MergeOptions = {
827 /** Leave its work item open: this pull request is only part of the work. */
828 keepItemOpen?: boolean;
829 /** Merge although required checks have not passed, where the rule requiring them allows it. */
830 ignoreChecks?: boolean;
831 /** Merge past rules a ruleset lets the actor bypass. Recorded as a bypass. */
832 bypassRules?: boolean;
833 /** The repository's default when absent. */
834 mergeMethod?: MergeMethod;
835 /** For `merge` and `squash`: the commit's title and the rest of its message. */
836 commitTitle?: string;
837 commitMessage?: string;
838 /** Refuse if its head is no longer this commit. */
839 expectedHead?: string;
840 /** Delete its branch once merged; the repository's setting when absent. */
841 deleteBranch?: boolean;
842};
843
844/** The settings a member can change. */
845export type RepoSettingsInput = Omit<RepoSettings, "updatedBy" | "updatedAt" | "workItemsEnabled">;
846
847/**
848 * What the runner is to do next for a pull request: always nothing now,
849 * since no step of a pull request is carried out in a sandbox any more.
850 */
851export type Advance = { action: "none" };
852
853export type OpenWorkItemInput = {
854 /** The workspace; may be left out with `repo`, meaning the repository's. */
855 workspace?: string;
856 /** The repository it is filed from, its origin. Without one, only a member of the workspace may file it. */
857 repo?: RepoPath;
858 /** Repositories of the workspace where the work is meant to land. */
859 targets?: RepoPath[];
860 title: string;
861 body: string;
862 labels?: string[];
863 /** The number of the workspace milestone to put it in. Needs the Triage role. */
864 milestone?: number;
865};
866
867export type UpdateWorkItemInput = {
868 title?: string;
869 body?: string;
870 labels?: string[];
871 /** Usernames of the people it is assigned to; replaces the whole set. */
872 assignees?: string[];
873 /** The number of the workspace milestone to put it in; 0 takes it out. */
874 milestone?: number;
875 /** The work items of the workspace that have to land first, by number; replaces the whole set. */
876 blocked_by?: number[];
877};
878
879/** The filters on `listWorkItems`. */
880export type WorkItemFilter = {
881 /** Only those linked to this repository (in any role, or in `role`). */
882 repo?: RepoPath;
883 role?: LinkRole;
884 state?: State;
885 label?: string;
886 milestone?: number;
887 /** Only those assigned to this username. */
888 assignee?: string;
889};
890
891export type OpenPullInput = {
892 /** The work item this is for, by number in the repository's workspace. */
893 item?: number;
894 /** Defaults to the work item's title; required without one. */
895 title?: string;
896 /** What changed and why. Usually set later, when a draft is marked ready. */
897 body?: string;
898 /**
899 * A branch of the repository that already holds the change. The pull
900 * request is then ready for review at once and has no fork.
901 */
902 branch?: string;
903 /**
904 * A fork of the repository (directly or through other forks) whose
905 * `branch` holds the change. Needs `branch`.
906 */
907 headRepo?: RepoPath;
908 agent: string;
909 runtime: Runtime;
910 /** The branch to merge into: the default branch when left out. */
911 base?: string;
912 /**
913 * Opened from a branch as a draft, still being worked on: it can't merge
914 * until it is marked ready.
915 */
916 draft?: boolean;
917};
918
919/** One repository's pull requests from `pullsForRepos`, newest first. */
920export type RepoPulls = {
921 repoId: string;
922 /** Draft and open. */
923 open: Pull[];
924 /** Merged and closed. */
925 closed: Pull[];
926};
927
928/** Where a comment is kept, for changing it: a repository's pull requests, or a workspace's work items. */
929export type CommentScope = { repo: RepoPath } | { workspace: string };
930
931/** Work items, pull requests, comments and sessions. */
932export interface WorkApi extends RulesApi, ChecksApi, CoordinationApi {
933 /**
934 * Files a work item: from `repo` (its origin) by anyone who may take part
935 * there, or in the workspace alone by a member. `targets` are where it is
936 * meant to land.
937 */
938 openWorkItem(actor: User, input: OpenWorkItemInput): Promise<Result<WorkItem>>;
939 /** A workspace's work items the viewer may see, newest first. */
940 listWorkItems(workspace: string, viewer: Viewer, filter?: WorkItemFilter): Promise<Result<WorkItem[]>>;
941 getWorkItem(workspace: string, number: number, viewer: Viewer): Promise<Result<WorkItemDetail>>;
942 /** Its owner, or someone with the Triage role on it, may. */
943 updateWorkItem(actor: User, workspace: string, number: number, input: UpdateWorkItemInput): Promise<Result<WorkItem>>;
944 /**
945 * Closes it; with `by`, as the commit there (on its repository's default
946 * branch, naming it after a closing keyword) would have: it lands there,
947 * and closes once nothing else is left to land.
948 */
949 closeWorkItem(
950 actor: User,
951 workspace: string,
952 number: number,
953 reason?: CloseReason,
954 by?: { repo: RepoPath; commit: string },
955 ): Promise<Result<WorkItem>>;
956 reopenWorkItem(actor: User, workspace: string, number: number): Promise<Result<WorkItem>>;
957 /** Links a repository of the workspace as a target (or as touched), or changes how it is linked. Needs Triage. */
958 linkWorkItem(actor: User, workspace: string, number: number, repo: RepoPath, role?: LinkRole): Promise<Result<WorkItem>>;
959 /** Removes a repository's link (never its origin's). Needs Triage. */
960 unlinkWorkItem(actor: User, workspace: string, number: number, repo: RepoPath): Promise<Result<WorkItem>>;
961 /** What `#number` names for the viewer, read in a repository (and its workspace) or a workspace alone. */
962 resolveRef(viewer: Viewer, context: { repo: RepoPath } | { workspace: string }, number: number): Promise<RefTarget>;
963 /** The work items that a commit pushed to the repository's default branch resolved, of those the viewer may read. */
964 closedByCommit(repo: RepoPath, commit: string, viewer: Viewer): Promise<Result<ClosedItem[]>>;
965 /** A workspace's labels, by name, each with how many work items and pull requests carry it. */
966 listLabels(workspace: string, viewer: Viewer): Promise<Result<Label[]>>;
967 /**
968 * Creates a label, or with `name` changes one; renaming it renames it on
969 * everything that carries it. Needs the Write role in the workspace.
970 */
971 saveLabel(
972 actor: User,
973 workspace: string,
974 label: { name?: string; newName?: string; color?: string; description?: string },
975 ): Promise<Result<Label>>;
976 /** Removes a label from the workspace and everything carrying it. */
977 deleteLabel(actor: User, workspace: string, name: string): Promise<Result<boolean>>;
978 /** Adds the default labels the workspace does not have yet; returns them all. */
979 addDefaultLabels(actor: User, workspace: string): Promise<Result<Label[]>>;
980 /**
981 * The labels of a work item or a pull request, replaced, added to or
982 * taken from. Labels the workspace lacks are created for someone who may
983 * manage labels. Returns its labels now.
984 */
985 setLabels(actor: User, thread: Thread, labels: string[], change?: LabelChange): Promise<Result<string[]>>;
986 /** A workspace's, open ones by due date, then closed ones; both unless `state` says. */
987 listMilestones(workspace: string, viewer: Viewer, state?: State): Promise<Result<Milestone[]>>;
988 getMilestone(workspace: string, number: number, viewer: Viewer): Promise<Result<MilestoneDetail>>;
989 /** Creates a milestone, or with `number` changes the fields given. Needs the Write role in the workspace. */
990 saveMilestone(
991 actor: User,
992 workspace: string,
993 milestone: { number?: number; title?: string; description?: string; dueOn?: string; state?: State },
994 ): Promise<Result<Milestone>>;
995 deleteMilestone(actor: User, workspace: string, number: number): Promise<Result<boolean>>;
996 /** How many work items linked to the repository, and how many of its pull requests, are open. */
997 counts(repo: RepoPath, viewer: Viewer): Promise<Result<{ items: number; pulls: number }>>;
998 /** Moves on the job that moves issues into work items; does nothing once done. */
999 migrateWorkItems(): Promise<{ done: boolean; repos: number; numbered: number; unplaced: number }>;
1000
1001 /**
1002 * On a work item or a pull request. On a pull request it may name a line
1003 * of the change and carry a verdict; nobody can give a verdict on their own.
1004 */
1005 addComment(actor: User, thread: Thread, comment: NewComment): Promise<Result<Comment>>;
1006 /**
1007 * Changes a comment's text. Its author may, and so may anyone with the
1008 * Maintain role or higher; notes of what happened cannot be changed.
1009 */
1010 editComment(actor: User, scope: CommentScope, commentId: string, body: string): Promise<Result<Comment>>;
1011 /**
1012 * Deletes a comment: its author, or anyone with the Maintain role or
1013 * higher. A review that gave a verdict cannot be deleted, only edited.
1014 */
1015 deleteComment(actor: User, scope: CommentScope, commentId: string): Promise<Result<boolean>>;
1016 /**
1017 * Reacts to comment `commentId` on a work item or pull request, or to its
1018 * description without one; with `remove`, takes it back. Anyone who can
1019 * read it may. What it has now.
1020 */
1021 react(
1022 actor: User,
1023 thread: Thread,
1024 reaction: { commentId?: string; content: ReactionContent; remove?: boolean },
1025 ): Promise<Result<ReactionGroup[]>>;
1026
1027 /**
1028 * What a sandbox says about a run from before checks were workflows: so
1029 * one still finishing is recorded.
1030 */
1031 reportChecks(runId: string, token: string, report: CheckReport): Promise<Result<CheckRun>>;
1032 /**
1033 * Claims the probe of whether a pull request merges cleanly that a
1034 * `pull.mergecheck` event asked for. Refused when it is no longer wanted
1035 * or the repository has as many running as it may. For the runner service.
1036 */
1037 startMergecheck(pullId: string): Promise<Result<MergecheckJob>>;
1038 /** Records that a probe could not be carried out. For the runner service. */
1039 failMergecheck(pullId: string, token: string, error: string): Promise<Result<Mergeable>>;
1040
1041 /**
1042 * Works out the next step for a pull request g1t is seeing through and,
1043 * if there is one to take now, claims it, so that it is taken once
1044 * however often this is called. For the runner service.
1045 */
1046 advance(pullId: string): Promise<Advance>;
1047 /**
1048 * Ids of the open pull requests to move along: those g1t merges by itself
1049 * or someone set to auto-merge; with `repoId`, also every open one whose
1050 * lifecycle is remembered, to bring it up to date after a merge there.
1051 */
1052 managedPulls(repoId?: string): Promise<string[]>;
1053
1054 /** A repository's merge queue: what is in it, in order, and what recently left. */
1055 queue(repo: RepoPath, viewer: Viewer): Promise<Result<QueueView>>;
1056 /**
1057 * The next batch of combined states to test for a repository, one per
1058 * entry; empty while a batch is being tested or nothing waits.
1059 */
1060 queueBuild(repoId: string): Promise<QueueJob[]>;
1061 /** Reports that a combined state could not be built or checked. */
1062 failQueue(entryId: string, token: string, error: string): Promise<Result<QueueState>>;
1063 /** Takes a pull request out of the merge queue. Members only. */
1064 removeFromQueue(actor: User, repo: RepoPath, number: number): Promise<Result<Pull>>;
1065
1066 getSettings(repo: RepoPath, viewer: Viewer): Promise<Result<RepoSettings>>;
1067 /** The check names reported on the repository's commits in the last 30 days, most recent first. */
1068 seenChecks(repo: RepoPath, viewer: Viewer): Promise<Result<SeenCheck[]>>;
1069 /** Members of the repository's workspace only. */
1070 updateSettings(actor: User, repo: RepoPath, settings: RepoSettingsInput): Promise<Result<RepoSettings>>;
1071 /** The CODEOWNERS file at a branch (the default when left out), checked. Needs Read. */
1072 codeownersErrors(repo: RepoPath, viewer: Viewer, ref?: string | null): Promise<Result<CodeownersReport>>;
1073
1074 /**
1075 * Opens a pull request: a draft with a fork to push to, or, given a
1076 * branch, one ready for review.
1077 */
1078 openPull(actor: User, repo: RepoPath, input: OpenPullInput): Promise<Result<Pull>>;
1079 /** Newest first. */
1080 listPulls(
1081 repo: RepoPath,
1082 viewer: Viewer,
1083 state?: State,
1084 filter?: { label?: string; milestone?: number; base?: string },
1085 ): Promise<Result<Pull[]>>;
1086 /**
1087 * Delivery for up to 100 repositories over UTC days `from` through
1088 * `until`, over their whole history. Repositories the viewer cannot read,
1089 * and forks, are left out of `projects`.
1090 */
1091 deliveryInsights(repoIds: string[], viewer: Viewer, from: string, until: string): Promise<DeliveryInsights>;
1092 /**
1093 * The newest `limit` open and closed pull requests of each repository,
1094 * in one call. Repositories the viewer cannot read, and forks, are left
1095 * out: ask those with `listPulls`.
1096 */
1097 pullsForRepos(repoIds: string[], viewer: Viewer, limit: number): Promise<RepoPulls[]>;
1098 getPull(repo: RepoPath, number: number, viewer: Viewer): Promise<Result<PullDetail>>;
1099 /**
1100 * Changes who a pull request is assigned to and whose review is asked
1101 * for; each list given replaces the whole set. Asking for `g1t`'s
1102 * review does not by itself start one: the runner's `review` does.
1103 */
1104 updatePull(
1105 actor: User,
1106 repo: RepoPath,
1107 number: number,
1108 changes: {
1109 assignees?: string[];
1110 reviewers?: string[];
1111 labels?: string[];
1112 /** 0 takes it out of its milestone. */
1113 milestone?: number;
1114 /** The branch it merges into. Needs the Write role. */
1115 base?: string;
1116 },
1117 ): Promise<Result<Pull>>;
1118 /**
1119 * Brings a pull request up to date with the default branch in seconds,
1120 * without a sandbox, when the two changed different files: the merge
1121 * commit is pushed to its branch as `actor`, who must be whoever opened
1122 * it (for a fork) or a member (for a branch). Otherwise `needs_agent`, and
1123 * nothing is pushed: the runner's `update` is the way on.
1124 */
1125 catchUpPull(
1126 actor: User,
1127 repo: RepoPath,
1128 number: number,
1129 options?: { updateMethod?: UpdateMethod; expectedHead?: string },
1130 ): Promise<Result<PullBranchUpdate>>;
1131 /**
1132 * Turns a pull request's auto-merge on (with how to merge) or off. On needs
1133 * Write, since it merges as whoever turned it on; its author may also turn
1134 * it off. Refused where the repository does not allow auto-merge or the
1135 * method. One that may merge already merges at once.
1136 */
1137 setAutoMerge(
1138 actor: User,
1139 repo: RepoPath,
1140 number: number,
1141 enabled: boolean,
1142 options?: { mergeMethod?: MergeMethod; commitTitle?: string; commitMessage?: string },
1143 ): Promise<Result<Pull>>;
1144 /** How a pull request may merge now: methods, default messages, auto-merge and branch deletion. */
1145 mergeChoices(viewer: Viewer, repo: RepoPath, number: number): Promise<Result<MergeChoices>>;
1146 /** Deletes a merged or closed pull request's branch (Push on where it is). */
1147 deleteHeadBranch(actor: User, repo: RepoPath, number: number): Promise<Result<Pull>>;
1148 /** Makes a deleted pull request branch again at its head. */
1149 restoreHeadBranch(actor: User, repo: RepoPath, number: number): Promise<Result<Pull>>;
1150 /** A commit's comments, oldest first; with no commit, the repository's, newest first, 30 a page. */
1151 commitComments(viewer: Viewer, repo: RepoPath, commit?: string | null, page?: number): Promise<Result<CommitComment[]>>;
1152 /** Comments on a commit, or on one line of a file it changed. Needs Read, where commit comments are on. */
1153 addCommitComment(
1154 actor: User,
1155 repo: RepoPath,
1156 commit: string,
1157 body: string,
1158 at?: { path: string; line: number },
1159 ): Promise<Result<CommitComment>>;
1160 editCommitComment(actor: User, repo: RepoPath, id: string, body: string): Promise<Result<CommitComment>>;
1161 deleteCommitComment(actor: User, repo: RepoPath, id: string): Promise<Result<boolean>>;
1162 /** Marks a draft ready for review and sets its description. */
1163 readyPull(actor: User, repo: RepoPath, number: number, summary: string): Promise<Result<Pull>>;
1164 closePull(actor: User, repo: RepoPath, number: number): Promise<Result<Pull>>;
1165 /** Opens a closed pull request again, as the draft it was if it was closed as one. Never a merged one. */
1166 reopenPull(actor: User, repo: RepoPath, number: number): Promise<Result<Pull>>;
1167 /** Turns an open pull request back into a draft; it leaves the merge queue. */
1168 convertPullToDraft(actor: User, repo: RepoPath, number: number): Promise<Result<Pull>>;
1169 /**
1170 * Lands the pull request on the repository's default branch. Unless
1171 * `keepItemOpen`, that lands the work items it closes in this repository
1172 * (each closing once nothing else is left to land), and the others still
1173 * in progress here for its own close as superseded. Only members of the
1174 * repository's workspace may merge.
1175 *
1176 * If the default branch has moved, the pull request is brought up to date
1177 * first and lands when that is done; it comes back still open, and
1178 * `PullDetail.landing` is true meanwhile. A repository that requires pull
1179 * requests to be up to date refuses instead.
1180 */
1181 mergePull(actor: User, repo: RepoPath, number: number, options?: MergeOptions): Promise<Result<Pull>>;
1182 /**
1183 * Drafts and open pull requests the viewer started, most recently active
1184 * first, each with where it stands if g1t is seeing it through.
1185 */
1186 listActivePulls(
1187 viewer: Viewer,
1188 ): Promise<{ pull: Pull; item: WorkItem | null; lifecycle: Lifecycle | null }[]>;
1189 /**
1190 * The work items and pull requests a person opened, a page at a time,
1191 * only those the viewer may read. Not found for no such account.
1192 */
1193 byAuthor(username: string, viewer: Viewer, filter?: AuthoredFilter): Promise<Result<Authored>>;
1194 /**
1195 * What a person did each day of the last year (work items and pull requests
1196 * opened, reviews given), only on repositories the viewer may read: the
1197 * calendar on their profile. Not found for no such account.
1198 */
1199 contributions(username: string, viewer: Viewer): Promise<Result<Contributions>>;
1200
1201
1202
1203 appendSession(actor: User, repo: RepoPath, number: number, entries: NewSessionEntry[]): Promise<Result<{ count: number }>>;
1204 readSession(repo: RepoPath, number: number, viewer: Viewer, afterSeq?: number): Promise<Result<SessionEntry[]>>;
1205}
1206
1207/**
1208 * What to pass `ReposApi.compare` to see what a pull request changes.
1209 *
1210 * A working copy is compared as a whole. A branch, of the repository or of
1211 * a fork of it, is compared by name while the pull request is open, and by
1212 * the commit it was merged or closed at afterwards, so later pushes to the
1213 * branch do not change the record. A fork's branch is compared from the
1214 * repository it merges into (`targetId`).
1215 */
1216export function pullComparison(pull: Pull): {
1217 repoId: string;
1218 base: string | null;
1219 head: string | null;
1220 /** The branch it merges into, which it is compared from. */
1221 baseBranch: string | null;
1222 /** For a fork's branch: the repository it merges into. */
1223 targetId: string | null;
1224} {
1225 const baseBranch = pull.base ?? null;
1226 if (pull.forkRepoId) return { repoId: pull.forkRepoId, base: pull.mergeBase, head: null, baseBranch, targetId: null };
1227 const settled = pull.status === "merged" || pull.status === "closed";
1228 return {
1229 repoId: pull.headRepoId ?? pull.repoId,
1230 base: pull.mergeBase,
1231 // A fork's branch by the commit it was last seen at, which the repository
1232 // it merges into holds too, so those who read that repository can read it.
1233 head: ((settled || pull.headRepoId) && pull.headCommit) || pull.branch,
1234 baseBranch,
1235 targetId: pull.headRepoId ? pull.repoId : null,
1236 };
1237}
1238
1239/** The id of the repository holding a pull request's change: its working copy, its fork, or the repository. */
1240export function pullSourceId(pull: Pull): string {
1241 return pull.forkRepoId ?? pull.headRepoId ?? pull.repoId;
1242}
1243
1244
1245export type QueueState = "waiting" | "testing" | "passed" | "failed" | "landed" | "removed";
1246
1247/** One pull request's place in a merge queue. */
1248export type QueueEntry = {
1249 id: string;
1250 number: number;
1251 title: string;
1252 agent: string;
1253 state: QueueState;
1254 /** The pull requests merged ahead of it in the state being tested, in order. */
1255 ahead: number[];
1256 baseCommit: string | null;
1257 combinedCommit: string | null;
1258 error: string | null;
1259 results: CheckResult[];
1260 /** Username of whoever merged it into the queue: a person, or `g1t`. */
1261 enqueuedBy: string;
1262 /** RFC 3339. */
1263 createdAt: string;
1264 /** RFC 3339. */
1265 finishedAt: string | null;
1266};
1267
1268/** A repository's merge queue: what is in it, in order, and what recently left. */
1269export type QueueView = {
1270 enabled: boolean;
1271 active: QueueEntry[];
1272 /** Newest first. */
1273 recent: QueueEntry[];
1274};
1275
1276export type QueueStackItem = {
1277 number: number;
1278 title: string;
1279 /** The repository holding the change, and its branch. */
1280 source: RepoPath;
1281 branch: string;
1282 commit: string;
1283 /** How it lands: the state is built with its commits made this way. */
1284 method?: MergeMethod;
1285 /** For merge and squash: the commit's message. */
1286 message?: string;
1287 /** For squash: who the commit is by. */
1288 author?: User;
1289};
1290
1291/** What a sandbox needs to build and check one combined state. */
1292export type QueueJob = {
1293 entryId: string;
1294 token: string;
1295 repo: RepoPath;
1296 defaultBranch: string;
1297 baseCommit: string;
1298 branch: string;
1299 stack: QueueStackItem[];
1300 /** Always empty: the state is checked by the merge_group workflows run on it. */
1301 checks: string[];
1302 /** Always empty, as `checks`. */
1303 contractChecks: string[];
1304 actor: User;
1305};
1306
1307// --- A person's work ---------------------------------------------------------
1308// Mirrors the `Authored*` types in `crates/contracts/src/work.rs`.
1309
1310export type AuthoredKind = "item" | "pull";
1311/** `closed` takes in merged pull requests too; `merged` is only those. */
1312export type AuthoredState = "open" | "closed" | "merged";
1313/** `created` is newest first, `updated` most recently changed, `oldest` oldest first. */
1314export type AuthoredSort = "created" | "updated" | "oldest";
1315
1316/** The most items one `byAuthor` page holds. */
1317export const AUTHORED_PAGE = 25;
1318
1319export type AuthoredFilter = {
1320 kind?: AuthoredKind;
1321 state?: AuthoredState;
1322 /** `namespace/name`. */
1323 repo?: string;
1324 sort?: AuthoredSort;
1325 /** The `next` of the page before. */
1326 before?: string;
1327 limit?: number;
1328};
1329
1330export type AuthoredItem = {
1331 kind: AuthoredKind;
1332 /** The workspace it is numbered in. */
1333 workspace: string;
1334 /** A pull request's repository, or the repository a work item was filed from, if it was. */
1335 repo: RepoPath | null;
1336 number: number;
1337 title: string;
1338 /** A merged pull request is closed. */
1339 state: State;
1340 /** A pull request's own status; null on a work item. */
1341 status: PullStatus | null;
1342 /** Why a work item was closed. */
1343 reason: CloseReason | null;
1344 draft: boolean;
1345 merged: boolean;
1346 /** RFC 3339. */
1347 createdAt: string;
1348 /** RFC 3339. */
1349 updatedAt: string;
1350 /** RFC 3339. */
1351 mergedAt: string | null;
1352};
1353
1354/** Over every repository the viewer may read, whatever the filters. */
1355export type AuthoredCounts = {
1356 pullsMerged: number;
1357 pullsOpen: number;
1358 pulls: number;
1359 items: number;
1360 itemsOpen: number;
1361};
1362
1363export type Authored = {
1364 items: AuthoredItem[];
1365 /** Pass as `before` for the next page; null on the last. */
1366 next: string | null;
1367 counts: AuthoredCounts;
1368 /** The repositories they worked in that the viewer may read, most work first. */
1369 repos: { repo: RepoPath; count: number }[];
1370};
1371
1372// Mirrors `Contributions` in `crates/contracts/src/work.rs`.
1373
1374/** How many days `contributions` covers: today and the 364 before it. */
1375export const CONTRIBUTION_DAYS = 365;
1376
1377/** One day with something on it; days with nothing are left out. */
1378export type ContributionDay = {
1379 /** `YYYY-MM-DD`, UTC. */
1380 date: string;
1381 /** Everything that day, commits included. */
1382 count: number;
1383 /** How many of `count` are commits they pushed to a default branch. */
1384 commits?: number;
1385};
1386
1387/** A person's year, as far as the viewer may see. */
1388export type Contributions = {
1389 /** Oldest first. */
1390 days: ContributionDay[];
1391 total: number;
1392 /** The first day counted, `YYYY-MM-DD`; the last is today (UTC). */
1393 from: string;
1394};
1395
1396// --- Coordinating work: sessions, claims and dependencies -----------------
1397// Mirrors `crates/contracts/src/work/coordination.rs`.
1398
1399/** The harness a session runs in. */
1400export type Harness = "claude_code" | "claude_managed_agents" | "codex" | "cursor" | "copilot" | "other";
1401
1402/** A session as a mark on something it made. */
1403export type SessionRef = { id: string; harness: Harness };
1404
1405/** The sessions tied to one pull request (`pullSessions`). */
1406export type PullSessions = { number: number; sessions: SessionRef[] };
1407
1408/** Active: heard from in 15 minutes; idle: not since; ended: ended, or quiet for 2 hours. */
1409export type SessionState = "active" | "idle" | "ended";
1410
1411/** One run of someone's own harness on a repository. */
1412export type WorkSession = {
1413 /** `ses_…`. */
1414 id: string;
1415 repoId: string;
1416 /** The work item it works on, by number in the repository's workspace. */
1417 item: number | null;
1418 pull: number | null;
1419 branch: string | null;
1420 actor: { id: string; username: string };
1421 harness: Harness;
1422 harnessSessionId: string | null;
1423 title: string | null;
1424 state: SessionState;
1425 headCommit: string | null;
1426 startedAt: string;
1427 lastSeenAt: string;
1428 endedAt: string | null;
1429 /** What the harness said, or `timed_out`. */
1430 endReason: string | null;
1431 /** For a subagent: the session that started it. */
1432 parentId?: string | null;
1433 /** For a subagent: its kind (Explore, security-reviewer). */
1434 agentType?: string | null;
1435 /** For a subagent: the task it was given, in a few words. */
1436 task?: string | null;
1437 /** For a subagent: the prompt its parent gave it. */
1438 prompt?: string | null;
1439 /** What it reported when it finished: a subagent's final message. */
1440 result?: string | null;
1441 model?: string | null;
1442 /** How many subagents it started. */
1443 subagents?: number;
1444 /** In a list, for one with no title: the start of the first prompt it recorded. */
1445 firstPrompt?: string | null;
1446};
1447
1448/** What kind of thing a session touched. */
1449export type TouchKind = "file" | "commit" | "branch" | "pull" | "item";
1450
1451/** Something a session touched, kept once per session. A work item's `value` is its number. */
1452export type SessionTouch = { sessionId: string; kind: TouchKind; value: string; at: string };
1453
1454/** How a session was tied to a work item or pull request. */
1455export type LinkVia = "attached" | "claim" | "branch" | "commit" | "trailer" | "opened" | "ref";
1456
1457/** A work item or pull request a session, or one of its subagents, worked on. */
1458export type SessionLink = {
1459 kind: "item" | "pull";
1460 number: number;
1461 title: string;
1462 /** The work item's state or the pull request's status. */
1463 state: string;
1464 via: LinkVia[];
1465 sessionIds: string[];
1466};
1467
1468/** A skill a session used, by name. */
1469export type SessionSkill = { sessionId: string; name: string; uses: number };
1470
1471export type ClaimState = "active" | "released" | "expired" | "handed_off";
1472
1473/** An advisory, expiring "I'm working on #N". Never a lock. */
1474export type Claim = {
1475 /** `clm_…`. */
1476 id: string;
1477 /** The repository of the checkout it was made from, if any. */
1478 repoId: string | null;
1479 /** The work item it is on: its workspace and number. */
1480 workspace: string;
1481 item: number;
1482 sessionId: string | null;
1483 actor: { id: string; username: string };
1484 harness: Harness | null;
1485 note: string | null;
1486 state: ClaimState;
1487 createdAt: string;
1488 expiresAt: string;
1489 endedAt: string | null;
1490 endReason: string | null;
1491 /** For a hand-off: who it was handed to; null for anyone. */
1492 handoffTo: string | null;
1493};
1494
1495/** `session.started` and `session.ended`. */
1496export type SessionEventData = { repoId: string; number?: number; item?: number; session: WorkSession };
1497
1498/** `work.claimed`, `work.released`, `work.claim_expired` and `work.handoff_requested`. */
1499export type ClaimEventData = {
1500 repoId?: string;
1501 workspace: string;
1502 number: number;
1503 item: number;
1504 /** The repositories the work item is linked to. */
1505 repoIds: string[];
1506 claim: Claim;
1507 others?: Claim[];
1508 tookOver?: string;
1509 reason?: string;
1510 from?: string;
1511 to?: string;
1512 note?: string;
1513};
1514
1515/** `dependency.changed`. */
1516export type DependencyChangedData = {
1517 workspace: string;
1518 number: number;
1519 item: number;
1520 repoIds: string[];
1521 blockedBy: number[];
1522 added: number[];
1523 removed: number[];
1524 cleared: number[];
1525 remaining: number[];
1526 mergedPull?: number;
1527};
1528
1529/** `overlap.detected` between two pull requests, sent once for each side of a new overlap. */
1530export type PullOverlapData = {
1531 repoId: string;
1532 number: number;
1533 pull: number;
1534 otherPull: number;
1535 files: string[];
1536 item?: number;
1537 otherItem?: number;
1538};
1539
1540/** `overlap.detected` between two sessions that touched the same files (siblings included), once for each side. */
1541export type SessionOverlapData = {
1542 repoId: string;
1543 number?: number;
1544 sessionId: string;
1545 otherSessionId: string;
1546 otherActor: string;
1547 files: string[];
1548 item?: number;
1549 otherItem?: number;
1550};
1551
1552/** `overlap.detected`: between pull requests, or between sessions (with `sessionId`). */
1553export type OverlapDetectedData = PullOverlapData | SessionOverlapData;
1554
1555/** `session.progressed`: a session recorded more (at most every few seconds). */
1556export type SessionProgressedData = { repoId: string; number?: number; sessionId: string; rootId?: string };
1557
1558/** A work item in brief. */
1559export type ItemBrief = { number: number; title: string; state: State };
1560
1561/** A pull request in brief. */
1562export type PullBrief = { number: number; title: string; status: PullStatus };
1563
1564/** A work item another is blocked by, and where it stands. */
1565export type Blocker = {
1566 number: number;
1567 title: string;
1568 state: State;
1569 /** Active claims on it. */
1570 claims: Claim[];
1571 pulls: PullBrief[];
1572};
1573
1574/** A pull request's place in the merge queue; `position` 1 is the front. */
1575export type QueuePlace = { state: QueueState; position: number };
1576
1577/** One of a work item's pull requests, with where it stands. */
1578export type WorkPull = {
1579 /** Its repository, `owner/name`. */
1580 repo: string;
1581 number: number;
1582 title: string;
1583 status: PullStatus;
1584 branch: string | null;
1585 headCommit: string | null;
1586 author: { id: string; username: string };
1587 /** `failed` when the merge queue took it out, until its head moves. */
1588 checkStatus: CheckStatus | null;
1589 requiredChecks: RequiredCheck[];
1590 mergeable: Mergeable;
1591 /** Other pull requests in progress changing the same files. */
1592 overlaps: Overlap[];
1593 /** Its place in the merge queue, while it is in it. */
1594 queue: QueuePlace | null;
1595};
1596
1597// --- Decision records (crates/contracts/src/decisions.rs) ------------------
1598
1599export type DecisionKind =
1600 | "proposal"
1601 | "claim"
1602 | "integration_failure"
1603 | "integration_passed"
1604 | "fix"
1605 | "approval"
1606 | "merge"
1607 | "note";
1608
1609/** Claimed: someone said so. Tested: a check or the merge queue proved it. Approved: a person approved it. */
1610export type DecisionConfidence = "claimed" | "tested" | "approved";
1611
1612/** A structured reason kept on a work item or pull request, with how sure it is. */
1613export type Decision = {
1614 /** `dec_…`. */
1615 id: string;
1616 /** The work item it is about, by number in its workspace. */
1617 item: number | null;
1618 pull: number | null;
1619 sessionId: string | null;
1620 /** Username of who recorded it; null when g1t did. */
1621 actor: string | null;
1622 kind: DecisionKind;
1623 confidence: DecisionConfidence;
1624 summary: string;
1625 /**
1626 * What it rests on, kept as recorded, in snake_case: `commit`, `check`
1627 * (`name`, `conclusion`, `url`), `combination`, `other_pulls`,
1628 * `suspects`, `bisected`, `merge_queue`.
1629 */
1630 evidence: Record<string, unknown>;
1631 /** RFC 3339. */
1632 createdAt: string;
1633};
1634
1635/** One repository a work item is linked to, with what landed there and its pull requests for it. */
1636export type WorkRepo = ItemRepo & {
1637 /** The checks every pull request into its default branch must pass. */
1638 requiredChecks: string[];
1639 /** Its pull requests for the work item, oldest first. */
1640 pulls: WorkPull[];
1641};
1642
1643/** A repository of the workspace the work may belong in; confirming it is `linkWorkItem` as a target. */
1644export type SuggestedTarget = { repoId: string; path: string; why: string; score: number };
1645
1646/** Everything about a work item an agent needs to pick it up: `get_work`. */
1647export type WorkContext = {
1648 item: WorkItem;
1649 /** The items under its "Definition of done" heading. */
1650 definitionOfDone: string[];
1651 /** Its repositories: origin, then targets, then touched. */
1652 repos: WorkRepo[];
1653 /** The targets it has not landed in yet, by path. */
1654 missing: string[];
1655 /** Repositories it may belong in, likeliest first, each with why. */
1656 suggestedTargets: SuggestedTarget[];
1657 /** Active claims on it. */
1658 claims: Claim[];
1659 /** Sessions on it or its pull requests, most recently heard from first. */
1660 sessions: WorkSession[];
1661 blockedBy: Blocker[];
1662 /** The work items it blocks. */
1663 blocks: ItemBrief[];
1664 /** On it and its pull requests, newest first. */
1665 decisions: Decision[];
1666 /** The latest `integration_failure` decision among them. */
1667 lastIntegrationFailure: Decision | null;
1668 /** Its latest coordination events, newest first. */
1669 recent: G1tEvent[];
1670};
1671
1672/** A harness session and what it recorded. */
1673export type WorkSessionDetail = {
1674 /** Pending repository coordination events; reading does not acknowledge them. */
1675 updates?: { id: string; kind: string; data: unknown; at: string }[];
1676 session: WorkSession;
1677 entries: SessionEntry[];
1678 /** Its subagents and theirs, in the order they started. */
1679 children?: WorkSession[];
1680 /** What it and its subagents touched. */
1681 touched?: SessionTouch[];
1682 /** The work items and pull requests it and its subagents worked on. */
1683 links?: SessionLink[];
1684 skills?: SessionSkill[];
1685 /** For a subagent: the session at the top of its tree. */
1686 root?: WorkSession | null;
1687 /**
1688 * The viewer is not a member of the repository's workspace: the session
1689 * shows that it runs, who runs it and what it is tied to, nothing more.
1690 */
1691 limited?: boolean;
1692};
1693
1694/** Reading coordination: work service methods (services/work/src/coordination, decisions.rs). */
1695export interface CoordinationApi {
1696 getWork(workspace: string, number: number, viewer: Viewer): Promise<Result<WorkContext>>;
1697 /**
1698 * A workspace's claims on the work items the viewer may see, newest
1699 * first; active ones unless `state` says otherwise. With `repo`, those
1700 * made from it or on work items linked to it.
1701 */
1702 listClaims(
1703 workspace: string,
1704 viewer: Viewer,
1705 filter?: { state?: ClaimState; item?: number; repo?: RepoPath },
1706 ): Promise<Result<Claim[]>>;
1707 /** Most recently heard from first; `item` is a work item of the repository's workspace. */
1708 listWorkSessions(
1709 repo: RepoPath,
1710 viewer: Viewer,
1711 filter?: { state?: SessionState; item?: number; pull?: number; path?: string; subagents?: boolean },
1712 ): Promise<Result<WorkSession[]>>;
1713 getWorkSession(repo: RepoPath, id: string, viewer: Viewer, afterSeq?: number): Promise<Result<WorkSessionDetail>>;
1714 /**
1715 * Ends a session by hand, from the site: the actor's own, or anyone's
1716 * when they have write access to the repository. It ends as
1717 * `ended by hand by @actor`, with its subagents still going
1718 * (`end_session` with `by_hand`; g1t_contracts::work::session_end_reason).
1719 */
1720 endWorkSession(actor: User, repo: RepoPath, id: string): Promise<Result<WorkSession>>;
1721 /**
1722 * The harness sessions tied to some pull requests (at most 100), any way
1723 * a session is tied to one; a subagent's as the session that started it.
1724 * Only pull requests with any are listed.
1725 */
1726 pullSessions(repo: RepoPath, viewer: Viewer, numbers: number[]): Promise<Result<PullSessions[]>>;
1727 /** A work item's (with its pull requests') or a pull request's, oldest first. */
1728 listDecisions(thread: Thread, viewer: Viewer): Promise<Result<Decision[]>>;
1729}
1730
1731/**
1732 * Delivery counts cover the requested UTC days; waiting counts are current.
1733 * `checking` and `failed` count each open pull request once, by its head's
1734 * checks (statuses, check runs and g1t Actions jobs); one with none is in neither.
1735 */
1736export type DeliveryMetrics = { repoId: string; opened: number; merged: number; active: number; unreviewed: number; checking: number; failed: number; queued: number; leadSeconds: number; reviewSeconds: number; reviewed: number };
1737/** `checksUnavailable`: g1t Actions could not be read, so `checking` and `failed` are not known. */
1738export type DeliveryInsights = { projects: DeliveryMetrics[]; days: { day: string; merged: number }[]; checksUnavailable: boolean };