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