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