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