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,853 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/**
927 * Where an image put in Markdown is kept (`upload_attachment`): a
928 * repository, for its pull requests, commits and the work items filed
929 * from it, or a work item, which keeps its own (one filed with no
930 * repository takes images too, once it is open).
931 */
932export type AttachmentScope = { repo: RepoPath } | { workspace: string; number: number };
933
934/** An image kept for Markdown: `path` is under the usercontent origin (`/attachments/<id>`). */
935export type Attachment = { id: string; name: string; content_type: string; size: number; path: string };
936
937/** Work items, pull requests, comments and sessions. */
938export interface WorkApi extends RulesApi, ChecksApi, CoordinationApi {
939 /**
940 * Files a work item: from `repo` (its origin) by anyone who may take part
941 * there, or in the workspace alone by a member. `targets` are where it is
942 * meant to land.
943 */
944 openWorkItem(actor: User, input: OpenWorkItemInput): Promise<Result<WorkItem>>;
945 /** A workspace's work items the viewer may see, newest first. */
946 listWorkItems(workspace: string, viewer: Viewer, filter?: WorkItemFilter): Promise<Result<WorkItem[]>>;
947 getWorkItem(workspace: string, number: number, viewer: Viewer): Promise<Result<WorkItemDetail>>;
948 /** Its owner, or someone with the Triage role on it, may. */
949 updateWorkItem(actor: User, workspace: string, number: number, input: UpdateWorkItemInput): Promise<Result<WorkItem>>;
950 /**
951 * Closes it; with `by`, as the commit there (on its repository's default
952 * branch, naming it after a closing keyword) would have: it lands there,
953 * and closes once nothing else is left to land.
954 */
955 closeWorkItem(
956 actor: User,
957 workspace: string,
958 number: number,
959 reason?: CloseReason,
960 by?: { repo: RepoPath; commit: string },
961 ): Promise<Result<WorkItem>>;
962 reopenWorkItem(actor: User, workspace: string, number: number): Promise<Result<WorkItem>>;
963 /** Links a repository of the workspace as a target (or as touched), or changes how it is linked. Needs Triage. */
964 linkWorkItem(actor: User, workspace: string, number: number, repo: RepoPath, role?: LinkRole): Promise<Result<WorkItem>>;
965 /** Removes a repository's link (never its origin's). Needs Triage. */
966 unlinkWorkItem(actor: User, workspace: string, number: number, repo: RepoPath): Promise<Result<WorkItem>>;
967 /** What `#number` names for the viewer, read in a repository (and its workspace) or a workspace alone. */
968 resolveRef(viewer: Viewer, context: { repo: RepoPath } | { workspace: string }, number: number): Promise<RefTarget>;
969 /** The work items that a commit pushed to the repository's default branch resolved, of those the viewer may read. */
970 closedByCommit(repo: RepoPath, commit: string, viewer: Viewer): Promise<Result<ClosedItem[]>>;
971 /** A workspace's labels, by name, each with how many work items and pull requests carry it. */
972 listLabels(workspace: string, viewer: Viewer): Promise<Result<Label[]>>;
973 /**
974 * Creates a label, or with `name` changes one; renaming it renames it on
975 * everything that carries it. Needs the Write role in the workspace.
976 */
977 saveLabel(
978 actor: User,
979 workspace: string,
980 label: { name?: string; newName?: string; color?: string; description?: string },
981 ): Promise<Result<Label>>;
982 /** Removes a label from the workspace and everything carrying it. */
983 deleteLabel(actor: User, workspace: string, name: string): Promise<Result<boolean>>;
984 /** Adds the default labels the workspace does not have yet; returns them all. */
985 addDefaultLabels(actor: User, workspace: string): Promise<Result<Label[]>>;
986 /**
987 * The labels of a work item or a pull request, replaced, added to or
988 * taken from. Labels the workspace lacks are created for someone who may
989 * manage labels. Returns its labels now.
990 */
991 setLabels(actor: User, thread: Thread, labels: string[], change?: LabelChange): Promise<Result<string[]>>;
992 /** A workspace's, open ones by due date, then closed ones; both unless `state` says. */
993 listMilestones(workspace: string, viewer: Viewer, state?: State): Promise<Result<Milestone[]>>;
994 getMilestone(workspace: string, number: number, viewer: Viewer): Promise<Result<MilestoneDetail>>;
995 /** Creates a milestone, or with `number` changes the fields given. Needs the Write role in the workspace. */
996 saveMilestone(
997 actor: User,
998 workspace: string,
999 milestone: { number?: number; title?: string; description?: string; dueOn?: string; state?: State },
1000 ): Promise<Result<Milestone>>;
1001 deleteMilestone(actor: User, workspace: string, number: number): Promise<Result<boolean>>;
1002 /** How many work items linked to the repository, and how many of its pull requests, are open. */
1003 counts(repo: RepoPath, viewer: Viewer): Promise<Result<{ items: number; pulls: number }>>;
1004 /** Moves on the job that moves issues into work items; does nothing once done. */
1005 migrateWorkItems(): Promise<{ done: boolean; repos: number; numbered: number; unplaced: number }>;
1006
1007 /**
1008 * On a work item or a pull request. On a pull request it may name a line
1009 * of the change and carry a verdict; nobody can give a verdict on their own.
1010 */
1011 addComment(actor: User, thread: Thread, comment: NewComment): Promise<Result<Comment>>;
1012 /**
1013 * Changes a comment's text. Its author may, and so may anyone with the
1014 * Maintain role or higher; notes of what happened cannot be changed.
1015 */
1016 editComment(actor: User, scope: CommentScope, commentId: string, body: string): Promise<Result<Comment>>;
1017 /**
1018 * Deletes a comment: its author, or anyone with the Maintain role or
1019 * higher. A review that gave a verdict cannot be deleted, only edited.
1020 */
1021 deleteComment(actor: User, scope: CommentScope, commentId: string): Promise<Result<boolean>>;
1022 /**
1023 * Keeps an image to show in Markdown there, for anyone who may comment
1024 * on the repository or take part in the work item. `data` is the file in
1025 * base64; its type is read from its bytes. PNG, JPEG, GIF or WebP of at
1026 * most 10 MB: larger is `too_large`, another type `unsupported_type`.
1027 */
1028 uploadAttachment(actor: User, scope: AttachmentScope, file: { name: string; contentType?: string; data: string }): Promise<Result<Attachment>>;
1029 /**
1030 * Reacts to comment `commentId` on a work item or pull request, or to its
1031 * description without one; with `remove`, takes it back. Anyone who can
1032 * read it may. What it has now.
1033 */
1034 react(
1035 actor: User,
1036 thread: Thread,
1037 reaction: { commentId?: string; content: ReactionContent; remove?: boolean },
1038 ): Promise<Result<ReactionGroup[]>>;
1039
1040 /**
1041 * What a sandbox says about a run from before checks were workflows: so
1042 * one still finishing is recorded.
1043 */
1044 reportChecks(runId: string, token: string, report: CheckReport): Promise<Result<CheckRun>>;
1045 /**
1046 * Claims the probe of whether a pull request merges cleanly that a
1047 * `pull.mergecheck` event asked for. Refused when it is no longer wanted
1048 * or the repository has as many running as it may. For the runner service.
1049 */
1050 startMergecheck(pullId: string): Promise<Result<MergecheckJob>>;
1051 /** Records that a probe could not be carried out. For the runner service. */
1052 failMergecheck(pullId: string, token: string, error: string): Promise<Result<Mergeable>>;
1053
1054 /**
1055 * Works out the next step for a pull request g1t is seeing through and,
1056 * if there is one to take now, claims it, so that it is taken once
1057 * however often this is called. For the runner service.
1058 */
1059 advance(pullId: string): Promise<Advance>;
1060 /**
1061 * Ids of the open pull requests to move along: those g1t merges by itself
1062 * or someone set to auto-merge; with `repoId`, also every open one whose
1063 * lifecycle is remembered, to bring it up to date after a merge there.
1064 */
1065 managedPulls(repoId?: string): Promise<string[]>;
1066
1067 /** A repository's merge queue: what is in it, in order, and what recently left. */
1068 queue(repo: RepoPath, viewer: Viewer): Promise<Result<QueueView>>;
1069 /**
1070 * The next batch of combined states to test for a repository, one per
1071 * entry; empty while a batch is being tested or nothing waits.
1072 */
1073 queueBuild(repoId: string): Promise<QueueJob[]>;
1074 /** Reports that a combined state could not be built or checked. */
1075 failQueue(entryId: string, token: string, error: string): Promise<Result<QueueState>>;
1076 /** Takes a pull request out of the merge queue. Members only. */
1077 removeFromQueue(actor: User, repo: RepoPath, number: number): Promise<Result<Pull>>;
1078
1079 getSettings(repo: RepoPath, viewer: Viewer): Promise<Result<RepoSettings>>;
1080 /** The check names reported on the repository's commits in the last 30 days, most recent first. */
1081 seenChecks(repo: RepoPath, viewer: Viewer): Promise<Result<SeenCheck[]>>;
1082 /** Members of the repository's workspace only. */
1083 updateSettings(actor: User, repo: RepoPath, settings: RepoSettingsInput): Promise<Result<RepoSettings>>;
1084 /** The CODEOWNERS file at a branch (the default when left out), checked. Needs Read. */
1085 codeownersErrors(repo: RepoPath, viewer: Viewer, ref?: string | null): Promise<Result<CodeownersReport>>;
1086
1087 /**
1088 * Opens a pull request: a draft with a fork to push to, or, given a
1089 * branch, one ready for review.
1090 */
1091 openPull(actor: User, repo: RepoPath, input: OpenPullInput): Promise<Result<Pull>>;
1092 /** Newest first. */
1093 listPulls(
1094 repo: RepoPath,
1095 viewer: Viewer,
1096 state?: State,
1097 filter?: { label?: string; milestone?: number; base?: string },
1098 ): Promise<Result<Pull[]>>;
1099 /**
1100 * Delivery for up to 100 repositories over UTC days `from` through
1101 * `until`, over their whole history. Repositories the viewer cannot read,
1102 * and forks, are left out of `projects`.
1103 */
1104 deliveryInsights(repoIds: string[], viewer: Viewer, from: string, until: string): Promise<DeliveryInsights>;
1105 /**
1106 * The newest `limit` open and closed pull requests of each repository,
1107 * in one call. Repositories the viewer cannot read, and forks, are left
1108 * out: ask those with `listPulls`.
1109 */
1110 pullsForRepos(repoIds: string[], viewer: Viewer, limit: number): Promise<RepoPulls[]>;
1111 getPull(repo: RepoPath, number: number, viewer: Viewer): Promise<Result<PullDetail>>;
1112 /**
1113 * Changes who a pull request is assigned to and whose review is asked
1114 * for; each list given replaces the whole set. Asking for `g1t`'s
1115 * review does not by itself start one: the runner's `review` does.
1116 */
1117 updatePull(
1118 actor: User,
1119 repo: RepoPath,
1120 number: number,
1121 changes: {
1122 assignees?: string[];
1123 reviewers?: string[];
1124 labels?: string[];
1125 /** 0 takes it out of its milestone. */
1126 milestone?: number;
1127 /** The branch it merges into. Needs the Write role. */
1128 base?: string;
1129 },
1130 ): Promise<Result<Pull>>;
1131 /**
1132 * Brings a pull request up to date with the default branch in seconds,
1133 * without a sandbox, when the two changed different files: the merge
1134 * commit is pushed to its branch as `actor`, who must be whoever opened
1135 * it (for a fork) or a member (for a branch). Otherwise `needs_agent`, and
1136 * nothing is pushed: the runner's `update` is the way on.
1137 */
1138 catchUpPull(
1139 actor: User,
1140 repo: RepoPath,
1141 number: number,
1142 options?: { updateMethod?: UpdateMethod; expectedHead?: string },
1143 ): Promise<Result<PullBranchUpdate>>;
1144 /**
1145 * Turns a pull request's auto-merge on (with how to merge) or off. On needs
1146 * Write, since it merges as whoever turned it on; its author may also turn
1147 * it off. Refused where the repository does not allow auto-merge or the
1148 * method. One that may merge already merges at once.
1149 */
1150 setAutoMerge(
1151 actor: User,
1152 repo: RepoPath,
1153 number: number,
1154 enabled: boolean,
1155 options?: { mergeMethod?: MergeMethod; commitTitle?: string; commitMessage?: string },
1156 ): Promise<Result<Pull>>;
1157 /** How a pull request may merge now: methods, default messages, auto-merge and branch deletion. */
1158 mergeChoices(viewer: Viewer, repo: RepoPath, number: number): Promise<Result<MergeChoices>>;
1159 /** Deletes a merged or closed pull request's branch (Push on where it is). */
1160 deleteHeadBranch(actor: User, repo: RepoPath, number: number): Promise<Result<Pull>>;
1161 /** Makes a deleted pull request branch again at its head. */
1162 restoreHeadBranch(actor: User, repo: RepoPath, number: number): Promise<Result<Pull>>;
1163 /** A commit's comments, oldest first; with no commit, the repository's, newest first, 30 a page. */
1164 commitComments(viewer: Viewer, repo: RepoPath, commit?: string | null, page?: number): Promise<Result<CommitComment[]>>;
1165 /** Comments on a commit, or on one line of a file it changed. Needs Read, where commit comments are on. */
1166 addCommitComment(
1167 actor: User,
1168 repo: RepoPath,
1169 commit: string,
1170 body: string,
1171 at?: { path: string; line: number },
1172 ): Promise<Result<CommitComment>>;
1173 editCommitComment(actor: User, repo: RepoPath, id: string, body: string): Promise<Result<CommitComment>>;
1174 deleteCommitComment(actor: User, repo: RepoPath, id: string): Promise<Result<boolean>>;
1175 /** Marks a draft ready for review and sets its description. */
1176 readyPull(actor: User, repo: RepoPath, number: number, summary: string): Promise<Result<Pull>>;
1177 closePull(actor: User, repo: RepoPath, number: number): Promise<Result<Pull>>;
1178 /** Opens a closed pull request again, as the draft it was if it was closed as one. Never a merged one. */
1179 reopenPull(actor: User, repo: RepoPath, number: number): Promise<Result<Pull>>;
1180 /** Turns an open pull request back into a draft; it leaves the merge queue. */
1181 convertPullToDraft(actor: User, repo: RepoPath, number: number): Promise<Result<Pull>>;
1182 /**
1183 * Lands the pull request on the repository's default branch. Unless
1184 * `keepItemOpen`, that lands the work items it closes in this repository
1185 * (each closing once nothing else is left to land), and the others still
1186 * in progress here for its own close as superseded. Only members of the
1187 * repository's workspace may merge.
1188 *
1189 * If the default branch has moved, the pull request is brought up to date
1190 * first and lands when that is done; it comes back still open, and
1191 * `PullDetail.landing` is true meanwhile. A repository that requires pull
1192 * requests to be up to date refuses instead.
1193 */
1194 mergePull(actor: User, repo: RepoPath, number: number, options?: MergeOptions): Promise<Result<Pull>>;
1195 /**
1196 * Drafts and open pull requests the viewer started, most recently active
1197 * first, each with where it stands if g1t is seeing it through.
1198 */
1199 listActivePulls(
1200 viewer: Viewer,
1201 ): Promise<{ pull: Pull; item: WorkItem | null; lifecycle: Lifecycle | null }[]>;
1202 /**
1203 * The work items and pull requests a person opened, a page at a time,
1204 * only those the viewer may read. Not found for no such account.
1205 */
1206 byAuthor(username: string, viewer: Viewer, filter?: AuthoredFilter): Promise<Result<Authored>>;
1207 /**
1208 * What a person did each day of the last year (work items and pull requests
1209 * opened, reviews given), only on repositories the viewer may read: the
1210 * calendar on their profile. Not found for no such account.
1211 */
1212 contributions(username: string, viewer: Viewer): Promise<Result<Contributions>>;
1213
1214
1215
1216 appendSession(actor: User, repo: RepoPath, number: number, entries: NewSessionEntry[]): Promise<Result<{ count: number }>>;
1217 readSession(repo: RepoPath, number: number, viewer: Viewer, afterSeq?: number): Promise<Result<SessionEntry[]>>;
1218}
1219
1220/**
1221 * What to pass `ReposApi.compare` to see what a pull request changes.
1222 *
1223 * A working copy is compared as a whole. A branch, of the repository or of
1224 * a fork of it, is compared by name while the pull request is open, and by
1225 * the commit it was merged or closed at afterwards, so later pushes to the
1226 * branch do not change the record. A fork's branch is compared from the
1227 * repository it merges into (`targetId`).
1228 */
1229export function pullComparison(pull: Pull): {
1230 repoId: string;
1231 base: string | null;
1232 head: string | null;
1233 /** The branch it merges into, which it is compared from. */
1234 baseBranch: string | null;
1235 /** For a fork's branch: the repository it merges into. */
1236 targetId: string | null;
1237} {
1238 const baseBranch = pull.base ?? null;
1239 if (pull.forkRepoId) return { repoId: pull.forkRepoId, base: pull.mergeBase, head: null, baseBranch, targetId: null };
1240 const settled = pull.status === "merged" || pull.status === "closed";
1241 return {
1242 repoId: pull.headRepoId ?? pull.repoId,
1243 base: pull.mergeBase,
1244 // A fork's branch by the commit it was last seen at, which the repository
1245 // it merges into holds too, so those who read that repository can read it.
1246 head: ((settled || pull.headRepoId) && pull.headCommit) || pull.branch,
1247 baseBranch,
1248 targetId: pull.headRepoId ? pull.repoId : null,
1249 };
1250}
1251
1252/** The id of the repository holding a pull request's change: its working copy, its fork, or the repository. */
1253export function pullSourceId(pull: Pull): string {
1254 return pull.forkRepoId ?? pull.headRepoId ?? pull.repoId;
1255}
1256
1257
1258export type QueueState = "waiting" | "testing" | "passed" | "failed" | "landed" | "removed";
1259
1260/** One pull request's place in a merge queue. */
1261export type QueueEntry = {
1262 id: string;
1263 number: number;
1264 title: string;
1265 agent: string;
1266 state: QueueState;
1267 /** The pull requests merged ahead of it in the state being tested, in order. */
1268 ahead: number[];
1269 baseCommit: string | null;
1270 combinedCommit: string | null;
1271 error: string | null;
1272 results: CheckResult[];
1273 /** Username of whoever merged it into the queue: a person, or `g1t`. */
1274 enqueuedBy: string;
1275 /** RFC 3339. */
1276 createdAt: string;
1277 /** RFC 3339. */
1278 finishedAt: string | null;
1279};
1280
1281/** A repository's merge queue: what is in it, in order, and what recently left. */
1282export type QueueView = {
1283 enabled: boolean;
1284 active: QueueEntry[];
1285 /** Newest first. */
1286 recent: QueueEntry[];
1287};
1288
1289export type QueueStackItem = {
1290 number: number;
1291 title: string;
1292 /** The repository holding the change, and its branch. */
1293 source: RepoPath;
1294 branch: string;
1295 commit: string;
1296 /** How it lands: the state is built with its commits made this way. */
1297 method?: MergeMethod;
1298 /** For merge and squash: the commit's message. */
1299 message?: string;
1300 /** For squash: who the commit is by. */
1301 author?: User;
1302};
1303
1304/** What a sandbox needs to build and check one combined state. */
1305export type QueueJob = {
1306 entryId: string;
1307 token: string;
1308 repo: RepoPath;
1309 defaultBranch: string;
1310 baseCommit: string;
1311 branch: string;
1312 stack: QueueStackItem[];
1313 /** Always empty: the state is checked by the merge_group workflows run on it. */
1314 checks: string[];
1315 /** Always empty, as `checks`. */
1316 contractChecks: string[];
1317 actor: User;
1318};
1319
1320// --- A person's work ---------------------------------------------------------
1321// Mirrors the `Authored*` types in `crates/contracts/src/work.rs`.
1322
1323export type AuthoredKind = "item" | "pull";
1324/** `closed` takes in merged pull requests too; `merged` is only those. */
1325export type AuthoredState = "open" | "closed" | "merged";
1326/** `created` is newest first, `updated` most recently changed, `oldest` oldest first. */
1327export type AuthoredSort = "created" | "updated" | "oldest";
1328
1329/** The most items one `byAuthor` page holds. */
1330export const AUTHORED_PAGE = 25;
1331
1332export type AuthoredFilter = {
1333 kind?: AuthoredKind;
1334 state?: AuthoredState;
1335 /** `namespace/name`. */
1336 repo?: string;
1337 sort?: AuthoredSort;
1338 /** The `next` of the page before. */
1339 before?: string;
1340 limit?: number;
1341};
1342
1343export type AuthoredItem = {
1344 kind: AuthoredKind;
1345 /** The workspace it is numbered in. */
1346 workspace: string;
1347 /** A pull request's repository, or the repository a work item was filed from, if it was. */
1348 repo: RepoPath | null;
1349 number: number;
1350 title: string;
1351 /** A merged pull request is closed. */
1352 state: State;
1353 /** A pull request's own status; null on a work item. */
1354 status: PullStatus | null;
1355 /** Why a work item was closed. */
1356 reason: CloseReason | null;
1357 draft: boolean;
1358 merged: boolean;
1359 /** RFC 3339. */
1360 createdAt: string;
1361 /** RFC 3339. */
1362 updatedAt: string;
1363 /** RFC 3339. */
1364 mergedAt: string | null;
1365};
1366
1367/** Over every repository the viewer may read, whatever the filters. */
1368export type AuthoredCounts = {
1369 pullsMerged: number;
1370 pullsOpen: number;
1371 pulls: number;
1372 items: number;
1373 itemsOpen: number;
1374};
1375
1376export type Authored = {
1377 items: AuthoredItem[];
1378 /** Pass as `before` for the next page; null on the last. */
1379 next: string | null;
1380 counts: AuthoredCounts;
1381 /** The repositories they worked in that the viewer may read, most work first. */
1382 repos: { repo: RepoPath; count: number }[];
1383};
1384
1385// Mirrors `Contributions` in `crates/contracts/src/work.rs`.
1386
1387/** How many days `contributions` covers: today and the 364 before it. */
1388export const CONTRIBUTION_DAYS = 365;
1389
1390/** One day with something on it; days with nothing are left out. */
1391export type ContributionDay = {
1392 /** `YYYY-MM-DD`, UTC. */
1393 date: string;
1394 /** Everything that day, commits included. */
1395 count: number;
1396 /** How many of `count` are commits they pushed to a default branch. */
1397 commits?: number;
1398};
1399
1400/** A person's year, as far as the viewer may see. */
1401export type Contributions = {
1402 /** Oldest first. */
1403 days: ContributionDay[];
1404 total: number;
1405 /** The first day counted, `YYYY-MM-DD`; the last is today (UTC). */
1406 from: string;
1407};
1408
1409// --- Coordinating work: sessions, claims and dependencies -----------------
1410// Mirrors `crates/contracts/src/work/coordination.rs`.
1411
1412/** The harness a session runs in. */
1413export type Harness = "claude_code" | "claude_managed_agents" | "codex" | "cursor" | "copilot" | "other";
1414
1415/** A session as a mark on something it made. */
1416export type SessionRef = { id: string; harness: Harness };
1417
1418/** The sessions tied to one pull request (`pullSessions`). */
1419export type PullSessions = { number: number; sessions: SessionRef[] };
1420
1421/** Active: heard from in 15 minutes; idle: not since; ended: ended, or quiet for 30 minutes. */
1422export type SessionState = "active" | "idle" | "ended";
1423
1424/** One run of someone's own harness on a repository. */
1425export type WorkSession = {
1426 /** `ses_…`. */
1427 id: string;
1428 repoId: string;
1429 /** The work item it works on, by number in the repository's workspace. */
1430 item: number | null;
1431 pull: number | null;
1432 branch: string | null;
1433 actor: { id: string; username: string };
1434 harness: Harness;
1435 harnessSessionId: string | null;
1436 title: string | null;
1437 state: SessionState;
1438 headCommit: string | null;
1439 startedAt: string;
1440 lastSeenAt: string;
1441 endedAt: string | null;
1442 /** What the harness said, or `timed_out`. */
1443 endReason: string | null;
1444 /** For a subagent: the session that started it. */
1445 parentId?: string | null;
1446 /** For a subagent: its kind (Explore, security-reviewer). */
1447 agentType?: string | null;
1448 /** For a subagent: the task it was given, in a few words. */
1449 task?: string | null;
1450 /** For a subagent: the prompt its parent gave it. */
1451 prompt?: string | null;
1452 /** What it reported when it finished: a subagent's final message. */
1453 result?: string | null;
1454 model?: string | null;
1455 /** How many subagents it started. */
1456 subagents?: number;
1457 /** In a list, for one with no title: the start of the first prompt it recorded. */
1458 firstPrompt?: string | null;
1459 /** Where its title came from; none for one with no title, or from before titles said. */
1460 titleSource?: TitleSource | null;
1461 /** When its title was last settled. */
1462 titleSettledAt?: string | null;
1463};
1464
1465/**
1466 * Where a session's title came from. g1t settles a title from the whole of
1467 * the session's record: `prompt` is provisional (its first prompt's
1468 * intent) until it does; `person` is pinned from the site and never
1469 * replaced; `agent` is its agent's own (`g1t session attach --title`);
1470 * `item`, `pull` and `summary` are g1t's.
1471 */
1472export type TitleSource = "prompt" | "agent" | "person" | "item" | "pull" | "summary";
1473
1474// --- Sessions across a workspace ------------------------------------------
1475// Mirrors `crates/contracts/src/work/workspace_sessions.rs`.
1476
1477/**
1478 * What kind of session one in a workspace's list is: someone's own
1479 * `harness`, one g1t started for someone on the workspace's Anthropic
1480 * provider (`hosted`), g1t's own `agent` on a pull request, or what a coding
1481 * agent `recorded` on a pull request with no session of its own.
1482 */
1483export type SessionKind = "harness" | "hosted" | "agent" | "recorded";
1484
1485/** Which of a workspace's sessions to list: those going now, every one, or those that ended. */
1486export type SessionsShown = "live" | "recent" | "ended";
1487
1488/** One session of a workspace, whatever its kind. */
1489export type WorkspaceSession = {
1490 kind: SessionKind;
1491 /** `ses_…`, `run_…` for g1t's agent, the pull request's id for a recorded one. */
1492 id: string;
1493 /** `owner/name`. */
1494 repo: string;
1495 repoId: string;
1496 title: string | null;
1497 /** Who runs it; `g1t` for g1t's own. */
1498 actor: string | null;
1499 harness: Harness | null;
1500 /** For g1t's agent and a recorded one: the agent label. */
1501 agent: string | null;
1502 branch: string | null;
1503 item: number | null;
1504 itemTitle: string | null;
1505 pull: number | null;
1506 pullTitle: string | null;
1507 pullStatus: PullStatus | null;
1508 state: SessionState;
1509 startedAt: string;
1510 lastActivityAt: string;
1511 subagents: number;
1512 /** For g1t's agent: what it is doing now. */
1513 step: string | null;
1514 /** For a harness or hosted session: the session itself. */
1515 session: WorkSession | null;
1516 /** With `subagents`: its subagents and theirs. */
1517 children?: WorkSession[];
1518};
1519
1520/** A repository of the workspace the viewer may read. */
1521export type SessionsRepo = { id: string; path: string; defaultBranch: string };
1522
1523/** One repository's merge queue, when something waits in it. */
1524export type RepoQueue = QueueView & { repo: string; defaultBranch: string };
1525
1526/** A pull request with the repository it is in. */
1527export type RepoPull = Pull & { repo: string };
1528
1529/** `listWorkspaceSessions`. */
1530export type WorkspaceSessions = {
1531 sessions: WorkspaceSession[];
1532 queue: RepoQueue[];
1533 landed: RepoPull[];
1534 repos: SessionsRepo[];
1535};
1536
1537export type WorkspaceSessionsFilter = {
1538 shown?: SessionsShown;
1539 kind?: SessionKind;
1540 harness?: Harness;
1541 person?: string;
1542 repo?: string;
1543 item?: number;
1544 subagents?: boolean;
1545 sessionsOnly?: boolean;
1546 limit?: number;
1547};
1548
1549/** A session and the repository it runs in. */
1550export type WorkspaceSessionDetail = WorkSessionDetail & { repo: string };
1551
1552/** What kind of thing a session touched. */
1553export type TouchKind = "file" | "commit" | "branch" | "pull" | "item";
1554
1555/** Something a session touched, kept once per session. A work item's `value` is its number. */
1556export type SessionTouch = { sessionId: string; kind: TouchKind; value: string; at: string };
1557
1558/** How a session was tied to a work item or pull request. */
1559export type LinkVia = "attached" | "claim" | "branch" | "commit" | "trailer" | "opened" | "ref";
1560
1561/** A work item or pull request a session, or one of its subagents, worked on. */
1562export type SessionLink = {
1563 kind: "item" | "pull";
1564 number: number;
1565 title: string;
1566 /** The work item's state or the pull request's status. */
1567 state: string;
1568 via: LinkVia[];
1569 sessionIds: string[];
1570};
1571
1572/** A skill a session used, by name. */
1573export type SessionSkill = { sessionId: string; name: string; uses: number };
1574
1575export type ClaimState = "active" | "released" | "expired" | "handed_off";
1576
1577/** An advisory, expiring "I'm working on #N". Never a lock. */
1578export type Claim = {
1579 /** `clm_…`. */
1580 id: string;
1581 /** The repository of the checkout it was made from, if any. */
1582 repoId: string | null;
1583 /** The work item it is on: its workspace and number. */
1584 workspace: string;
1585 item: number;
1586 sessionId: string | null;
1587 actor: { id: string; username: string };
1588 harness: Harness | null;
1589 note: string | null;
1590 state: ClaimState;
1591 createdAt: string;
1592 expiresAt: string;
1593 endedAt: string | null;
1594 endReason: string | null;
1595 /** For a hand-off: who it was handed to; null for anyone. */
1596 handoffTo: string | null;
1597};
1598
1599/** `session.started` and `session.ended`. */
1600export type SessionEventData = { repoId: string; number?: number; item?: number; session: WorkSession };
1601
1602/** `work.claimed`, `work.released`, `work.claim_expired` and `work.handoff_requested`. */
1603export type ClaimEventData = {
1604 repoId?: string;
1605 workspace: string;
1606 number: number;
1607 item: number;
1608 /** The repositories the work item is linked to. */
1609 repoIds: string[];
1610 claim: Claim;
1611 others?: Claim[];
1612 tookOver?: string;
1613 reason?: string;
1614 from?: string;
1615 to?: string;
1616 note?: string;
1617};
1618
1619/** `dependency.changed`. */
1620export type DependencyChangedData = {
1621 workspace: string;
1622 number: number;
1623 item: number;
1624 repoIds: string[];
1625 blockedBy: number[];
1626 added: number[];
1627 removed: number[];
1628 cleared: number[];
1629 remaining: number[];
1630 mergedPull?: number;
1631};
1632
1633/** `overlap.detected` between two pull requests, sent once for each side of a new overlap. */
1634export type PullOverlapData = {
1635 repoId: string;
1636 number: number;
1637 pull: number;
1638 otherPull: number;
1639 files: string[];
1640 item?: number;
1641 otherItem?: number;
1642};
1643
1644/** `overlap.detected` between two sessions that touched the same files (siblings included), once for each side. */
1645export type SessionOverlapData = {
1646 repoId: string;
1647 number?: number;
1648 sessionId: string;
1649 otherSessionId: string;
1650 otherActor: string;
1651 files: string[];
1652 item?: number;
1653 otherItem?: number;
1654};
1655
1656/** `overlap.detected`: between pull requests, or between sessions (with `sessionId`). */
1657export type OverlapDetectedData = PullOverlapData | SessionOverlapData;
1658
1659/** `session.progressed`: a session recorded more (at most every few seconds). */
1660export type SessionProgressedData = { repoId: string; number?: number; sessionId: string; rootId?: string };
1661
1662/** A work item in brief. */
1663export type ItemBrief = { number: number; title: string; state: State };
1664
1665/** A pull request in brief. */
1666export type PullBrief = { number: number; title: string; status: PullStatus };
1667
1668/** A work item another is blocked by, and where it stands. */
1669export type Blocker = {
1670 number: number;
1671 title: string;
1672 state: State;
1673 /** Active claims on it. */
1674 claims: Claim[];
1675 pulls: PullBrief[];
1676};
1677
1678/** A pull request's place in the merge queue; `position` 1 is the front. */
1679export type QueuePlace = { state: QueueState; position: number };
1680
1681/** One of a work item's pull requests, with where it stands. */
1682export type WorkPull = {
1683 /** Its repository, `owner/name`. */
1684 repo: string;
1685 number: number;
1686 title: string;
1687 status: PullStatus;
1688 branch: string | null;
1689 headCommit: string | null;
1690 author: { id: string; username: string };
1691 /** `failed` when the merge queue took it out, until its head moves. */
1692 checkStatus: CheckStatus | null;
1693 requiredChecks: RequiredCheck[];
1694 mergeable: Mergeable;
1695 /** Other pull requests in progress changing the same files. */
1696 overlaps: Overlap[];
1697 /** Its place in the merge queue, while it is in it. */
1698 queue: QueuePlace | null;
1699};
1700
1701// --- Decision records (crates/contracts/src/decisions.rs) ------------------
1702
1703export type DecisionKind =
1704 | "proposal"
1705 | "claim"
1706 | "integration_failure"
1707 | "integration_passed"
1708 | "fix"
1709 | "approval"
1710 | "merge"
1711 | "note";
1712
1713/** Claimed: someone said so. Tested: a check or the merge queue proved it. Approved: a person approved it. */
1714export type DecisionConfidence = "claimed" | "tested" | "approved";
1715
1716/** A structured reason kept on a work item or pull request, with how sure it is. */
1717export type Decision = {
1718 /** `dec_…`. */
1719 id: string;
1720 /** The work item it is about, by number in its workspace. */
1721 item: number | null;
1722 pull: number | null;
1723 sessionId: string | null;
1724 /** Username of who recorded it; null when g1t did. */
1725 actor: string | null;
1726 kind: DecisionKind;
1727 confidence: DecisionConfidence;
1728 summary: string;
1729 /**
1730 * What it rests on, kept as recorded, in snake_case: `commit`, `check`
1731 * (`name`, `conclusion`, `url`), `combination`, `other_pulls`,
1732 * `suspects`, `bisected`, `merge_queue`.
1733 */
1734 evidence: Record<string, unknown>;
1735 /** RFC 3339. */
1736 createdAt: string;
1737};
1738
1739/** One repository a work item is linked to, with what landed there and its pull requests for it. */
1740export type WorkRepo = ItemRepo & {
1741 /** The checks every pull request into its default branch must pass. */
1742 requiredChecks: string[];
1743 /** Its pull requests for the work item, oldest first. */
1744 pulls: WorkPull[];
1745};
1746
1747/** A repository of the workspace the work may belong in; confirming it is `linkWorkItem` as a target. */
1748export type SuggestedTarget = { repoId: string; path: string; why: string; score: number };
1749
1750/** Everything about a work item an agent needs to pick it up: `get_work`. */
1751export type WorkContext = {
1752 item: WorkItem;
1753 /** The items under its "Definition of done" heading. */
1754 definitionOfDone: string[];
1755 /** Its repositories: origin, then targets, then touched. */
1756 repos: WorkRepo[];
1757 /** The targets it has not landed in yet, by path. */
1758 missing: string[];
1759 /** Repositories it may belong in, likeliest first, each with why. */
1760 suggestedTargets: SuggestedTarget[];
1761 /** Active claims on it. */
1762 claims: Claim[];
1763 /** Sessions on it or its pull requests, most recently heard from first. */
1764 sessions: WorkSession[];
1765 blockedBy: Blocker[];
1766 /** The work items it blocks. */
1767 blocks: ItemBrief[];
1768 /** On it and its pull requests, newest first. */
1769 decisions: Decision[];
1770 /** The latest `integration_failure` decision among them. */
1771 lastIntegrationFailure: Decision | null;
1772 /** Its latest coordination events, newest first. */
1773 recent: G1tEvent[];
1774};
1775
1776/** A harness session and what it recorded. */
1777export type WorkSessionDetail = {
1778 /** Pending repository coordination events; reading does not acknowledge them. */
1779 updates?: { id: string; kind: string; data: unknown; at: string }[];
1780 session: WorkSession;
1781 entries: SessionEntry[];
1782 /** Its subagents and theirs, in the order they started. */
1783 children?: WorkSession[];
1784 /** What it and its subagents touched. */
1785 touched?: SessionTouch[];
1786 /** The work items and pull requests it and its subagents worked on. */
1787 links?: SessionLink[];
1788 skills?: SessionSkill[];
1789 /** For a subagent: the session at the top of its tree. */
1790 root?: WorkSession | null;
1791 /**
1792 * The viewer is not a member of the repository's workspace: the session
1793 * shows that it runs, who runs it and what it is tied to, nothing more.
1794 */
1795 limited?: boolean;
1796};
1797
1798/** Reading coordination: work service methods (services/work/src/coordination, decisions.rs). */
1799export interface CoordinationApi {
1800 getWork(workspace: string, number: number, viewer: Viewer): Promise<Result<WorkContext>>;
1801 /**
1802 * A workspace's claims on the work items the viewer may see, newest
1803 * first; active ones unless `state` says otherwise. With `repo`, those
1804 * made from it or on work items linked to it.
1805 */
1806 listClaims(
1807 workspace: string,
1808 viewer: Viewer,
1809 filter?: { state?: ClaimState; item?: number; repo?: RepoPath },
1810 ): Promise<Result<Claim[]>>;
1811 /** Most recently heard from first; `item` is a work item of the repository's workspace. */
1812 listWorkSessions(
1813 repo: RepoPath,
1814 viewer: Viewer,
1815 filter?: { state?: SessionState; item?: number; pull?: number; path?: string; subagents?: boolean },
1816 ): Promise<Result<WorkSession[]>>;
1817 getWorkSession(repo: RepoPath, id: string, viewer: Viewer, afterSeq?: number): Promise<Result<WorkSessionDetail>>;
1818 /** The sessions of every repository of a workspace the viewer may read, with its merge queues and what landed. */
1819 listWorkspaceSessions(workspace: string, viewer: Viewer, filter?: WorkspaceSessionsFilter): Promise<Result<WorkspaceSessions>>;
1820 /** One harness or hosted session of a workspace, in whichever of its repositories it runs. */
1821 getWorkspaceSession(workspace: string, id: string, viewer: Viewer, afterSeq?: number): Promise<Result<WorkspaceSessionDetail>>;
1822 /**
1823 * Ends a session by hand, from the site: the actor's own, or anyone's
1824 * when they have write access to the repository. It ends as
1825 * `ended by hand by @actor`, with its subagents still going
1826 * (`end_session` with `by_hand`; g1t_contracts::work::session_end_reason).
1827 */
1828 endWorkSession(actor: User, repo: RepoPath, id: string): Promise<Result<WorkSession>>;
1829 /**
1830 * A person names a session from the site, pinning its title: neither
1831 * g1t nor its agent replaces it. No title unpins it, and g1t names it
1832 * again from its record. The actor's own, or anyone's with write access
1833 * to the repository (`title_session`).
1834 */
1835 titleWorkSession(actor: User, repo: RepoPath, id: string, title: string | null): Promise<Result<WorkSession>>;
1836 /**
1837 * The harness sessions tied to some pull requests (at most 100), any way
1838 * a session is tied to one; a subagent's as the session that started it.
1839 * Only pull requests with any are listed.
1840 */
1841 pullSessions(repo: RepoPath, viewer: Viewer, numbers: number[]): Promise<Result<PullSessions[]>>;
1842 /** A work item's (with its pull requests') or a pull request's, oldest first. */
1843 listDecisions(thread: Thread, viewer: Viewer): Promise<Result<Decision[]>>;
1844}
1845
1846/**
1847 * Delivery counts cover the requested UTC days; waiting counts are current.
1848 * `checking` and `failed` count each open pull request once, by its head's
1849 * checks (statuses, check runs and g1t Actions jobs); one with none is in neither.
1850 */
1851export type DeliveryMetrics = { repoId: string; opened: number; merged: number; active: number; unreviewed: number; checking: number; failed: number; queued: number; leadSeconds: number; reviewSeconds: number; reviewed: number };
1852/** `checksUnavailable`: g1t Actions could not be read, so `checking` and `failed` are not known. */
1853export type DeliveryInsights = { projects: DeliveryMetrics[]; days: { day: string; merged: number }[]; checksUnavailable: boolean };