Skip to content

g1t/packages/contracts/src/work.ts

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