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.

g1t.sh
1,148 linesCodeBlameRaw
1import type { ChecksApi } from "./checks";
2import type { CodeownersReport, PullCodeOwners } from "./codeowners";
3import type { User, Viewer } from "./identity";
4import type { PullBranchUpdate, RepoPath } from "./repos";
5import type { Result } from "./result";
6import type { MergeRules, RulesApi } from "./rules";
7
8/**
9 * The filter on lists of issues and pull requests. An open pull request is
10 * a draft or one ready for review; a closed one was merged or closed
11 * without merging.
12 */
13export type State = "open" | "closed";
14
15/** Why an issue was closed. */
16export type IssueReason = "completed" | "not_planned";
17
18/**
19 * Something that should change in a repository: a bug, a feature, a
20 * question. Opened by a person, an agent or an integration. Pull requests
21 * are made against it; the one that is merged resolves it.
22 *
23 * Issues and pull requests share one sequence of numbers per repository.
24 */
25export type Issue = {
26 id: string;
27 repoId: string;
28 /** Shown as `#12`. */
29 number: number;
30 title: string;
31 /** Markdown. Also what an agent is given to work from. */
32 body: string;
33 labels: string[];
34 state: State;
35 /** Set when closed. */
36 reason: IssueReason | null;
37 /** The number of the pull request whose merge closed this issue. */
38 resolvedBy: number | null;
39 /** Who opened it: a person, an integration, or g1t (`kind` `agent`) for one its agent filed while at work. */
40 author: User;
41 /**
42 * For an issue g1t's agent filed: the person it was working for. They may
43 * manage it as its author could. See `workOwner`.
44 */
45 requestedBy: User | null;
46 /** RFC 3339. */
47 createdAt: string;
48 /** RFC 3339. */
49 updatedAt: string;
50 /** RFC 3339. */
51 closedAt: string | null;
52 /** Pull requests made against this issue, in any state. */
53 pullCount: number;
54 commentCount: number;
55 /** Usernames of the people it is assigned to. */
56 assignees: string[];
57 /** The numbers of the issues that have to be merged before this one is worked on. */
58 blockedBy: number[];
59 /**
60 * Whether a g1t agent takes it as soon as it can: at once, or when what it
61 * is blocked by has merged.
62 */
63 queued: boolean;
64 /**
65 * The agent working on it now: the one behind its newest pull request
66 * that is still in progress in a fork, such as `g1t`.
67 */
68 agent: string | null;
69 /** The milestone it is in, if any. */
70 milestone?: MilestoneRef | null;
71};
72
73/**
74 * A label of a repository: a name, a color and what it means. Issues and
75 * pull requests carry labels by name; names are lowercase.
76 */
77export type Label = {
78 name: string;
79 /** Six hex digits, without `#`. */
80 color: string;
81 description: string;
82 /** How many issues carry it, open or closed. */
83 issues: number;
84 /** How many pull requests carry it, in any state. */
85 pulls: number;
86};
87
88/** A milestone, as an issue or pull request names it. */
89export type MilestoneRef = { number: number; title: string };
90
91/**
92 * A goal, with an optional due date, that issues and pull requests are
93 * gathered under. Its progress is how many of them are closed.
94 */
95export type Milestone = {
96 /** Numbered from 1 in each repository, apart from issues. */
97 number: number;
98 title: string;
99 /** Markdown. */
100 description: string;
101 /** `YYYY-MM-DD`. */
102 dueOn: string | null;
103 state: State;
104 /** Open issues and pull requests in it. */
105 openItems: number;
106 /** Closed issues, and merged or closed pull requests, in it. */
107 closedItems: number;
108 createdAt: string;
109 updatedAt: string;
110 closedAt: string | null;
111};
112
113/** One milestone and what is in it, newest first. */
114export type MilestoneDetail = { milestone: Milestone; issues: Issue[]; pulls: Pull[] };
115
116/** How `setLabels` changes an item's labels. */
117export type LabelChange = "set" | "add" | "remove";
118
119/**
120 * Whose an issue or a pull request is to answer for: whoever asked g1t for
121 * it, or its author. They may change, close and steer it, are never asked to
122 * review it and cannot approve it, and see it as theirs. Mirrors
123 * `Pull::owner` in the Rust contracts.
124 */
125export function workOwner(item: Pick<Pull, "author" | "requestedBy">): User {
126 return item.requestedBy ?? item.author;
127}
128
129/** `draft` is still being worked on; `open` is ready for review. */
130export type PullStatus = "draft" | "open" | "merged" | "closed";
131
132/** Where the agent runs: on g1t's sandboxes, or in someone's own session. */
133export type Runtime = "hosted" | "external";
134
135/**
136 * A proposed change. It is made either in a fork created for it, which is
137 * how agents work, or on a branch pushed to the repository itself.
138 */
139export type Pull = {
140 id: string;
141 repoId: string;
142 /** Shown as `#12`. */
143 number: number;
144 /** The number of the issue this is for, if any. */
145 issue: number | null;
146 title: string;
147 /** Markdown: what changed and why. Set when marked ready. */
148 body: string | null;
149 /** A label for the agent doing the work, e.g. `claude-code`. */
150 agent: string;
151 runtime: Runtime;
152 status: PullStatus;
153 /** The fork holding the change, unless it is on a branch. */
154 fork: RepoPath | null;
155 /** The fork's repository id. */
156 forkRepoId: string | null;
157 /** The branch of the repository holding the change, unless it is in a fork. */
158 branch: string | null;
159 headCommit: string | null;
160 /**
161 * For a merged pull request, what the branch pointed to before the merge.
162 * Comparing against it shows what the pull request changed.
163 */
164 mergeBase: string | null;
165 /** Username of whoever merged it. */
166 mergedBy: string | null;
167 /** RFC 3339. */
168 mergedAt: string | null;
169 /**
170 * Set on a pull request closed because another one for the same issue was
171 * merged: that one's number.
172 */
173 supersededBy: number | null;
174 /**
175 * Where the latest run of the issue's acceptance checks stands, if there
176 * has been one against the current head.
177 */
178 checkStatus: CheckStatus | null;
179 /** The files it changes, as of its latest push. */
180 files: ChangedFile[];
181 /** Usernames of the people it is assigned to. */
182 assignees: string[];
183 /**
184 * Those whose review was asked for: usernames, and `g1t` when a g1t
185 * agent was asked.
186 */
187 reviewers: string[];
188 /**
189 * Teams whose review was asked for, as `workspace/slug`. A team stays here
190 * after review assignment picks people from it, who are in `reviewers`.
191 */
192 teamReviewers?: string[];
193 /** The labels it carries, by name. */
194 labels?: string[];
195 /** The milestone it is in, if any. */
196 milestone?: MilestoneRef | null;
197 /**
198 * The branch it merges into. Lists and `getPull` name it; null only in
199 * what services pass between themselves, for the default branch.
200 */
201 base?: string | null;
202 /** Who opened it: a person, or g1t (`kind` `agent`, username `g1t`) for a change g1t made. */
203 author: User;
204 /**
205 * For a change g1t made: the person who asked for it, by assigning an issue
206 * or handing g1t the work. They answer for it as its author would. See
207 * `workOwner`.
208 */
209 requestedBy: User | null;
210 /** RFC 3339. */
211 createdAt: string;
212 /** RFC 3339. */
213 updatedAt: string;
214 /** Who last moved its head (a user id), and when; absent until a push after rulesets arrived. */
215 headPushedBy?: string;
216 headPushedAt?: string;
217};
218
219/** One file a pull request changes, and by how much. */
220export type ChangedFile = { path: string; additions: number; deletions: number };
221
222/**
223 * Another pull request in progress that changes some of the same files. Two
224 * for the same issue are alternatives; two for different issues are heading
225 * for a conflict.
226 */
227export type Overlap = {
228 number: number;
229 title: string;
230 /** The number of the issue the other pull request is for. */
231 issue: number | null;
232 /** The files both change. */
233 paths: string[];
234};
235
236/**
237 * On a pull request, `failed` means the merge queue took it out, until its
238 * head moves. The other states are from runs of commands written on issues,
239 * which g1t no longer runs.
240 */
241export type CheckStatus = "queued" | "running" | "passed" | "failed" | "errored";
242
243/** How one command went, in a run recorded before checks were workflows. */
244export type CheckResult = {
245 command: string;
246 passed: boolean;
247 /** Null when the command was stopped for taking too long. */
248 exitCode: number | null;
249 /** What the command printed; the end of it, when there was a lot. */
250 output: string;
251 durationMs: number;
252};
253
254/**
255 * A record against a pull request's head: the merge queue taking it out,
256 * with why, or an earlier run of commands written on its issue.
257 */
258export type CheckRun = {
259 id: string;
260 /** The commit that was checked. */
261 headCommit: string;
262 status: CheckStatus;
263 results: CheckResult[];
264 /** Why the checks could not be run, when `status` is `errored`. */
265 error: string | null;
266 /** RFC 3339. */
267 createdAt: string;
268 /** RFC 3339. */
269 finishedAt: string | null;
270};
271
272/** A reviewer's decision on a pull request. */
273export type Verdict = "approve" | "request_changes";
274
275/**
276 * A comment on an issue or a pull request. On a pull request it can sit on
277 * one line of the change, and it can carry a reviewer's verdict.
278 */
279export type Comment = {
280 id: string;
281 /**
282 * Something a person or an agent wrote, or something that happened: an
283 * assignment, a review asked for, a close.
284 */
285 kind: "comment" | "event";
286 author: User;
287 /**
288 * Markdown. For an event, what its author did, as the rest of a sentence
289 * that starts with their name: "assigned ana".
290 */
291 body: string;
292 /** The file commented on, for a comment on a line. */
293 path: string | null;
294 /** The line of that file, as numbered after the change. */
295 line: number | null;
296 verdict: Verdict | null;
297 /** RFC 3339. */
298 createdAt: string;
299 /** When its text was last edited, RFC 3339; absent if it never was. */
300 editedAt?: string;
301 /**
302 * Set when one of the workspace's agents wrote it, as itself. `author`
303 * is then the agent (its id, its handle as `username`, kind `agent`):
304 * show it by `agent.displayName` with an Agent badge, never as a person.
305 */
306 agent?: AgentRef;
307 /** Who the agent acted for, whose access capped it. Set with `agent`. */
308 actingFor?: User;
309 /**
310 * An agent's review. Its `verdict` (none for a review that only
311 * comments) is shown but advisory: it never counts toward required
312 * approvals or code owners, and never blocks a merge.
313 */
314 advisory?: boolean;
315};
316
317/**
318 * One of a workspace's own agents, as a comment or review it wrote before
319 * g1t's agents were removed shows it: Margo (@margo), with the face drawn
320 * from `avatarSeed`. Kept so those comments still render.
321 */
322export type AgentRef = {
323 id: string;
324 handle: string;
325 displayName: string;
326 avatarSeed: string;
327};
328
329export type NewComment = {
330 /** May be empty when approving. */
331 body: string;
332 path?: string;
333 line?: number;
334 verdict?: Verdict;
335};
336
337/** What a sandbox needs to carry out a check run. */
338export type CheckJob = {
339 runId: string;
340 /** Lets the sandbox, and nothing else, report this run's results. */
341 token: string;
342 commands: string[];
343 /** The repository holding the commit: the fork, or the repository itself. */
344 source: RepoPath;
345 commit: string;
346 /** Who the pull request is for (`workOwner`: whoever asked g1t for it, or its author), and so can read its source. */
347 author: User;
348 /** Username of whoever wrote the checks: the issue's author. */
349 requestedBy: string;
350 repo: RepoPath;
351 number: number;
352};
353
354export type CheckReport = { results?: CheckResult[]; error?: string; skip?: boolean };
355
356export type SessionEntryKind = "prompt" | "message" | "tool_call" | "tool_result" | "note";
357
358/** One step of an agent's session: the "why" behind a pull request's commits. */
359export type SessionEntry = {
360 seq: number;
361 kind: SessionEntryKind;
362 text: string;
363 /** For tool calls and results. */
364 tool: string | null;
365 /** The fork's head commit when this entry was recorded, if known. */
366 commit: string | null;
367 /** RFC 3339. */
368 at: string;
369};
370
371export type NewSessionEntry = Pick<SessionEntry, "kind" | "text"> &
372 Partial<Pick<SessionEntry, "tool" | "commit">>;
373
374export type IssueDetail = {
375 issue: Issue;
376 /** Every pull request made against it, oldest first. */
377 pulls: Pull[];
378 comments: Comment[];
379};
380
381export type PullDetail = {
382 pull: Pull;
383 /** The issue it is for, if any. */
384 issue: Issue | null;
385 comments: Comment[];
386 /** The latest record against its head: the merge queue taking it out. */
387 checks: CheckRun | null;
388 /** Other pull requests in progress that change the same files. */
389 overlaps: Overlap[];
390 /**
391 * Whether the branch it would merge into has moved on without it, so that
392 * it has to catch up before it can merge.
393 */
394 behind: boolean;
395 /**
396 * Where it stands on its way to being merged, for a pull request g1t is
397 * seeing through. Null on anyone else's.
398 */
399 lifecycle: Lifecycle | null;
400 /**
401 * A merge was asked for while it was behind: g1t is bringing it up to
402 * date and will then land it.
403 */
404 landing: boolean;
405 /** Why g1t stopped working on it, if it did. */
406 stalled: string | null;
407 /** What workflow runs said about its head commit, one per workflow. */
408 statuses?: CommitStatus[];
409 /**
410 * Whether it merges cleanly into the branch it targets, worked out ahead
411 * of time whenever either side moves.
412 */
413 mergeable?: Mergeable;
414 /** When `mergeable` is `conflicting`: the files that conflict. */
415 conflicts?: string[];
416 /** Earlier records like `checks`, newest first, without their output. */
417 earlierChecks?: CheckRun[];
418 /**
419 * The checks the default branch's protection requires, each as it stands
420 * on the head commit. Empty when none are required.
421 */
422 requiredChecks?: RequiredCheck[];
423 /**
424 * Who owns the files it changes (the CODEOWNERS file of the branch it
425 * merges into) and whose approval is still needed. Absent without one.
426 */
427 codeOwners?: PullCodeOwners | null;
428 /**
429 * The rules of the branch it merges into it does not meet yet, for whoever
430 * is looking: what refuses the merge, what they may bypass, and what rulesets
431 * in evaluate would refuse. Absent once it is closed or merged.
432 */
433 rules?: MergeRules | null;
434};
435
436/** Where a required check stands on a commit; `expected` when nothing has reported it yet. */
437export type RequiredState = "success" | "failure" | "pending" | "expected";
438
439/** One check a branch's protection requires, as it stands on a commit. */
440export type RequiredCheck = {
441 /** A workflow's name, such as `CI`, or another status's context. */
442 name: string;
443 state: RequiredState;
444 description: string | null;
445 /** Where to see more: the workflow run, for one a workflow reported. */
446 targetUrl: string | null;
447};
448
449/** A check name reported on a repository's commits lately, for choosing required checks. */
450export type SeenCheck = {
451 name: string;
452 /** The events it was reported for, such as `pull_request`; empty for a status that names none. */
453 events: string[];
454 /** RFC 3339. */
455 lastSeen: string;
456};
457
458/**
459 * A status context's check name: `CI / pull_request` is the `CI` check,
460 * reported for a `pull_request` event. A context that does not end in an
461 * event, such as `g1t / deploy`, is its own name. As the work service reads it.
462 */
463export function checkName(context: string): string {
464 const at = context.lastIndexOf(" / ");
465 if (at <= 0) return context;
466 return STATUS_EVENTS.has(context.slice(at + 3)) ? context.slice(0, at) : context;
467}
468
469const STATUS_EVENTS = new Set([
470 "push",
471 "pull_request",
472 "pull_request_target",
473 "pull_request_review",
474 "merge_group",
475 "workflow_dispatch",
476 "workflow_run",
477 "workflow_call",
478 "schedule",
479 "release",
480 "issues",
481 "issue_comment",
482 "repository_dispatch",
483]);
484
485/**
486 * Whether a pull request merges cleanly into the branch it targets:
487 * `unknown` when it was never worked out or could not be, `checking` while
488 * a probe merges the two.
489 */
490export type Mergeable = "clean" | "conflicting" | "unknown" | "checking";
491
492/** What a sandbox needs to find out whether a pull request merges cleanly. */
493export type MergecheckJob = {
494 pullId: string;
495 /** Lets the sandbox, and nothing else, report this probe. */
496 token: string;
497 repo: RepoPath;
498 number: number;
499 defaultBranch: string;
500 /** The default branch's commit to merge into. */
501 base: string;
502 /** The repository holding the change: its fork, or the repository. */
503 source: RepoPath;
504 /** The branch of `source` holding it. */
505 branch: string;
506 /** The change's commit. */
507 head: string;
508 /** Who the pull request is for (`workOwner`: whoever asked g1t for it, or its author), and so can read its source. */
509 author: User;
510};
511
512/** What a workflow run (or another tool) says about a commit. */
513export type CommitStatus = {
514 /** What reported it, such as `CI / push`. */
515 context: string;
516 state: "pending" | "success" | "failure" | "error";
517 description: string | null;
518 targetUrl: string | null;
519 updatedAt: string;
520 /** The integration that reported it: `actions`, `deployments`, `security`, `g1t` or `api`. */
521 source?: string;
522 /** Set on the status a check run stands as: the check run's id. */
523 checkRunId?: string;
524};
525
526/**
527 * A step on the way from an assigned issue to a pull request that is ready
528 * to merge. g1t takes each one without being asked: `working` (the agent is
529 * making the change), `checking`, `reviewing`, `revising` (the agent is
530 * addressing failed checks or a review), `catching_up` (merging in the
531 * branch it would land on), `answering` (woken to answer another agent),
532 * then `ready` for a person to merge. `needs_you`
533 * means g1t has stopped and a person decides what happens next.
534 */
535export type Stage =
536 | "working"
537 | "checking"
538 | "reviewing"
539 | "revising"
540 | "catching_up"
541 | "answering"
542 | "queued"
543 | "ready"
544 | "needs_you";
545
546export type Lifecycle = {
547 stage: Stage;
548 /** One sentence saying what is happening, or why it stopped. */
549 detail: string;
550 /** How many times the agent has been sent back to revise it. */
551 revisions: number;
552};
553
554/**
555 * How a repository wants its pull requests handled. A repository that has
556 * changed nothing has the defaults.
557 */
558export type RepoSettings = {
559 /**
560 * Land a g1t agent's pull request without a person once it is ready:
561 * checks passed and approved as the settings below require.
562 */
563 autoMerge: boolean;
564 /**
565 * The checks that must pass on a pull request's head before it may merge
566 * into the default branch, by name: a workflow's name (`CI`) or another
567 * status's context (`g1t / deploy`). The same for people and agents, and
568 * for the merge queue.
569 */
570 requiredChecks: string[];
571 /**
572 * Refuse to merge a pull request that does not contain the default
573 * branch's latest commits, so that what merges is what was checked. When
574 * off, merging one that is behind brings it up to date first.
575 */
576 requireUpToDate: boolean;
577 /**
578 * How many approving reviews a pull request needs before it may merge. A
579 * reviewer who has since asked for changes blocks it.
580 */
581 requiredApprovals: number;
582 /** Whether a g1t agent's approval counts towards `requiredApprovals`. */
583 countAgentApprovals: boolean;
584 /** Whether someone who may merge can bypass required checks that have not passed. */
585 allowIgnoringChecks: boolean;
586 /**
587 * Merge through a queue: pull requests are tested together with those
588 * ahead of them, and only a combination that passed reaches the default
589 * branch.
590 */
591 mergeQueue: boolean;
592 /**
593 * Refuse to merge until the code owners of every file it changes have
594 * approved, as many as each section asks. People only; `g1t` only where
595 * the file names `@g1t`.
596 */
597 requireCodeOwnerReview?: boolean;
598 /** Username of the member who last changed the settings, if anyone has. */
599 updatedBy: string | null;
600 /** RFC 3339. */
601 updatedAt: string | null;
602};
603
604/** The settings a member can change. */
605export type RepoSettingsInput = Omit<RepoSettings, "updatedBy" | "updatedAt">;
606
607/**
608 * What the runner is to do next for a pull request: always nothing now,
609 * since no step of a pull request is carried out in a sandbox any more.
610 */
611export type Advance = { action: "none" };
612
613export type OpenIssueInput = {
614 title: string;
615 body: string;
616 labels?: string[];
617 /** Deprecated: commands, added to the body under "Definition of done". */
618 checks?: string[];
619 /** The number of the milestone to put it in. Needs the Triage role. */
620 milestone?: number;
621};
622
623export type UpdateIssueInput = {
624 title?: string;
625 body?: string;
626 labels?: string[];
627 /** Usernames of the people it is assigned to; replaces the whole set. */
628 assignees?: string[];
629 /** The number of the milestone to put it in; 0 takes it out. */
630 milestone?: number;
631 /** The issues of the same repository that have to land first; replaces the whole set. */
632 blocked_by?: number[];
633};
634
635export type OpenPullInput = {
636 /** The number of the issue this is for. */
637 issue?: number;
638 /** Defaults to the issue's title; required without an issue. */
639 title?: string;
640 /** What changed and why. Usually set later, when a draft is marked ready. */
641 body?: string;
642 /**
643 * A branch of the repository that already holds the change. The pull
644 * request is then ready for review at once and has no fork.
645 */
646 branch?: string;
647 agent: string;
648 runtime: Runtime;
649 /** The branch to merge into: the default branch when left out. */
650 base?: string;
651 /**
652 * Opened from a branch as a draft, still being worked on: it can't merge,
653 * and agents' review routines wait, until it is marked ready.
654 */
655 draft?: boolean;
656};
657
658/** One repository's pull requests from `pullsForRepos`, newest first. */
659export type RepoPulls = {
660 repoId: string;
661 /** Draft and open. */
662 open: Pull[];
663 /** Merged and closed. */
664 closed: Pull[];
665};
666
667/** Issues, pull requests, comments and sessions. */
668export interface WorkApi extends RulesApi, ChecksApi {
669 openIssue(actor: User, repo: RepoPath, input: OpenIssueInput): Promise<Result<Issue>>;
670 /** Newest first. */
671 listIssues(
672 repo: RepoPath,
673 viewer: Viewer,
674 filter?: { state?: State; label?: string; milestone?: number },
675 ): Promise<Result<Issue[]>>;
676 getIssue(repo: RepoPath, number: number, viewer: Viewer): Promise<Result<IssueDetail>>;
677 /** The author or a member of the workspace may. */
678 updateIssue(actor: User, repo: RepoPath, number: number, input: UpdateIssueInput): Promise<Result<Issue>>;
679 closeIssue(actor: User, repo: RepoPath, number: number, reason?: IssueReason): Promise<Result<Issue>>;
680 reopenIssue(actor: User, repo: RepoPath, number: number): Promise<Result<Issue>>;
681 /** A repository's labels, by name, each with how many issues and pull requests carry it. */
682 listLabels(repo: RepoPath, viewer: Viewer): Promise<Result<Label[]>>;
683 /**
684 * Creates a label, or with `name` changes one; renaming it renames it on
685 * everything that carries it. Needs the Triage role.
686 */
687 saveLabel(
688 actor: User,
689 repo: RepoPath,
690 label: { name?: string; newName?: string; color?: string; description?: string },
691 ): Promise<Result<Label>>;
692 /** Removes a label from the repository and everything carrying it. */
693 deleteLabel(actor: User, repo: RepoPath, name: string): Promise<Result<boolean>>;
694 /** Adds the default labels the repository does not have yet; returns them all. */
695 addDefaultLabels(actor: User, repo: RepoPath): Promise<Result<Label[]>>;
696 /**
697 * The labels of an issue or a pull request, replaced, added to or taken
698 * from. Labels the repository lacks are created for someone with the
699 * Triage role. Returns its labels now.
700 */
701 setLabels(
702 actor: User,
703 repo: RepoPath,
704 number: number,
705 labels: string[],
706 change?: LabelChange,
707 ): Promise<Result<string[]>>;
708 /** Open ones by due date, then closed ones; both unless `state` says. */
709 listMilestones(repo: RepoPath, viewer: Viewer, state?: State): Promise<Result<Milestone[]>>;
710 getMilestone(repo: RepoPath, number: number, viewer: Viewer): Promise<Result<MilestoneDetail>>;
711 /** Creates a milestone, or with `number` changes the fields given. Needs the Triage role. */
712 saveMilestone(
713 actor: User,
714 repo: RepoPath,
715 milestone: { number?: number; title?: string; description?: string; dueOn?: string; state?: State },
716 ): Promise<Result<Milestone>>;
717 deleteMilestone(actor: User, repo: RepoPath, number: number): Promise<Result<boolean>>;
718 /** How many issues and pull requests are open. */
719 counts(repo: RepoPath, viewer: Viewer): Promise<Result<{ issues: number; pulls: number }>>;
720
721 /**
722 * On an issue or a pull request. On a pull request it may name a line of
723 * the change and carry a verdict; nobody can give a verdict on their own.
724 */
725 addComment(actor: User, repo: RepoPath, number: number, comment: NewComment): Promise<Result<Comment>>;
726 /**
727 * Changes a comment's text. Its author may, and so may anyone with the
728 * Maintain role or higher; notes of what happened cannot be changed.
729 */
730 editComment(actor: User, repo: RepoPath, commentId: string, body: string): Promise<Result<Comment>>;
731 /**
732 * Deletes a comment: its author, or anyone with the Maintain role or
733 * higher. A review that gave a verdict cannot be deleted, only edited.
734 */
735 deleteComment(actor: User, repo: RepoPath, commentId: string): Promise<Result<boolean>>;
736
737 /**
738 * Always refused now: a pull request's checks are the workflows run on
739 * it. Kept for a runner from before.
740 */
741 startChecks(pullId: string): Promise<Result<CheckJob>>;
742 /**
743 * What a sandbox says about a run from before checks were workflows: so
744 * one still finishing is recorded.
745 */
746 reportChecks(runId: string, token: string, report: CheckReport): Promise<Result<CheckRun>>;
747 /**
748 * Claims the probe of whether a pull request merges cleanly that a
749 * `pull.mergecheck` event asked for. Refused when it is no longer wanted
750 * or the repository has as many running as it may. For the runner service.
751 */
752 startMergecheck(pullId: string): Promise<Result<MergecheckJob>>;
753 /** Records that a probe could not be carried out. For the runner service. */
754 failMergecheck(pullId: string, token: string, error: string): Promise<Result<Mergeable>>;
755
756 /**
757 * Works out the next step for a pull request g1t is seeing through and,
758 * if there is one to take now, claims it, so that it is taken once
759 * however often this is called. For the runner service.
760 */
761 advance(pullId: string): Promise<Advance>;
762 /** Records that a step could not be carried out, so a person is asked. */
763 stall(pullId: string, reason: string): Promise<boolean>;
764 /** Ids of the open pull requests g1t is seeing through. */
765 managedPulls(repoId?: string): Promise<string[]>;
766
767 /** A repository's merge queue: what is in it, in order, and what recently left. */
768 queue(repo: RepoPath, viewer: Viewer): Promise<Result<QueueView>>;
769 /**
770 * The next batch of combined states to test for a repository, one per
771 * entry; empty while a batch is being tested or nothing waits.
772 */
773 queueBuild(repoId: string): Promise<QueueJob[]>;
774 /** Reports that a combined state could not be built or checked. */
775 failQueue(entryId: string, token: string, error: string): Promise<Result<QueueState>>;
776 /** Takes a pull request out of the merge queue. Members only. */
777 removeFromQueue(actor: User, repo: RepoPath, number: number): Promise<Result<Pull>>;
778
779 getSettings(repo: RepoPath, viewer: Viewer): Promise<Result<RepoSettings>>;
780 /** The check names reported on the repository's commits in the last 30 days, most recent first. */
781 seenChecks(repo: RepoPath, viewer: Viewer): Promise<Result<SeenCheck[]>>;
782 /** Members of the repository's workspace only. */
783 updateSettings(actor: User, repo: RepoPath, settings: RepoSettingsInput): Promise<Result<RepoSettings>>;
784 /** The CODEOWNERS file at a branch (the default when left out), checked. Needs Read. */
785 codeownersErrors(repo: RepoPath, viewer: Viewer, ref?: string | null): Promise<Result<CodeownersReport>>;
786
787 /**
788 * Opens a pull request: a draft with a fork to push to, or, given a
789 * branch, one ready for review.
790 */
791 openPull(actor: User, repo: RepoPath, input: OpenPullInput): Promise<Result<Pull>>;
792 /** Newest first. */
793 listPulls(
794 repo: RepoPath,
795 viewer: Viewer,
796 state?: State,
797 filter?: { label?: string; milestone?: number; base?: string },
798 ): Promise<Result<Pull[]>>;
799 /**
800 * The newest `limit` open and closed pull requests of each repository,
801 * in one call. Repositories the viewer cannot read, and forks, are left
802 * out: ask those with `listPulls`.
803 */
804 pullsForRepos(repoIds: string[], viewer: Viewer, limit: number): Promise<RepoPulls[]>;
805 getPull(repo: RepoPath, number: number, viewer: Viewer): Promise<Result<PullDetail>>;
806 /**
807 * Changes who a pull request is assigned to and whose review is asked
808 * for; each list given replaces the whole set. Asking for `g1t`'s
809 * review does not by itself start one: the runner's `review` does.
810 */
811 updatePull(
812 actor: User,
813 repo: RepoPath,
814 number: number,
815 changes: {
816 assignees?: string[];
817 reviewers?: string[];
818 labels?: string[];
819 /** 0 takes it out of its milestone. */
820 milestone?: number;
821 /** The branch it merges into. Needs the Write role. */
822 base?: string;
823 },
824 ): Promise<Result<Pull>>;
825 /**
826 * Brings a pull request up to date with the default branch in seconds,
827 * without a sandbox, when the two changed different files: the merge
828 * commit is pushed to its branch as `actor`, who must be whoever opened
829 * it (for a fork) or a member (for a branch). Otherwise `needs_agent`, and
830 * nothing is pushed: the runner's `update` is the way on.
831 */
832 catchUpPull(actor: User, repo: RepoPath, number: number): Promise<Result<PullBranchUpdate>>;
833 /** Marks a draft ready for review and sets its description. */
834 readyPull(actor: User, repo: RepoPath, number: number, summary: string): Promise<Result<Pull>>;
835 closePull(actor: User, repo: RepoPath, number: number): Promise<Result<Pull>>;
836 /** Opens a closed pull request again, as the draft it was if it was closed as one. Never a merged one. */
837 reopenPull(actor: User, repo: RepoPath, number: number): Promise<Result<Pull>>;
838 /** Turns an open pull request back into a draft; it leaves the merge queue. */
839 convertPullToDraft(actor: User, repo: RepoPath, number: number): Promise<Result<Pull>>;
840 /**
841 * Lands the pull request on the repository's default branch. Unless
842 * `keepIssueOpen`, that resolves the issue it was for: the issue closes
843 * naming this pull request, and the others still in progress for it close
844 * as superseded. Only members of the repository's workspace may merge.
845 *
846 * If the default branch has moved, the pull request is brought up to date
847 * first and lands when that is done; it comes back still open, and
848 * `PullDetail.landing` is true meanwhile. A repository that requires pull
849 * requests to be up to date refuses instead.
850 */
851 mergePull(
852 actor: User,
853 repo: RepoPath,
854 number: number,
855 options?: {
856 keepIssueOpen?: boolean;
857 /** Merge although required checks have not passed, where the rule requiring them allows it. */
858 ignoreChecks?: boolean;
859 /** Merge past rules a ruleset lets the actor bypass. Recorded as a bypass. */
860 bypassRules?: boolean;
861 },
862 ): Promise<Result<Pull>>;
863 /**
864 * Drafts and open pull requests the viewer started, most recently active
865 * first, each with where it stands if g1t is seeing it through.
866 */
867 listActivePulls(
868 viewer: Viewer,
869 ): Promise<{ pull: Pull; issue: Issue | null; lifecycle: Lifecycle | null }[]>;
870 /**
871 * The issues and pull requests a person opened, a page at a time, only
872 * on repositories the viewer may read. Not found for no such account.
873 */
874 byAuthor(username: string, viewer: Viewer, filter?: AuthoredFilter): Promise<Result<Authored>>;
875 /**
876 * What a person did each day of the last year (issues and pull requests
877 * opened, reviews given), only on repositories the viewer may read: the
878 * calendar on their profile. Not found for no such account.
879 */
880 contributions(username: string, viewer: Viewer): Promise<Result<Contributions>>;
881
882
883 /** Open issues assigned to the viewer, most recently changed first. */
884 listAssignedIssues(viewer: Viewer): Promise<Issue[]>;
885
886 appendSession(actor: User, repo: RepoPath, number: number, entries: NewSessionEntry[]): Promise<Result<{ count: number }>>;
887 readSession(repo: RepoPath, number: number, viewer: Viewer, afterSeq?: number): Promise<Result<SessionEntry[]>>;
888}
889
890/**
891 * What to pass `ReposApi.compare` to see what a pull request changes.
892 *
893 * A fork is compared as a whole. A branch is compared by name while the
894 * pull request is open, and by the commit it was merged or closed at
895 * afterwards, so later pushes to the branch do not change the record.
896 */
897export function pullComparison(pull: Pull): {
898 repoId: string;
899 base: string | null;
900 head: string | null;
901 /** The branch it merges into, which it is compared from. */
902 baseBranch: string | null;
903} {
904 const baseBranch = pull.base ?? null;
905 if (pull.forkRepoId) return { repoId: pull.forkRepoId, base: pull.mergeBase, head: null, baseBranch };
906 const settled = pull.status === "merged" || pull.status === "closed";
907 return {
908 repoId: pull.repoId,
909 base: pull.mergeBase,
910 head: (settled && pull.headCommit) || pull.branch,
911 baseBranch,
912 };
913}
914
915
916export type QueueState = "waiting" | "testing" | "passed" | "failed" | "landed" | "removed";
917
918/** One pull request's place in a merge queue. */
919export type QueueEntry = {
920 id: string;
921 number: number;
922 title: string;
923 agent: string;
924 state: QueueState;
925 /** The pull requests merged ahead of it in the state being tested, in order. */
926 ahead: number[];
927 baseCommit: string | null;
928 combinedCommit: string | null;
929 error: string | null;
930 results: CheckResult[];
931 /** Username of whoever merged it into the queue: a person, or `g1t`. */
932 enqueuedBy: string;
933 /** RFC 3339. */
934 createdAt: string;
935 /** RFC 3339. */
936 finishedAt: string | null;
937};
938
939/** A repository's merge queue: what is in it, in order, and what recently left. */
940export type QueueView = {
941 enabled: boolean;
942 active: QueueEntry[];
943 /** Newest first. */
944 recent: QueueEntry[];
945};
946
947export type QueueStackItem = {
948 number: number;
949 title: string;
950 /** The repository holding the change, and its branch. */
951 source: RepoPath;
952 branch: string;
953 commit: string;
954};
955
956/** What a sandbox needs to build and check one combined state. */
957export type QueueJob = {
958 entryId: string;
959 token: string;
960 repo: RepoPath;
961 defaultBranch: string;
962 baseCommit: string;
963 branch: string;
964 stack: QueueStackItem[];
965 /** Always empty: the state is checked by the merge_group workflows run on it. */
966 checks: string[];
967 /** Always empty, as `checks`. */
968 contractChecks: string[];
969 actor: User;
970};
971
972// --- A person's work ---------------------------------------------------------
973// Mirrors the `Authored*` types in `crates/contracts/src/work.rs`.
974
975export type AuthoredKind = "issue" | "pull";
976/** `closed` takes in merged pull requests too; `merged` is only those. */
977export type AuthoredState = "open" | "closed" | "merged";
978/** `created` is newest first, `updated` most recently changed, `oldest` oldest first. */
979export type AuthoredSort = "created" | "updated" | "oldest";
980
981/** The most items one `byAuthor` page holds. */
982export const AUTHORED_PAGE = 25;
983
984export type AuthoredFilter = {
985 kind?: AuthoredKind;
986 state?: AuthoredState;
987 /** `namespace/name`. */
988 repo?: string;
989 sort?: AuthoredSort;
990 /** The `next` of the page before. */
991 before?: string;
992 limit?: number;
993};
994
995export type AuthoredItem = {
996 kind: AuthoredKind;
997 repo: RepoPath;
998 number: number;
999 title: string;
1000 /** A merged pull request is closed. */
1001 state: State;
1002 /** A pull request's own status; null on an issue. */
1003 status: PullStatus | null;
1004 /** Why an issue was closed. */
1005 reason: IssueReason | null;
1006 draft: boolean;
1007 merged: boolean;
1008 /** RFC 3339. */
1009 createdAt: string;
1010 /** RFC 3339. */
1011 updatedAt: string;
1012 /** RFC 3339. */
1013 mergedAt: string | null;
1014};
1015
1016/** Over every repository the viewer may read, whatever the filters. */
1017export type AuthoredCounts = {
1018 pullsMerged: number;
1019 pullsOpen: number;
1020 pulls: number;
1021 issues: number;
1022 issuesOpen: number;
1023};
1024
1025export type Authored = {
1026 items: AuthoredItem[];
1027 /** Pass as `before` for the next page; null on the last. */
1028 next: string | null;
1029 counts: AuthoredCounts;
1030 /** The repositories they worked in that the viewer may read, most work first. */
1031 repos: { repo: RepoPath; count: number }[];
1032};
1033
1034// Mirrors `Contributions` in `crates/contracts/src/work.rs`.
1035
1036/** How many days `contributions` covers: today and the 364 before it. */
1037export const CONTRIBUTION_DAYS = 365;
1038
1039/** One day with something on it; days with nothing are left out. */
1040export type ContributionDay = {
1041 /** `YYYY-MM-DD`, UTC. */
1042 date: string;
1043 /** Everything that day, commits included. */
1044 count: number;
1045 /** How many of `count` are commits they pushed to a default branch. */
1046 commits?: number;
1047};
1048
1049/** A person's year, as far as the viewer may see. */
1050export type Contributions = {
1051 /** Oldest first. */
1052 days: ContributionDay[];
1053 total: number;
1054 /** The first day counted, `YYYY-MM-DD`; the last is today (UTC). */
1055 from: string;
1056};
1057
1058// --- Coordinating work: sessions, claims and dependencies -----------------
1059// Mirrors `crates/contracts/src/work/coordination.rs`.
1060
1061/** The harness a session runs in. */
1062export type Harness = "claude_code" | "codex" | "cursor" | "copilot" | "other";
1063
1064/** Active: heard from in 15 minutes; idle: not since; ended: ended, or quiet for 2 hours. */
1065export type SessionState = "active" | "idle" | "ended";
1066
1067/** One run of someone's own harness on a repository. */
1068export type WorkSession = {
1069 /** `ses_…`. */
1070 id: string;
1071 repoId: string;
1072 issue: number | null;
1073 pull: number | null;
1074 branch: string | null;
1075 actor: { id: string; username: string };
1076 harness: Harness;
1077 harnessSessionId: string | null;
1078 title: string | null;
1079 state: SessionState;
1080 headCommit: string | null;
1081 startedAt: string;
1082 lastSeenAt: string;
1083 endedAt: string | null;
1084 /** What the harness said, or `timed_out`. */
1085 endReason: string | null;
1086};
1087
1088export type ClaimState = "active" | "released" | "expired" | "handed_off";
1089
1090/** An advisory, expiring "I'm working on #N". Never a lock. */
1091export type Claim = {
1092 /** `clm_…`. */
1093 id: string;
1094 repoId: string;
1095 issue: number;
1096 sessionId: string | null;
1097 actor: { id: string; username: string };
1098 harness: Harness | null;
1099 note: string | null;
1100 state: ClaimState;
1101 createdAt: string;
1102 expiresAt: string;
1103 endedAt: string | null;
1104 endReason: string | null;
1105 /** For a hand-off: who it was handed to; null for anyone. */
1106 handoffTo: string | null;
1107};
1108
1109/** `session.started` and `session.ended`. */
1110export type SessionEventData = { repoId: string; number?: number; issue?: number; session: WorkSession };
1111
1112/** `work.claimed`, `work.released`, `work.claim_expired` and `work.handoff_requested`. */
1113export type ClaimEventData = {
1114 repoId: string;
1115 number: number;
1116 issue: number;
1117 claim: Claim;
1118 others?: Claim[];
1119 tookOver?: string;
1120 reason?: string;
1121 from?: string;
1122 to?: string;
1123 note?: string;
1124};
1125
1126/** `dependency.changed`. */
1127export type DependencyChangedData = {
1128 repoId: string;
1129 number: number;
1130 issue: number;
1131 blockedBy: number[];
1132 added: number[];
1133 removed: number[];
1134 cleared: number[];
1135 remaining: number[];
1136 mergedPull?: number;
1137};
1138
1139/** `overlap.detected`, sent once for each side of a new overlap. */
1140export type OverlapDetectedData = {
1141 repoId: string;
1142 number: number;
1143 pull: number;
1144 otherPull: number;
1145 files: string[];
1146 issue?: number;
1147 otherIssue?: number;
1148};