Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

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