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