Skip to content
2,911 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1//! The work service: issues, pull requests, comments and sessions.
2//!
3//! Each `*Args` struct is the argument of the method of the same name,
4//! served at `POST /rpc/<method>`.
5//!
6//! Issues and pull requests share one sequence of numbers per repository,
7//! so `#12` names exactly one of them.
8
9use serde::{Deserialize, Serialize};
10
11use crate::repos::{CompareArgs, RepoPath};
12use crate::{User, Viewer};
13
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar14/// The labels a new repository starts with, and that "Add the default
15/// labels" adds to one that is missing some: `(name, color, description)`.
16/// Colors are six hex digits, without `#`.
17pub const DEFAULT_LABELS: [(&str, &str, &str); 11] = [
18 ("bug", "d73a4a", "Something isn't working"),
19 ("documentation", "0075ca", "Improvements or additions to documentation"),
20 ("duplicate", "cfd3d7", "This issue or pull request already exists"),
21 ("enhancement", "a2eeef", "New feature or request"),
22 ("good first issue", "7057ff", "Good for newcomers"),
23 ("help wanted", "008672", "Extra attention is needed"),
24 ("invalid", "e4e669", "This doesn't seem right"),
25 ("question", "d876e3", "Further information is requested"),
26 ("wontfix", "ffffff", "This will not be worked on"),
27 ("dependencies", "0366d6", "Updates a dependency"),
28 ("security", "ee0701", "A security fix or a vulnerability"),
29];
30
31/// The color a label gets when none is given: chosen from its name, so
32/// the same name always gets the same color.
33pub fn label_color_for(name: &str) -> String {
34 const PALETTE: [&str; 12] = [
35 "b60205", "d93f0b", "fbca04", "0e8a16", "006b75", "1d76db", "0052cc", "5319e7", "e99695", "f9d0c4",
36 "c2e0c6", "bfdadc",
37 ];
38 if let Some((_, color, _)) = DEFAULT_LABELS.iter().find(|(known, _, _)| *known == name) {
39 return (*color).to_owned();
40 }
41 let hash = name.bytes().fold(0u32, |hash, byte| hash.wrapping_mul(31).wrapping_add(u32::from(byte)));
42 PALETTE[(hash as usize) % PALETTE.len()].to_owned()
43}
44
45/// A color as six lowercase hex digits, from `#A1B2C3`, `a1b2c3` or `abc`.
46pub fn tidy_color(color: &str) -> Option<String> {
47 let hex = color.trim().trim_start_matches('#').to_ascii_lowercase();
48 if !hex.chars().all(|c| c.is_ascii_hexdigit()) {
49 return None;
50 }
51 match hex.len() {
52 6 => Some(hex),
53 3 => Some(hex.chars().flat_map(|c| [c, c]).collect()),
54 _ => None,
55 }
56}
57
58/// The most labels one issue or pull request can carry, and the longest
59/// name and description of one.
60pub const MAX_LABELS: usize = 20;
61pub const MAX_LABEL_CHARS: usize = 50;
62pub const MAX_LABEL_DESCRIPTION_CHARS: usize = 100;
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request63
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar64/// A label of a repository: a name, a color and what it means. Issues and
65/// pull requests carry labels by name. Names are lowercase and unique in
66/// a repository.
67#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
68#[serde(rename_all = "camelCase")]
69pub struct Label {
70 pub name: String,
71 /// Six hex digits, without `#`.
72 pub color: String,
73 #[serde(default)]
74 pub description: String,
75 /// How many issues carry it, open or closed.
76 #[serde(default)]
77 pub issues: u32,
78 /// How many pull requests carry it, in any state.
79 #[serde(default)]
80 pub pulls: u32,
81}
82
83/// A milestone, as an issue or pull request names it.
84#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
85#[serde(rename_all = "camelCase")]
86pub struct MilestoneRef {
87 pub number: u32,
88 pub title: String,
89}
90
91/// A milestone: a goal, with an optional due date, that issues and pull
92/// requests are gathered under. Its progress is how many of them are
93/// closed (a merged pull request counts as closed).
94#[derive(Clone, Debug, Serialize, Deserialize)]
95#[serde(rename_all = "camelCase")]
96pub struct Milestone {
97 /// Numbered from 1 in each repository, apart from issues.
98 pub number: u32,
99 pub title: String,
100 /// Markdown.
101 #[serde(default)]
102 pub description: String,
103 /// The day it is due, `YYYY-MM-DD`.
104 #[serde(default)]
105 pub due_on: Option<String>,
106 pub state: State,
107 /// Open issues and pull requests in it (drafts count as open).
108 #[serde(default)]
109 pub open_items: u32,
110 /// Closed issues, and merged or closed pull requests, in it.
111 #[serde(default)]
112 pub closed_items: u32,
113 /// RFC 3339.
114 pub created_at: String,
115 /// RFC 3339.
116 pub updated_at: String,
117 /// RFC 3339.
118 #[serde(default)]
119 pub closed_at: Option<String>,
120}
121
122impl Milestone {
123 /// How far along it is, from 0 to 100: closed items over all of them.
124 pub fn percent(&self) -> u32 {
125 let total = self.open_items + self.closed_items;
126 (self.closed_items * 100).checked_div(total).unwrap_or(0)
127 }
128}
129
130/// The most characters a milestone's title and description may have.
131pub const MAX_MILESTONE_TITLE_CHARS: usize = 100;
132pub const MAX_MILESTONE_DESCRIPTION_CHARS: usize = 4000;
133
134/// A due date as `YYYY-MM-DD`, from that or from an RFC 3339 time.
135pub fn tidy_due_on(value: &str) -> Option<String> {
136 let day = value.trim().get(..10)?;
137 let bytes = day.as_bytes();
138 let digits = |range: std::ops::Range<usize>| bytes[range].iter().all(u8::is_ascii_digit);
139 if !(digits(0..4) && bytes[4] == b'-' && digits(5..7) && bytes[7] == b'-' && digits(8..10)) {
140 return None;
141 }
142 let month: u32 = day[5..7].parse().ok()?;
143 let date: u32 = day[8..10].parse().ok()?;
144 ((1..=12).contains(&month) && (1..=31).contains(&date)).then(|| day.to_owned())
145}
146
147/// `list_labels`: a repository's labels, by name, each with how many
148/// issues and pull requests carry it. Takes `ViewArgs`; returns
149/// `Outcome<Vec<Label>>`.
150///
151/// `save_label`: creates a label, or with `name` changes one (renaming it
152/// renames it on every issue and pull request). Needs the Triage role.
153/// Returns `Outcome<Label>`.
154#[derive(Debug, Serialize, Deserialize)]
155#[serde(rename_all = "camelCase")]
156pub struct SaveLabelArgs {
157 pub actor: User,
158 pub repo: RepoPath,
159 /// The label to change; absent to create one.
160 #[serde(default)]
161 pub name: Option<String>,
162 /// The name it should have: required to create one.
163 #[serde(default)]
164 pub new_name: Option<String>,
165 /// Six hex digits; one is chosen from the name when creating without.
166 #[serde(default)]
167 pub color: Option<String>,
168 #[serde(default)]
169 pub description: Option<String>,
170}
171
172/// `delete_label`: removes a label from the repository and from every
173/// issue and pull request that carries it. Needs the Triage role. Returns
174/// `Outcome<bool>`.
175#[derive(Debug, Serialize, Deserialize)]
176pub struct DeleteLabelArgs {
177 pub actor: User,
178 pub repo: RepoPath,
179 pub name: String,
180}
181
182/// `add_default_labels`: adds those of [`DEFAULT_LABELS`] the repository
183/// does not have yet. Needs the Triage role. Returns
184/// `Outcome<Vec<Label>>`, every label it has now.
185#[derive(Debug, Serialize, Deserialize)]
186pub struct RepoActorArgs {
187 pub actor: User,
188 pub repo: RepoPath,
189}
190
191/// How `set_labels` changes an issue's or pull request's labels.
192#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
193#[serde(rename_all = "lowercase")]
194pub enum LabelChange {
195 /// Replace them all.
196 #[default]
197 Set,
198 /// Add these to the ones it has.
199 Add,
200 /// Take these off.
201 Remove,
202}
203
204/// `set_labels`: changes the labels of an issue or a pull request (they
205/// share numbers). A label the repository does not have yet is created
206/// when the actor has the Triage role; anyone else may only use existing
207/// ones, on what they opened. Returns `Outcome<Vec<String>>`, its labels
208/// now.
209#[derive(Debug, Serialize, Deserialize)]
210#[serde(rename_all = "camelCase")]
211pub struct SetLabelsArgs {
212 pub actor: User,
213 pub repo: RepoPath,
214 pub number: u32,
215 pub labels: Vec<String>,
216 #[serde(default)]
217 pub change: LabelChange,
218}
219
220/// `list_milestones`: a repository's milestones, open ones by due date
221/// (soonest first, those without one last), then closed ones most recently
222/// closed first. Returns `Outcome<Vec<Milestone>>`.
223#[derive(Debug, Serialize, Deserialize)]
224pub struct ListMilestonesArgs {
225 pub repo: RepoPath,
226 pub viewer: Viewer,
227 /// Both when absent.
228 #[serde(default)]
229 pub state: Option<State>,
230}
231
232/// `get_milestone`: one milestone and what is in it. Takes `ViewArgs`,
233/// whose `number` is the milestone's. Returns `Outcome<MilestoneDetail>`.
234#[derive(Clone, Debug, Serialize, Deserialize)]
235pub struct MilestoneDetail {
236 pub milestone: Milestone,
237 /// Newest first, open and closed.
238 pub issues: Vec<Issue>,
239 /// Newest first, in any state.
240 pub pulls: Vec<Pull>,
241}
242
243/// `save_milestone`: creates a milestone, or with `number` changes the
244/// fields given. Needs the Triage role. Returns `Outcome<Milestone>`.
245#[derive(Debug, Serialize, Deserialize)]
246#[serde(rename_all = "camelCase")]
247pub struct SaveMilestoneArgs {
248 pub actor: User,
249 pub repo: RepoPath,
250 /// The milestone to change; absent to create one.
251 #[serde(default)]
252 pub number: Option<u32>,
253 /// Required to create one.
254 #[serde(default)]
255 pub title: Option<String>,
256 #[serde(default)]
257 pub description: Option<String>,
258 /// `YYYY-MM-DD`; an empty string clears it.
259 #[serde(default)]
260 pub due_on: Option<String>,
261 #[serde(default)]
262 pub state: Option<State>,
263}
264
265/// `delete_milestone`: removes a milestone; what was in it is in none.
266/// Needs the Triage role. Returns `Outcome<bool>`.
267#[derive(Debug, Serialize, Deserialize)]
268pub struct DeleteMilestoneArgs {
269 pub actor: User,
270 pub repo: RepoPath,
271 pub number: u32,
272}
273
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request274/// `open` or `closed`: the filter on lists of issues and pull requests. An
275/// open pull request is a draft or one ready for review; a closed one was
276/// merged or closed without merging.
277#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
278#[serde(rename_all = "lowercase")]
279pub enum State {
280 Open,
281 Closed,
282}
283
284/// Why an issue was closed.
285#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
286#[serde(rename_all = "snake_case")]
287pub enum IssueReason {
288 /// The work was done. If a pull request did it, `resolved_by` names it.
289 Completed,
290 NotPlanned,
291}
292
293impl IssueReason {
294 pub fn as_str(self) -> &'static str {
295 match self {
296 IssueReason::Completed => "completed",
297 IssueReason::NotPlanned => "not_planned",
298 }
299 }
300}
301
302/// Something that should change in a repository: a bug, a feature, a
303/// question. Opened by a person, an agent or an integration. Pull requests
304/// are made against it; the one that is merged resolves it.
305#[derive(Clone, Debug, Serialize, Deserialize)]
306#[serde(rename_all = "camelCase")]
307pub struct Issue {
308 pub id: String,
309 pub repo_id: String,
310 /// Shown as `#12`.
311 pub number: u32,
312 pub title: String,
313 /// Markdown. Also what an agent is given to work from.
314 pub body: String,
315 pub labels: Vec<String>,
316 pub state: State,
317 /// Set when closed.
318 pub reason: Option<IssueReason>,
319 /// The number of the pull request whose merge closed this issue.
320 pub resolved_by: Option<u32>,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights321 /// Who opened it: a person, an integration, or g1t (`kind` `agent`)
322 /// for one its agent filed while at work.
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request323 pub author: User,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights324 /// For an issue g1t's agent filed: the person it was working for. They
325 /// may manage it as its author could. See [`owner`](Issue::owner).
326 #[serde(default)]
327 pub requested_by: Option<User>,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request328 /// RFC 3339.
329 pub created_at: String,
330 /// RFC 3339.
331 pub updated_at: String,
332 /// RFC 3339.
333 pub closed_at: Option<String>,
334 /// Pull requests made against this issue, in any state.
335 pub pull_count: u32,
336 pub comment_count: u32,
337 /// Usernames of the people it is assigned to.
338 #[serde(default)]
339 pub assignees: Vec<String>,
340 /// The numbers of the issues that have to be merged before this one is
341 /// worked on.
342 #[serde(default)]
343 pub blocked_by: Vec<u32>,
344 /// Whether a g1t agent takes it as soon as it can: at once, or when
345 /// what it is blocked by has merged.
346 #[serde(default)]
347 pub queued: bool,
348 /// The agent working on it now: the one behind its newest pull request
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent349 /// that is still in progress in a fork, such as `g1t`.
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request350 #[serde(default)]
351 pub agent: Option<String>,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar352 /// The milestone it is in, if any.
353 #[serde(default)]
354 pub milestone: Option<MilestoneRef>,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request355}
356
357#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
358#[serde(rename_all = "lowercase")]
359pub enum PullStatus {
360 /// Still being worked on.
361 Draft,
362 /// Ready for review.
363 Open,
364 Merged,
365 /// Closed without merging.
366 Closed,
367}
368
369impl PullStatus {
370 pub fn as_str(self) -> &'static str {
371 match self {
372 PullStatus::Draft => "draft",
373 PullStatus::Open => "open",
374 PullStatus::Merged => "merged",
375 PullStatus::Closed => "closed",
376 }
377 }
378
379 /// Whether the pull request can still be changed or merged.
380 pub fn is_active(self) -> bool {
381 matches!(self, PullStatus::Draft | PullStatus::Open)
382 }
383}
384
385/// Where the agent runs: on g1t's sandboxes, or in someone's own session.
386#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
387#[serde(rename_all = "lowercase")]
388pub enum Runtime {
389 Hosted,
390 External,
391}
392
393/// A proposed change. It is made either in a fork created for it, which is
394/// how agents work, or on a branch pushed to the repository itself.
395#[derive(Clone, Debug, Serialize, Deserialize)]
396#[serde(rename_all = "camelCase")]
397pub struct Pull {
398 pub id: String,
399 pub repo_id: String,
400 /// Shown as `#12`.
401 pub number: u32,
402 /// The number of the issue this is for, if any.
403 pub issue: Option<u32>,
404 pub title: String,
405 /// Markdown: what changed and why. Set when marked ready.
406 pub body: Option<String>,
407 /// A label for the agent doing the work, e.g. `claude-code`.
408 pub agent: String,
409 pub runtime: Runtime,
410 pub status: PullStatus,
411 /// The fork holding the change, unless it is on a branch.
412 pub fork: Option<RepoPath>,
413 /// The fork's repository id.
414 pub fork_repo_id: Option<String>,
415 /// The branch of the repository holding the change, unless it is in a
416 /// fork.
417 pub branch: Option<String>,
418 pub head_commit: Option<String>,
419 /// For a merged pull request, what the branch pointed to before the
420 /// merge. Comparing against it shows what the pull request changed.
421 pub merge_base: Option<String>,
422 /// Username of whoever merged it.
423 pub merged_by: Option<String>,
424 /// RFC 3339.
425 pub merged_at: Option<String>,
426 /// Set on a pull request closed because another one for the same issue
427 /// was merged: that one's number.
428 pub superseded_by: Option<u32>,
Fast pages, required checks on the branch, self-hosted runners, honest incidents429 /// `failed` when the merge queue took it out because its combined
430 /// state failed, until its head moves. Its checks are the statuses
431 /// workflows report on its head: see `PullDetail::statuses` and
432 /// `PullDetail::required_checks`.
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request433 pub check_status: Option<CheckStatus>,
434 /// The files it changes, as of its latest push.
435 #[serde(default)]
436 pub files: Vec<ChangedFile>,
437 /// Usernames of the people it is assigned to.
438 #[serde(default)]
439 pub assignees: Vec<String>,
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent440 /// Those whose review was asked for: usernames, and `g1t` when a
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request441 /// g1t agent was asked.
442 #[serde(default)]
443 pub reviewers: Vec<String>,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar444 /// Teams whose review was asked for, as `workspace/slug`. A team stays
445 /// here after review assignment picks people from it, who are listed
446 /// in `reviewers`.
447 #[serde(default)]
448 pub team_reviewers: Vec<String>,
449 /// The labels it carries, by name.
450 #[serde(default)]
451 pub labels: Vec<String>,
452 /// The milestone it is in, if any.
453 #[serde(default)]
454 pub milestone: Option<MilestoneRef>,
455 /// The branch it merges into. Stored as null for the repository's
456 /// default branch, so that it follows a change of default; lists and
457 /// `get_pull` fill in the name. See [`base_branch`](Pull::base_branch).
458 #[serde(default)]
459 pub base: Option<String>,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights460 /// Who opened it: a person, or g1t (`kind` `agent`, username `g1t`)
461 /// for a change g1t made.
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request462 pub author: User,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights463 /// For a change g1t made: the person who asked for it, by assigning
464 /// an issue or handing g1t the work. They answer for it as its author
465 /// would. See [`owner`](Pull::owner).
466 #[serde(default)]
467 pub requested_by: Option<User>,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request468 /// RFC 3339.
469 pub created_at: String,
470 /// RFC 3339.
471 pub updated_at: String,
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step472 /// How sure g1t is of a g1t agent's change, from what it can observe,
473 /// once the agent has finished it. Absent before then, and on changes
474 /// g1t is not seeing through.
475 #[serde(default)]
476 pub confidence: Option<Confidence>,
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge477 /// Who last moved its head (a user id), and when, RFC 3339: for rules
478 /// about the most recent push. Absent until a push after rulesets
479 /// arrived.
480 #[serde(default, skip_serializing_if = "Option::is_none")]
481 pub head_pushed_by: Option<String>,
482 #[serde(default, skip_serializing_if = "Option::is_none")]
483 pub head_pushed_at: Option<String>,
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step484}
485
486/// How sure g1t is that an agent's change is right. Low is below medium,
487/// which is below high, so the lower of two is their minimum.
488#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
489#[serde(rename_all = "lowercase")]
490pub enum ConfidenceLevel {
491 Low,
492 Medium,
493 High,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request494}
495
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step496impl ConfidenceLevel {
497 pub fn as_str(self) -> &'static str {
498 match self {
499 ConfidenceLevel::Low => "low",
500 ConfidenceLevel::Medium => "medium",
501 ConfidenceLevel::High => "high",
502 }
503 }
504
505 pub fn parse(value: &str) -> Option<ConfidenceLevel> {
506 match value.trim().to_ascii_lowercase().as_str() {
507 "low" => Some(ConfidenceLevel::Low),
508 "medium" => Some(ConfidenceLevel::Medium),
509 "high" => Some(ConfidenceLevel::High),
510 _ => None,
511 }
512 }
513}
514
515/// How sure g1t is of a change an agent made, worked out from what can be
516/// observed: its checks, how often it was sent back, the reviewer agent's
517/// verdict, whether it touched tests, its size, where it reached, how close
518/// it came to its guardrails, and what it asked and was not answered. The
519/// agent may say how sure it is too; what g1t observes can only lower that.
520#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
521#[serde(rename_all = "camelCase")]
522pub struct Confidence {
523 pub level: ConfidenceLevel,
524 /// A few words each, most telling first: what lowered it, or for
525 /// `high`, what it rests on.
526 pub reasons: Vec<String>,
527 /// What the agent said of its own change, if it said.
528 #[serde(default)]
529 pub self_reported: Option<ConfidenceLevel>,
530 /// What the agent said it was unsure about.
531 #[serde(default)]
532 pub uncertain_about: Vec<String>,
533 /// The agent run it was worked out after.
534 #[serde(default)]
535 pub run_id: Option<String>,
536 /// RFC 3339.
537 pub assessed_at: String,
538}
539
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request540/// One file a pull request changes, and by how much.
541#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
542pub struct ChangedFile {
543 pub path: String,
544 pub additions: u32,
545 pub deletions: u32,
546}
547
548/// Another pull request in progress that changes some of the same files.
549/// Two for the same issue are alternatives; two for different issues are
550/// heading for a conflict.
551#[derive(Clone, Debug, Serialize, Deserialize)]
552pub struct Overlap {
553 pub number: u32,
554 pub title: String,
555 /// The number of the issue the other pull request is for.
556 pub issue: Option<u32>,
557 /// The files both change.
558 pub paths: Vec<String>,
559}
560
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights561impl Issue {
562 /// Who the issue is theirs to manage: the person g1t's agent filed it
563 /// for, or its author. They may edit, close and reopen it without the
564 /// Triage role.
565 pub fn owner(&self) -> &User {
566 self.requested_by.as_ref().unwrap_or(&self.author)
567 }
568}
569
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request570impl Pull {
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights571 /// Who the pull request is theirs to answer for: whoever asked g1t to
572 /// make it, or its author. Every rule that once read "its author" reads
573 /// this: they may update, close and steer it, are never asked to review
574 /// it and cannot approve it, see it as theirs, and a sandbox at work on
575 /// it acts as them.
576 pub fn owner(&self) -> &User {
577 self.requested_by.as_ref().unwrap_or(&self.author)
578 }
579
580 /// Whether `id` is its [`owner`](Pull::owner)'s.
581 pub fn is_owned_by(&self, id: &str) -> bool {
582 self.owner().id == id
583 }
584
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar585 /// The branch it merges into: its own base, or the repository's
586 /// default branch when it has none.
587 pub fn base_branch<'a>(&'a self, default_branch: &'a str) -> &'a str {
588 self.base.as_deref().filter(|base| !base.is_empty()).unwrap_or(default_branch)
589 }
590
591 /// Whether it merges into the repository's default branch.
592 pub fn targets_default(&self, default_branch: &str) -> bool {
593 self.base_branch(default_branch) == default_branch
594 }
595
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request596 /// What to ask the repos service to see what this pull request changes.
597 ///
598 /// A fork is compared as a whole. A branch is compared by name while
599 /// the pull request is open, and by the commit it was merged or closed
600 /// at afterwards, so later pushes to the branch do not change the record.
601 pub fn comparison(&self, viewer: &Viewer) -> CompareArgs {
602 let settled = matches!(self.status, PullStatus::Merged | PullStatus::Closed);
603 let (repo_id, head) = match &self.fork_repo_id {
604 Some(fork) => (fork.clone(), None),
605 None => (
606 self.repo_id.clone(),
607 self.head_commit
608 .clone()
609 .filter(|_| settled)
610 .or_else(|| self.branch.clone()),
611 ),
612 };
613 CompareArgs {
614 repo_id,
615 viewer: viewer.clone(),
616 base: self.merge_base.clone(),
617 head,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar618 base_branch: self.base.clone().filter(|base| !base.is_empty()),
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request619 }
620 }
621}
622
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights623/// g1t's agent, as the author of what it opens: stored by
624/// [`AGENT_ID`](crate::identity::AGENT_ID), shown as `g1t`.
625pub fn g1t_author() -> User {
626 User {
627 id: crate::identity::AGENT_ID.to_owned(),
628 username: crate::identity::AGENT_NAME.to_owned(),
629 kind: crate::PrincipalKind::Agent,
630 ..User::default()
631 }
632}
633
634/// Who is recorded as opening an issue or a pull request that `actor`
635/// opens, and who asked for it: `(author, requested_by)`.
636///
637/// - g1t's agent, acting for someone through its token (an issue it files
638/// while at work): g1t, requested by that person.
639/// - A person having g1t make the change (`by_g1t`: a hosted g1t agent in
640/// a fork of its own): g1t, requested by them.
641/// - Anyone else, and g1t's own work that nobody asked for: the actor, and
642/// nobody asking.
643pub fn authorship(actor: &User, by_g1t: bool) -> (User, Option<User>) {
644 let person = |id: &str, username: &str| User {
645 id: id.to_owned(),
646 username: username.to_owned(),
647 kind: crate::PrincipalKind::User,
648 ..User::default()
649 };
650 if actor.kind == crate::PrincipalKind::Agent {
651 let asked = actor
652 .acting
653 .as_ref()
654 .map(|acting| &acting.on_behalf_of)
655 .filter(|on_behalf_of| !crate::system::is_system_id(&on_behalf_of.id))
656 .map(|on_behalf_of| person(&on_behalf_of.id, &on_behalf_of.username));
657 return (g1t_author(), asked);
658 }
659 if by_g1t && actor.kind != crate::PrincipalKind::System {
660 return (g1t_author(), Some(person(&actor.id, &actor.username)));
661 }
662 let author = User {
663 id: actor.id.clone(),
664 username: actor.username.clone(),
665 kind: actor.kind,
666 ..User::default()
667 };
668 (author, None)
669}
670
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request671#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
672#[serde(rename_all = "lowercase")]
673pub enum CheckStatus {
674 /// Waiting for a sandbox.
675 Queued,
676 Running,
677 Passed,
678 Failed,
679 /// The checks could not be run at all.
680 Errored,
681}
682
683impl CheckStatus {
684 pub fn as_str(self) -> &'static str {
685 match self {
686 CheckStatus::Queued => "queued",
687 CheckStatus::Running => "running",
688 CheckStatus::Passed => "passed",
689 CheckStatus::Failed => "failed",
690 CheckStatus::Errored => "errored",
691 }
692 }
693}
694
Fast pages, required checks on the branch, self-hosted runners, honest incidents695/// How one command went. Recorded by earlier runs of commands written on
696/// issues, which g1t no longer runs; kept so their history still reads.
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request697#[derive(Clone, Debug, Serialize, Deserialize)]
698#[serde(rename_all = "camelCase")]
699pub struct CheckResult {
700 pub command: String,
701 pub passed: bool,
702 /// Absent when the command was stopped for taking too long.
703 #[serde(default)]
704 pub exit_code: Option<i32>,
705 /// What the command printed, standard output and error together. The
706 /// end of it, when there was a lot.
707 #[serde(default)]
708 pub output: String,
709 #[serde(default)]
710 pub duration_ms: u64,
711}
712
Fast pages, required checks on the branch, self-hosted runners, honest incidents713/// A record against a pull request's head: the merge queue taking it out,
714/// with why, or an earlier run of commands written on its issue.
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request715#[derive(Clone, Debug, Serialize, Deserialize)]
716#[serde(rename_all = "camelCase")]
717pub struct CheckRun {
718 pub id: String,
719 /// The commit that was checked.
720 pub head_commit: String,
721 pub status: CheckStatus,
722 pub results: Vec<CheckResult>,
723 /// Why the checks could not be run, when `status` is `errored`.
724 pub error: Option<String>,
725 /// RFC 3339.
726 pub created_at: String,
727 /// RFC 3339.
728 pub finished_at: Option<String>,
729}
730
731/// A reviewer's decision on a pull request.
732#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
733#[serde(rename_all = "snake_case")]
734pub enum Verdict {
735 Approve,
736 RequestChanges,
737}
738
739impl Verdict {
740 pub fn as_str(self) -> &'static str {
741 match self {
742 Verdict::Approve => "approve",
743 Verdict::RequestChanges => "request_changes",
744 }
745 }
746}
747
748/// What an entry in a conversation is.
749#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
750#[serde(rename_all = "lowercase")]
751pub enum CommentKind {
752 /// Something a person or an agent wrote.
753 #[default]
754 Comment,
755 /// Something that happened: an assignment, a review asked for, a close.
756 Event,
757}
758
759/// A comment on an issue or a pull request. On a pull request it can sit
760/// on one line of the change, and it can carry a reviewer's verdict.
761#[derive(Clone, Debug, Serialize, Deserialize)]
762#[serde(rename_all = "camelCase")]
763pub struct Comment {
764 pub id: String,
765 /// Something a person wrote, or something that happened.
766 #[serde(default)]
767 pub kind: CommentKind,
768 pub author: User,
769 /// Markdown. For an event, what its author did, as the rest of a
770 /// sentence that starts with their name: "assigned ana".
771 pub body: String,
772 /// The file commented on, for a comment on a line.
773 pub path: Option<String>,
774 /// The line of that file, as numbered after the change.
775 pub line: Option<u32>,
776 pub verdict: Option<Verdict>,
777 /// RFC 3339.
778 pub created_at: String,
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts779 /// When its text was last edited, RFC 3339; absent if it never was.
780 #[serde(default, skip_serializing_if = "Option::is_none")]
781 pub edited_at: Option<String>,
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar782 /// Set when one of the workspace's agents wrote it, as itself
783 /// (`author` is then the agent: its id, its handle as `username`,
784 /// kind `agent`). Absent for people and for g1t.
785 #[serde(default, skip_serializing_if = "Option::is_none")]
786 pub agent: Option<AgentRef>,
787 /// Who the agent acted for: the person whose access it had, who
788 /// answers for it as its author would. Set with `agent`.
789 #[serde(default, skip_serializing_if = "Option::is_none")]
790 pub acting_for: Option<User>,
791 /// An agent's review: shown with its verdict (or none, for a review
792 /// that only comments) but advisory. It never counts toward required
793 /// approvals or code owners, and never blocks a merge.
794 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
795 pub advisory: bool,
796}
797
798impl Comment {
799 /// The person who answers for it: whoever an agent acted for, or its author.
800 pub fn answerable(&self) -> &User {
801 self.acting_for.as_ref().unwrap_or(&self.author)
802 }
803}
804
805/// One of a workspace's own agents, as a comment or review it wrote shows
806/// it: Margo (@margo), with the pixel face drawn from `avatar_seed`. Its
807/// page is `/<workspace>/-/agents/<handle>`. Read in camelCase, like the
808/// comment it sits on; the snake_case spelling is accepted too.
809#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
810#[serde(rename_all = "camelCase")]
811pub struct AgentRef {
812 pub id: String,
813 pub handle: String,
814 #[serde(alias = "display_name")]
815 pub display_name: String,
816 #[serde(alias = "avatar_seed")]
817 pub avatar_seed: String,
818}
819
820/// An agent's review: a comment, an approval or a request for changes,
821/// all of them advisory.
822#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
823#[serde(rename_all = "snake_case")]
824pub enum AgentVerdict {
825 Comment,
826 Approve,
827 RequestChanges,
828}
829
830impl AgentVerdict {
831 pub fn as_str(self) -> &'static str {
832 match self {
833 AgentVerdict::Comment => "comment",
834 AgentVerdict::Approve => "approve",
835 AgentVerdict::RequestChanges => "request_changes",
836 }
837 }
838
839 pub fn parse(text: &str) -> Option<Self> {
840 match text {
841 "comment" => Some(AgentVerdict::Comment),
842 "approve" => Some(AgentVerdict::Approve),
843 "request_changes" => Some(AgentVerdict::RequestChanges),
844 _ => None,
845 }
846 }
847
848 /// As a person's verdict reads, for showing it; none for a comment.
849 pub fn verdict(self) -> Option<Verdict> {
850 match self {
851 AgentVerdict::Comment => None,
852 AgentVerdict::Approve => Some(Verdict::Approve),
853 AgentVerdict::RequestChanges => Some(Verdict::RequestChanges),
854 }
855 }
856}
857
858/// How many comments and reviews one agent may write on one issue or pull
859/// request in [`AGENT_COMMENT_WINDOW_MS`].
860pub const AGENT_COMMENTS_PER_WINDOW: u32 = 5;
861/// An hour.
862pub const AGENT_COMMENT_WINDOW_MS: u64 = 60 * 60 * 1000;
863
864/// `workspace_agent_comment`: one of a workspace's agents comments on an
865/// issue or a pull request as itself, on behalf of `acting_for`. For the
866/// agents service only; never reachable with a person's token. The same
867/// checks as a person commenting apply to `acting_for` (verified, can
868/// read the repository, not archived), and an agent writes at most
869/// [`AGENT_COMMENTS_PER_WINDOW`] comments and reviews on one issue or
870/// pull request an hour. Publishes `comment.created` with `agent`.
871/// Returns `Outcome<Comment>`.
872#[derive(Debug, Serialize, Deserialize)]
873pub struct WorkspaceAgentCommentArgs {
874 pub repo: RepoPath,
875 pub number: u32,
876 pub agent: AgentRef,
877 #[serde(alias = "actingFor")]
878 pub acting_for: User,
879 pub body: String,
880}
881
882/// `workspace_agent_review`: one of a workspace's agents reviews a pull
883/// request as itself, on behalf of `acting_for`. Advisory: the verdict is
884/// shown but never counts toward required approvals or code owners, and a
885/// request for changes never blocks a merge. Not on a draft, nor on a
886/// closed or merged pull request. `body` may be empty only when approving.
887/// The same checks and limit as `workspace_agent_comment`. Publishes
888/// `comment.created` with `agent`, `advisory` and the verdict.
889/// Returns `Outcome<Comment>`.
890#[derive(Debug, Serialize, Deserialize)]
891pub struct WorkspaceAgentReviewArgs {
892 pub repo: RepoPath,
893 pub number: u32,
894 pub agent: AgentRef,
895 #[serde(alias = "actingFor")]
896 pub acting_for: User,
897 pub verdict: AgentVerdict,
898 #[serde(default)]
899 pub body: String,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request900}
901
902#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
903#[serde(rename_all = "snake_case")]
904pub enum SessionEntryKind {
905 Prompt,
906 Message,
907 ToolCall,
908 ToolResult,
909 Note,
910}
911
912/// One step of an agent's session: the "why" behind a pull request's commits.
913#[derive(Clone, Debug, Serialize, Deserialize)]
914pub struct SessionEntry {
915 pub seq: u32,
916 pub kind: SessionEntryKind,
917 pub text: String,
918 /// For tool calls and results.
919 pub tool: Option<String>,
920 /// The fork's head commit when this entry was recorded, if known.
921 pub commit: Option<String>,
922 /// RFC 3339.
923 pub at: String,
924}
925
926#[derive(Clone, Debug, Serialize, Deserialize)]
927pub struct NewSessionEntry {
928 pub kind: SessionEntryKind,
929 pub text: String,
930 #[serde(default)]
931 pub tool: Option<String>,
932 #[serde(default)]
933 pub commit: Option<String>,
934}
935
936#[derive(Clone, Debug, Serialize, Deserialize)]
937pub struct IssueDetail {
938 pub issue: Issue,
939 /// Every pull request made against it, oldest first.
940 pub pulls: Vec<Pull>,
941 pub comments: Vec<Comment>,
942}
943
944#[derive(Clone, Debug, Serialize, Deserialize)]
945pub struct PullDetail {
946 pub pull: Pull,
947 /// The issue it is for, if any.
948 pub issue: Option<Issue>,
949 pub comments: Vec<Comment>,
Fast pages, required checks on the branch, self-hosted runners, honest incidents950 /// The latest record against its head: the merge queue taking it out,
951 /// or (from before checks were workflows) a run of its issue's commands.
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request952 pub checks: Option<CheckRun>,
953 /// Other pull requests in progress that change the same files.
954 #[serde(default)]
955 pub overlaps: Vec<Overlap>,
956 /// Whether the branch it would merge into has moved on without it, so
957 /// that it has to catch up before it can merge.
958 #[serde(default)]
959 pub behind: bool,
960 /// Whether a g1t agent is reviewing it right now.
961 #[serde(default)]
962 pub review_pending: bool,
963 /// Where it stands on its way to being merged, for a pull request g1t
964 /// is seeing through. Absent on anyone else's.
965 #[serde(default)]
966 pub lifecycle: Option<Lifecycle>,
967 /// A merge was asked for while it was behind: g1t is bringing it up to
968 /// date and will then land it.
969 #[serde(default)]
970 pub landing: bool,
971 /// Why g1t stopped working on it, if it did: a catch-up that could not
972 /// be completed, for example.
973 #[serde(default)]
974 pub stalled: Option<String>,
975 /// Messages people sent the agent while it worked, oldest first.
976 #[serde(default)]
977 pub messages: Vec<AgentMessage>,
GitHub Actions on g1t, part two: running workflows978 /// What workflow runs said about its head commit, one per workflow.
979 #[serde(default)]
980 pub statuses: Vec<CommitStatus>,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains981 /// Whether it merges cleanly into the branch it targets, worked out
982 /// ahead of time whenever either side moves.
983 #[serde(default)]
984 pub mergeable: Mergeable,
985 /// When `mergeable` is `conflicting`: the files that conflict.
986 #[serde(default)]
987 pub conflicts: Vec<String>,
Fast pages, required checks on the branch, self-hosted runners, honest incidents988 /// Earlier records like `checks`, newest first, without their output.
Agents and memory, checks and conflicts, profiles, slug renames, custom domains989 #[serde(default)]
990 pub earlier_checks: Vec<CheckRun>,
Fast pages, required checks on the branch, self-hosted runners, honest incidents991 /// The checks the default branch's protection requires, each as it
992 /// stands on the head commit. Empty when none are required.
993 #[serde(default, alias = "requiredChecks")]
994 pub required_checks: Vec<RequiredCheck>,
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge995 /// The rules of the branch it merges into that it does not meet yet,
996 /// for whoever is looking. Absent while it is not open.
997 #[serde(default, skip_serializing_if = "Option::is_none")]
998 pub rules: Option<crate::rules::MergeRules>,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar999 /// Who owns the files it changes, from the CODEOWNERS file of the
1000 /// branch it merges into, and whose approval is still needed. Absent
1001 /// when that branch has no CODEOWNERS file.
1002 #[serde(default, alias = "codeOwners")]
1003 pub code_owners: Option<crate::codeowners::PullCodeOwners>,
Fast pages, required checks on the branch, self-hosted runners, honest incidents1004}
1005
1006/// Where a required check stands on a commit.
1007#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1008#[serde(rename_all = "lowercase")]
1009pub enum RequiredState {
1010 Success,
1011 Failure,
1012 /// Reported and still running.
1013 Pending,
1014 /// Nothing has reported it on this commit yet.
1015 Expected,
1016}
1017
1018impl RequiredState {
1019 pub fn as_str(self) -> &'static str {
1020 match self {
1021 RequiredState::Success => "success",
1022 RequiredState::Failure => "failure",
1023 RequiredState::Pending => "pending",
1024 RequiredState::Expected => "expected",
1025 }
1026 }
1027}
1028
1029/// One check a branch's protection requires, as it stands on a commit.
1030#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1031#[serde(rename_all = "camelCase")]
1032pub struct RequiredCheck {
1033 /// The check's name, such as `CI` or `g1t / deploy`.
1034 pub name: String,
1035 pub state: RequiredState,
1036 /// What the status that decided it says, if one did.
1037 #[serde(default)]
1038 pub description: Option<String>,
1039 /// Where to see more: the workflow run, for one a workflow reported.
1040 #[serde(default)]
1041 pub target_url: Option<String>,
1042}
1043
1044/// The events a workflow status's context can end in: `CI / pull_request`
1045/// is the `CI` check, reported by a run for a `pull_request` event.
1046const STATUS_EVENTS: &[&str] = &[
1047 "push",
1048 "pull_request",
1049 "pull_request_target",
1050 "pull_request_review",
1051 "merge_group",
1052 "workflow_dispatch",
1053 "workflow_run",
1054 "workflow_call",
1055 "schedule",
1056 "release",
1057 "issues",
1058 "issue_comment",
1059 "repository_dispatch",
1060];
1061
1062/// A status context's check name and the event it was reported for:
1063/// `CI / pull_request` is `("CI", Some("pull_request"))`. A context that
1064/// does not end in an event, such as `g1t / deploy`, is its own name.
1065pub fn check_name(context: &str) -> (&str, Option<&str>) {
1066 match context.rsplit_once(" / ") {
1067 Some((name, event)) if STATUS_EVENTS.contains(&event) && !name.trim().is_empty() => (name, Some(event)),
1068 _ => (context, None),
1069 }
1070}
1071
1072/// Where each required check stands among a commit's statuses. A check is
1073/// met by any status of that name, for any event: one that failed fails
1074/// it, one still running holds it, and with neither, one that passed
1075/// passes it. Names compare without regard to case.
1076pub fn required_checks(required: &[String], statuses: &[CommitStatus]) -> Vec<RequiredCheck> {
1077 required
1078 .iter()
1079 .map(|name| {
1080 let matching: Vec<&CommitStatus> = statuses
1081 .iter()
1082 .filter(|status| check_name(&status.context).0.eq_ignore_ascii_case(name.trim()))
1083 .collect();
1084 let failed = matching.iter().find(|s| s.state == "failure" || s.state == "error");
1085 let pending = matching.iter().find(|s| s.state == "pending");
1086 let passed = matching.iter().find(|s| s.state == "success");
1087 let (state, decided) = match (failed, pending, passed) {
1088 (Some(status), _, _) => (RequiredState::Failure, Some(*status)),
1089 (None, Some(status), _) => (RequiredState::Pending, Some(*status)),
1090 (None, None, Some(status)) => (RequiredState::Success, Some(*status)),
1091 _ => (RequiredState::Expected, None),
1092 };
1093 RequiredCheck {
1094 name: name.trim().to_owned(),
1095 state,
1096 description: decided.and_then(|status| status.description.clone()),
1097 target_url: decided.and_then(|status| status.target_url.clone()),
1098 }
1099 })
1100 .collect()
1101}
1102
1103/// A repository's required check names, tidied: trimmed, without blanks
1104/// or repeats (ignoring case), at most [`MAX_REQUIRED_CHECKS`].
1105pub fn tidy_required(names: &[String]) -> Vec<String> {
1106 let mut out: Vec<String> = Vec::new();
1107 for name in names {
1108 let name: String = name.trim().chars().take(MAX_CHECK_NAME_CHARS).collect();
1109 if !name.is_empty() && !out.iter().any(|kept| kept.eq_ignore_ascii_case(&name)) {
1110 out.push(name);
1111 }
1112 }
1113 out.truncate(MAX_REQUIRED_CHECKS);
1114 out
1115}
1116
1117/// The most checks a branch can require, and the longest name of one.
1118pub const MAX_REQUIRED_CHECKS: usize = 20;
1119pub const MAX_CHECK_NAME_CHARS: usize = 100;
1120
1121/// A check name seen on the repository's commits recently, for choosing
1122/// required checks: what reported it, and for which events.
1123#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1124#[serde(rename_all = "camelCase")]
1125pub struct SeenCheck {
1126 pub name: String,
1127 /// The events it was reported for, such as `pull_request`; empty for a
1128 /// status that names none, such as a deployment's.
1129 pub events: Vec<String>,
1130 /// RFC 3339. The latest report.
1131 pub last_seen: String,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains1132}
1133
Fast pages, required checks on the branch, self-hosted runners, honest incidents1134/// `seen_checks`: the check names reported on a repository's commits in
1135/// the last 30 days, most recent first. Returns `Outcome<Vec<SeenCheck>>`.
1136#[derive(Debug, Serialize, Deserialize)]
1137pub struct SeenChecksArgs {
1138 pub repo: RepoPath,
1139 pub viewer: Viewer,
1140}
1141
1142/// The heading an issue's plain-words description of done goes under.
1143pub const DEFINITION_OF_DONE: &str = "## Definition of done";
1144
1145/// An issue's body with `items` added as a bulleted "Definition of done"
1146/// section, for the agent and reviewers to read. Nothing is added when
1147/// `items` is empty, or when the body already has the section, so folding
1148/// the same items twice changes nothing.
1149pub fn with_definition_of_done(body: &str, items: &[String]) -> String {
1150 let items: Vec<&str> = items.iter().map(|item| item.trim()).filter(|item| !item.is_empty()).collect();
1151 let body = body.trim();
1152 if items.is_empty() || body.contains(DEFINITION_OF_DONE) {
1153 return body.to_owned();
1154 }
1155 let list = items.iter().map(|item| format!("- {item}")).collect::<Vec<_>>().join("\n");
1156 if body.is_empty() {
1157 format!("{DEFINITION_OF_DONE}\n\n{list}")
1158 } else {
1159 format!("{body}\n\n{DEFINITION_OF_DONE}\n\n{list}")
1160 }
1161}
1162
1163/// Commands, as items of a definition of done: "`npm test` passes."
1164pub fn commands_pass(commands: &[String]) -> Vec<String> {
1165 commands
1166 .iter()
1167 .map(|command| command.trim())
1168 .filter(|command| !command.is_empty())
1169 .map(|command| format!("`{}` passes.", command.replace('`', "'")))
1170 .collect()
1171}
1172
Agents and memory, checks and conflicts, profiles, slug renames, custom domains1173/// Whether a pull request's change merges cleanly into the branch it
1174/// targets.
1175#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
1176#[serde(rename_all = "lowercase")]
1177pub enum Mergeable {
1178 /// It merges without conflicts.
1179 Clean,
1180 /// Some files conflict: see `PullDetail::conflicts`.
1181 Conflicting,
1182 /// Not known: never worked out, or it could not be.
1183 #[default]
1184 Unknown,
1185 /// Being worked out now.
1186 Checking,
GitHub Actions on g1t, part two: running workflows1187}
1188
Agents and memory, checks and conflicts, profiles, slug renames, custom domains1189impl Mergeable {
1190 pub fn as_str(self) -> &'static str {
1191 match self {
1192 Mergeable::Clean => "clean",
1193 Mergeable::Conflicting => "conflicting",
1194 Mergeable::Unknown => "unknown",
1195 Mergeable::Checking => "checking",
1196 }
1197 }
1198
1199 pub fn parse(value: Option<&str>) -> Mergeable {
1200 match value {
1201 Some("clean") => Mergeable::Clean,
1202 Some("conflicting") => Mergeable::Conflicting,
1203 Some("checking") => Mergeable::Checking,
1204 _ => Mergeable::Unknown,
1205 }
1206 }
1207}
1208
1209/// `start_mergecheck`: claims the probe of whether a pull request merges
1210/// cleanly, which the work service asked for with a `pull.mergecheck`
1211/// event. Called by the runner service, which starts the sandbox. Refused
1212/// when it is no longer wanted, or when the repository already has as many
1213/// probes running as it may. Returns `Outcome<MergecheckJob>`.
1214#[derive(Debug, Serialize, Deserialize)]
1215#[serde(rename_all = "camelCase")]
1216pub struct StartMergecheckArgs {
1217 pub pull_id: String,
1218}
1219
1220/// What a sandbox needs to find out whether a pull request merges cleanly.
1221#[derive(Debug, Serialize, Deserialize)]
1222#[serde(rename_all = "camelCase")]
1223pub struct MergecheckJob {
1224 pub pull_id: String,
1225 /// Lets the sandbox, and nothing else, report this probe.
1226 pub token: String,
1227 pub repo: RepoPath,
1228 pub number: u32,
1229 pub default_branch: String,
1230 /// The default branch's commit to merge into.
1231 pub base: String,
1232 /// The repository holding the change: its fork, or the repository.
1233 pub source: RepoPath,
1234 /// The branch of `source` holding it.
1235 pub branch: String,
1236 /// The change's commit.
1237 pub head: String,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1238 /// Who the pull request is for ([`Pull::owner`]: whoever asked g1t for
1239 /// it, or its author), and so can read its source.
Agents and memory, checks and conflicts, profiles, slug renames, custom domains1240 pub author: User,
1241}
1242
1243/// `report_mergecheck`: what a sandbox found. Returns `Outcome<Mergeable>`.
1244#[derive(Debug, Serialize, Deserialize)]
1245#[serde(rename_all = "camelCase")]
1246pub struct ReportMergecheckArgs {
1247 pub pull_id: String,
1248 pub token: String,
1249 /// The files that conflict; empty when it merges cleanly.
1250 #[serde(default)]
1251 pub conflicts: Vec<String>,
1252 /// Why it could not be found out.
1253 #[serde(default)]
1254 pub error: Option<String>,
1255}
1256
GitHub Actions on g1t, part two: running workflows1257/// What a workflow run (or another tool) says about a commit.
Merge checks: statuses and check runs on every commit1258#[derive(Clone, Debug, Default, Serialize, Deserialize)]
GitHub Actions on g1t, part two: running workflows1259#[serde(rename_all = "camelCase")]
1260pub struct CommitStatus {
1261 /// What reported it, such as `CI / push`.
1262 pub context: String,
1263 /// `pending`, `success`, `failure` or `error`.
1264 pub state: String,
1265 pub description: Option<String>,
1266 /// Where to see more, such as the run's page.
1267 pub target_url: Option<String>,
1268 pub updated_at: String,
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge1269 /// The integration that reported it (`actions`, `deployments`,
1270 /// `security`, `g1t`), when recorded. A required check can insist on
1271 /// one (`rules::RequiredCheck::integration`).
1272 #[serde(default, skip_serializing_if = "Option::is_none")]
1273 pub source: Option<String>,
Merge checks: statuses and check runs on every commit1274 /// Set on the status a check run stands as (see `checks`): the check
1275 /// run's id. Such a status is listed as its check run, not again.
1276 #[serde(default, skip_serializing_if = "Option::is_none")]
1277 pub check_run_id: Option<String>,
GitHub Actions on g1t, part two: running workflows1278}
1279
1280/// `set_commit_status`: for services only. Returns `Outcome<bool>`.
1281#[derive(Debug, Serialize, Deserialize)]
1282#[serde(rename_all = "camelCase")]
1283pub struct SetCommitStatusArgs {
1284 pub repo_id: String,
1285 pub sha: String,
1286 pub context: String,
1287 pub state: String,
1288 #[serde(default)]
1289 pub description: Option<String>,
1290 #[serde(default)]
1291 pub target_url: Option<String>,
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge1292 /// The integration reporting it: `actions`, `deployments`, `security`
1293 /// or `g1t`.
1294 #[serde(default)]
1295 pub source: Option<String>,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1296}
1297
1298/// A message a person sent an agent at work on a pull request. The agent
1299/// receives it at its next step.
1300#[derive(Clone, Debug, Serialize, Deserialize)]
1301#[serde(rename_all = "camelCase")]
1302pub struct AgentMessage {
1303 pub id: String,
1304 pub author: String,
1305 pub body: String,
1306 /// RFC 3339.
1307 pub created_at: String,
1308 /// RFC 3339. When the agent received it; null until then.
1309 pub delivered_at: Option<String>,
Agents ask each other, hand each other work, and answer1310 /// `message` from a person, or from another pull request's agent a
1311 /// `question`, a `handoff` of work, or the `answer` to one.
1312 #[serde(default = "message_kind")]
1313 pub kind: String,
1314 /// The pull request whose agent sent it, when an agent did.
1315 #[serde(default)]
1316 pub from_number: Option<u32>,
1317 /// The pull request it was sent to.
1318 #[serde(default)]
1319 pub to_number: u32,
1320 /// For a question or handoff: the reply, once there is one.
1321 #[serde(default)]
1322 pub answer: Option<String>,
1323 /// For a handoff: whether it was declined.
1324 #[serde(default)]
1325 pub declined: bool,
1326 /// For the agent that sent it: what to expect, when the agent it asked
1327 /// is not at work and will not answer soon.
1328 #[serde(default, skip_serializing_if = "Option::is_none")]
1329 pub hint: Option<String>,
1330}
1331
1332fn message_kind() -> String {
1333 "message".to_owned()
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1334}
1335
1336/// `message_agent`: sends the agent working on a pull request a message.
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1337/// The pull request's owner ([`Pull::owner`]: whoever asked g1t for it, or
1338/// its author) and members of the workspace may. Returns
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1339/// `Outcome<AgentMessage>`.
1340#[derive(Debug, Serialize, Deserialize)]
1341pub struct MessageAgentArgs {
1342 pub actor: User,
1343 pub repo: RepoPath,
1344 pub number: u32,
1345 pub body: String,
Agents ask each other, hand each other work, and answer1346 /// For an agent: `question` or `handoff`; a person's is a `message`.
1347 #[serde(default)]
1348 pub kind: Option<String>,
1349 /// For an agent: the pull request it is working on, which the reply
1350 /// goes back to.
1351 #[serde(default)]
1352 pub from_number: Option<u32>,
1353}
1354
1355/// `answer_message`: replies to a question or a handoff an agent received,
1356/// accepting or declining a handoff. The reply reaches the asking agent at
1357/// its next step. Returns `Outcome<AgentMessage>`, the message answered.
1358#[derive(Debug, Serialize, Deserialize)]
1359pub struct AnswerMessageArgs {
1360 pub actor: User,
1361 pub repo: RepoPath,
1362 pub id: String,
1363 pub body: String,
1364 #[serde(default)]
1365 pub decline: bool,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1366}
1367
1368/// `take_messages`: the messages not yet delivered to the agent working on
1369/// a pull request, marked delivered. Only g1t's agents may. Returns
1370/// `Outcome<Vec<AgentMessage>>`.
1371#[derive(Debug, Serialize, Deserialize)]
1372pub struct TakeMessagesArgs {
1373 pub actor: User,
1374 pub repo: RepoPath,
1375 pub number: u32,
1376}
1377
1378/// A step on the way from an assigned issue to a pull request that is ready
1379/// to merge. g1t takes each one without being asked.
1380#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1381#[serde(rename_all = "snake_case")]
1382pub enum Stage {
1383 /// The agent is making the change.
1384 Working,
Fast pages, required checks on the branch, self-hosted runners, honest incidents1385 /// Waiting for the checks workflows report on its head.
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1386 Checking,
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1387 /// g1t is reviewing it.
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1388 Reviewing,
1389 /// The agent is addressing failed checks or a review.
1390 Revising,
1391 /// The agent is merging in the branch it would land on, which moved.
1392 CatchingUp,
Agents asked while not at work are woken to answer1393 /// Woken to answer a question another agent asked it, or a handoff.
1394 Answering,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1395 /// In the repository's merge queue, being tested with what is ahead of
1396 /// it before it lands.
1397 Queued,
Fast pages, required checks on the branch, self-hosted runners, honest incidents1398 /// Required checks passed, reviewed and approved, up to date. A
1399 /// person merges.
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1400 Ready,
1401 /// g1t has stopped and a person has to decide what happens next.
1402 NeedsYou,
1403}
1404
1405/// Where a pull request made by a g1t agent stands. See [`Stage`].
1406#[derive(Clone, Debug, Serialize, Deserialize)]
1407pub struct Lifecycle {
1408 pub stage: Stage,
1409 /// One sentence saying what is happening, or why it stopped.
1410 pub detail: String,
1411 /// How many times the agent has been sent back to revise it.
1412 pub revisions: u32,
1413}
1414
1415/// `advance`: works out the next step for a pull request g1t is seeing
1416/// through and, if there is one to take now, claims it, so that it is
1417/// taken once however many times this is called. Called by the runner
1418/// service, which carries the step out. Returns `Advance`.
1419#[derive(Debug, Serialize, Deserialize)]
1420#[serde(rename_all = "camelCase")]
1421pub struct AdvanceArgs {
1422 pub pull_id: String,
1423}
1424
1425#[derive(Debug, Serialize, Deserialize)]
1426#[serde(tag = "action", rename_all = "snake_case")]
1427pub enum Advance {
1428 /// Nothing to do now: a step is under way, or it is a person's turn.
1429 None,
1430 /// Have a g1t agent review it.
1431 Review { job: LifecycleJob },
1432 /// Send the agent back to address `job.feedback`.
1433 Revise { job: LifecycleJob },
1434 /// Merge in the branch it would land on.
1435 CatchUp { job: LifecycleJob },
1436}
1437
1438/// What the runner needs to carry out a step of a pull request's lifecycle.
1439#[derive(Debug, Serialize, Deserialize)]
1440#[serde(rename_all = "camelCase")]
1441pub struct LifecycleJob {
1442 pub pull_id: String,
1443 pub repo: RepoPath,
1444 pub number: u32,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1445 /// Who the pull request belongs to ([`Pull::owner`]: whoever asked g1t
1446 /// for it, or its author). Sandboxes act as them.
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1447 pub author: User,
1448 /// The repository holding the change: its fork, or the repository
1449 /// itself for one made on a branch.
1450 pub source: RepoPath,
1451 /// The branch of the source holding the change; its default branch
1452 /// when absent.
1453 #[serde(default)]
1454 pub branch: Option<String>,
1455 pub default_branch: String,
1456 pub title: String,
1457 pub description: String,
1458 pub issue: Option<Issue>,
1459 /// For a revision: the failed checks or the review to address.
1460 pub feedback: String,
1461 /// For a revision: which one this is, from 1.
1462 pub round: u32,
1463}
1464
1465/// How a repository wants its pull requests handled. A repository that has
1466/// changed nothing has the defaults.
1467#[derive(Clone, Debug, Serialize, Deserialize)]
1468#[serde(rename_all = "camelCase", default)]
1469pub struct RepoSettings {
1470 /// Land a g1t agent's pull request without a person once it is ready:
Fast pages, required checks on the branch, self-hosted runners, honest incidents1471 /// required checks passed and approved as the settings below require.
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1472 pub auto_merge: bool,
Fast pages, required checks on the branch, self-hosted runners, honest incidents1473 /// The checks that must pass on a pull request's head before it may
1474 /// merge into the default branch, by name: a workflow's name (`CI`), or
1475 /// the context of another status (`g1t / deploy`). The same for a
1476 /// person's pull request and an agent's, and for the merge queue.
1477 pub required_checks: Vec<String>,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1478 /// Refuse to merge a pull request that does not contain the default
1479 /// branch's latest commits, so that what merges is what was checked.
1480 /// When off, merging one that is behind brings it up to date first.
1481 pub require_up_to_date: bool,
1482 /// How many approving reviews a pull request needs before it may
1483 /// merge. A reviewer who has since asked for changes blocks it.
1484 pub required_approvals: u32,
1485 /// Whether a g1t agent's approval counts towards `required_approvals`.
1486 pub count_agent_approvals: bool,
Fast pages, required checks on the branch, self-hosted runners, honest incidents1487 /// Whether someone who may merge can bypass required checks that have
1488 /// not passed, by saying so as they merge.
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1489 pub allow_ignoring_checks: bool,
1490 /// Whether a g1t agent's pull request is reviewed by a second agent
1491 /// without being asked.
1492 pub agent_review: bool,
1493 /// How many times a g1t agent is sent back to its pull request before
1494 /// a person is asked instead.
1495 pub max_revisions: u32,
1496 /// Merge through a queue: pull requests are tested together with those
1497 /// ahead of them, and only a combination that passed reaches the default
1498 /// branch.
1499 pub merge_queue: bool,
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step1500 /// Ask a person before merging a g1t agent's change whose confidence is
1501 /// low: auto-merge and the merge queue leave it, and it needs someone,
1502 /// until a person approves it.
1503 pub hold_low_confidence: bool,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1504 /// Refuse to merge until the code owners of every file it changes
1505 /// (its CODEOWNERS file) have approved, as many as each section asks.
1506 /// Only people count, and `g1t` only where the file names `@g1t`.
1507 pub require_code_owner_review: bool,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1508 /// Username of the member who last changed the settings, if anyone has.
1509 pub updated_by: Option<String>,
1510 /// RFC 3339.
1511 pub updated_at: Option<String>,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1512}
1513
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1514impl Default for RepoSettings {
1515 fn default() -> Self {
1516 RepoSettings {
1517 auto_merge: false,
Fast pages, required checks on the branch, self-hosted runners, honest incidents1518 required_checks: Vec::new(),
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1519 require_up_to_date: false,
1520 required_approvals: 0,
1521 count_agent_approvals: true,
1522 allow_ignoring_checks: true,
1523 agent_review: true,
1524 max_revisions: 2,
1525 merge_queue: false,
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step1526 hold_low_confidence: true,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1527 require_code_owner_review: false,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1528 updated_by: None,
1529 updated_at: None,
1530 }
1531 }
1532}
1533
1534/// `update_settings`: replaces a repository's settings. Members of its
1535/// workspace only. Returns `Outcome<RepoSettings>`. `get_settings` takes
1536/// `ViewArgs` and returns the same.
1537#[derive(Debug, Serialize, Deserialize)]
1538pub struct UpdateSettingsArgs {
1539 pub actor: User,
1540 pub repo: RepoPath,
1541 /// Who changed them and when are filled in by the service.
1542 pub settings: RepoSettings,
1543}
1544
1545/// `catch_up_job`: what the runner needs to bring a pull request up to date
1546/// because a merge of it was asked for. Null if none was. Returns
1547/// `Option<LifecycleJob>`.
1548#[derive(Debug, Serialize, Deserialize)]
1549#[serde(rename_all = "camelCase")]
1550pub struct CatchUpJobArgs {
1551 pub pull_id: String,
1552}
1553
Agents asked while not at work are woken to answer1554/// `wake_for_messages`: the agent on a pull request was asked a question
1555/// or handed work while it was not at work. Claims a short step for it to
1556/// answer, and hands over what it was sent, marked read. Null when there
1557/// is nothing waiting, or the pull request cannot take a step now.
1558/// Returns `Option<Wake>`.
1559#[derive(Debug, Serialize, Deserialize)]
1560#[serde(rename_all = "camelCase")]
1561pub struct WakeForMessagesArgs {
1562 pub pull_id: String,
1563}
1564
1565/// What an agent woken to answer needs: its pull request, and what it was
1566/// sent, oldest first.
1567#[derive(Debug, Serialize, Deserialize)]
1568#[serde(rename_all = "camelCase")]
1569pub struct Wake {
1570 pub job: LifecycleJob,
1571 pub messages: Vec<AgentMessage>,
1572}
1573
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1574/// `stall`: records that a step could not be carried out, so that g1t
1575/// stops and a person is asked. Returns `bool`.
1576#[derive(Debug, Serialize, Deserialize)]
1577#[serde(rename_all = "camelCase")]
1578pub struct StallArgs {
1579 pub pull_id: String,
1580 pub reason: String,
Events: review requests, assignments, stops and deployments are published1581 /// The person who stopped it, by id, when someone did: they are not
1582 /// told it needs them.
1583 #[serde(default)]
1584 pub by: Option<String>,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1585}
1586
1587/// `managed_pulls`: ids of the open pull requests g1t is seeing through,
1588/// in one repository or in all of them. Returns `Vec<String>`.
1589#[derive(Debug, Default, Serialize, Deserialize)]
1590#[serde(rename_all = "camelCase")]
1591pub struct ManagedPullsArgs {
1592 #[serde(default)]
1593 pub repo_id: Option<String>,
1594}
1595
1596/// `open_issue`. Returns `Outcome<Issue>`.
1597#[derive(Debug, Serialize, Deserialize)]
1598pub struct OpenIssueArgs {
1599 pub actor: User,
1600 pub repo: RepoPath,
1601 pub title: String,
1602 #[serde(default)]
1603 pub body: String,
1604 #[serde(default)]
1605 pub labels: Vec<String>,
Fast pages, required checks on the branch, self-hosted runners, honest incidents1606 /// Deprecated: commands, added to the body under "Definition of done".
1607 /// Checks are the workflows the branch's protection requires.
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1608 #[serde(default)]
1609 pub checks: Vec<String>,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1610 /// The number of the milestone to put it in. Needs the Triage role.
1611 #[serde(default)]
1612 pub milestone: Option<u32>,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1613}
1614
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1615/// `delegate_issue`: opens an issue to put g1t on at once, refused
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step1616/// before anything is opened unless `actor` may put agents to work in the
1617/// repository (Run, which the Write role has). The runner service's
1618/// `delegate` calls it and then starts the agent. Returns `Outcome<Issue>`.
1619#[derive(Debug, Serialize, Deserialize)]
1620pub struct DelegateIssueArgs {
1621 pub actor: User,
1622 pub repo: RepoPath,
1623 pub title: String,
1624 #[serde(default)]
1625 pub body: String,
1626 #[serde(default)]
1627 pub labels: Vec<String>,
Fast pages, required checks on the branch, self-hosted runners, honest incidents1628 /// Deprecated, as on `OpenIssueArgs`.
Thirteen MCP tools and classic token scopes; agents rate their confidence and can be put on an issue in one step1629 #[serde(default)]
1630 pub checks: Vec<String>,
1631}
1632
1633/// What became of the agent when an issue was opened and handed to it in
1634/// one step.
1635#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1636#[serde(rename_all = "snake_case")]
1637pub enum AgentStartStatus {
1638 /// It is at work on the issue's pull request.
1639 Started,
1640 /// Every agent slot of the workspace is busy: it starts on its own when
1641 /// one frees up.
1642 Queued,
1643 /// It did not start, and will not until someone fixes what `code` says.
1644 NotStarted,
1645}
1646
1647/// Whether the agent started, and if not, why and what fixes it.
1648#[derive(Clone, Debug, Serialize, Deserialize)]
1649#[serde(rename_all = "camelCase")]
1650pub struct AgentStart {
1651 pub status: AgentStartStatus,
1652 /// Why it did not start: `not_paid`, `trial_used`, `limit`, `paused`,
1653 /// `issue_cap`, `billing_unavailable` or `no_model`; `waiting` when
1654 /// queued.
1655 #[serde(default)]
1656 pub code: Option<String>,
1657 /// What happened, in a sentence or two, with what to do.
1658 #[serde(default)]
1659 pub message: Option<String>,
1660 /// Where the fix is: the workspace's billing or model settings.
1661 #[serde(default)]
1662 pub fix_url: Option<String>,
1663}
1664
1665/// The runner service's `delegate`: the issue opened, and the agent put on
1666/// it. The issue exists whatever became of the agent.
1667#[derive(Clone, Debug, Serialize, Deserialize)]
1668pub struct Delegated {
1669 pub issue: Issue,
1670 /// The pull request the agent opened, when it started.
1671 #[serde(default)]
1672 pub pull: Option<Pull>,
1673 pub agent: AgentStart,
1674}
1675
1676/// `report_confidence`: what the agent of a run says of its own change,
1677/// with the run's own token. Kept with the run, and the pull request's
1678/// confidence is worked out again with it. Returns `Outcome<bool>`.
1679#[derive(Debug, Serialize, Deserialize)]
1680#[serde(rename_all = "camelCase")]
1681pub struct ReportConfidenceArgs {
1682 pub run_id: String,
1683 pub token: String,
1684 /// `high`, `medium` or `low`.
1685 pub confidence: String,
1686 #[serde(default)]
1687 pub uncertain_about: Vec<String>,
1688}
1689
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1690/// `list_issues`, newest first. Returns `Outcome<Vec<Issue>>`.
1691#[derive(Debug, Serialize, Deserialize)]
1692pub struct ListIssuesArgs {
1693 pub repo: RepoPath,
1694 pub viewer: Viewer,
1695 #[serde(default)]
1696 pub state: Option<State>,
1697 /// Only issues carrying this label.
1698 #[serde(default)]
1699 pub label: Option<String>,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1700 /// Only issues in the milestone of this number.
1701 #[serde(default)]
1702 pub milestone: Option<u32>,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1703}
1704
1705/// `list_pulls`, newest first. Returns `Outcome<Vec<Pull>>`.
1706#[derive(Debug, Serialize, Deserialize)]
1707pub struct ListPullsArgs {
1708 pub repo: RepoPath,
1709 pub viewer: Viewer,
1710 #[serde(default)]
1711 pub state: Option<State>,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1712 /// Only pull requests carrying this label.
1713 #[serde(default)]
1714 pub label: Option<String>,
1715 /// Only pull requests in the milestone of this number.
1716 #[serde(default)]
1717 pub milestone: Option<u32>,
1718 /// Only pull requests into this branch.
1719 #[serde(default)]
1720 pub base: Option<String>,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1721}
1722
Fast pages, required checks on the branch, self-hosted runners, honest incidents1723/// `pulls_for_repos`: the newest open and the newest closed pull requests
1724/// of many repositories, in one call, for pages that show several projects
1725/// at once. Repositories the viewer cannot read are left out, as are forks
1726/// (ask those with `list_pulls`). Returns `Vec<RepoPulls>`.
1727#[derive(Debug, Serialize, Deserialize)]
1728#[serde(rename_all = "camelCase")]
1729pub struct PullsForReposArgs {
1730 /// At most [`MAX_PULLS_FOR_REPOS`] are looked at.
1731 pub repo_ids: Vec<String>,
1732 pub viewer: Viewer,
1733 /// How many of each, open and closed, per repository (at most 100).
1734 pub limit: u32,
1735}
1736
1737/// The most repositories one `pulls_for_repos` call looks at.
1738pub const MAX_PULLS_FOR_REPOS: usize = 50;
1739
1740/// One repository's pull requests from `pulls_for_repos`, newest first.
1741#[derive(Debug, Serialize, Deserialize)]
1742#[serde(rename_all = "camelCase")]
1743pub struct RepoPulls {
1744 pub repo_id: String,
1745 /// Draft and open.
1746 pub open: Vec<Pull>,
1747 /// Merged and closed.
1748 pub closed: Vec<Pull>,
1749}
1750
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1751/// `get_issue` (`Outcome<IssueDetail>`), `get_pull` (`Outcome<PullDetail>`),
1752/// `read_session` (`Outcome<Vec<SessionEntry>>`), `list_labels`
1753/// (`Outcome<Vec<String>>`) and `counts` (`Outcome<Counts>`). The last two
1754/// ignore `number`.
1755#[derive(Debug, Serialize, Deserialize)]
1756#[serde(rename_all = "camelCase")]
1757pub struct ViewArgs {
1758 pub repo: RepoPath,
1759 #[serde(default)]
1760 pub number: u32,
1761 pub viewer: Viewer,
1762 /// For `read_session`: only entries after this sequence number.
1763 #[serde(default)]
1764 pub after_seq: u32,
1765}
1766
1767/// How many issues and pull requests are open on a repository.
1768#[derive(Debug, Serialize, Deserialize)]
1769pub struct Counts {
1770 pub issues: u32,
1771 pub pulls: u32,
1772}
1773
1774/// `update_issue`: changes whichever fields are given. The author or a
1775/// member of the workspace may. Returns `Outcome<Issue>`.
1776#[derive(Debug, Serialize, Deserialize)]
1777pub struct UpdateIssueArgs {
1778 pub actor: User,
1779 pub repo: RepoPath,
1780 pub number: u32,
1781 #[serde(default)]
1782 pub title: Option<String>,
1783 #[serde(default)]
1784 pub body: Option<String>,
1785 #[serde(default)]
1786 pub labels: Option<Vec<String>>,
1787 /// Usernames of the people it is assigned to; replaces the whole set.
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent1788 /// Assigning it to g1t is the runner's `run`, not this.
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1789 #[serde(default)]
1790 pub assignees: Option<Vec<String>>,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1791 /// The number of the milestone to put it in; 0 takes it out of its
1792 /// milestone. Needs the Triage role.
1793 #[serde(default)]
1794 pub milestone: Option<u32>,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1795}
1796
1797/// `close_issue` and `reopen_issue`. Each returns `Outcome<Issue>`.
1798#[derive(Debug, Serialize, Deserialize)]
1799pub struct IssueActionArgs {
1800 pub actor: User,
1801 pub repo: RepoPath,
1802 pub number: u32,
1803 /// For `close_issue`; `completed` if left out.
1804 #[serde(default)]
1805 pub reason: Option<IssueReason>,
1806}
1807
1808/// `add_comment`, on an issue or a pull request. On a pull request it may
1809/// name a line of the change, and may carry a verdict; nobody can give a
1810/// verdict on their own pull request. Returns `Outcome<Comment>`.
1811#[derive(Debug, Serialize, Deserialize)]
1812pub struct AddCommentArgs {
1813 pub actor: User,
1814 pub repo: RepoPath,
1815 pub number: u32,
1816 /// May be empty when approving.
1817 #[serde(default)]
1818 pub body: String,
1819 #[serde(default)]
1820 pub path: Option<String>,
1821 #[serde(default)]
1822 pub line: Option<u32>,
1823 #[serde(default)]
1824 pub verdict: Option<Verdict>,
1825}
1826
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1827/// `edit_comment` and `delete_comment`: change the text of a comment on an
1828/// issue or a pull request, or delete it. Its author may, and so may anyone
1829/// with the Maintain role or higher ([`may_change_comment`]). Notes of what
1830/// happened (`CommentKind::Event`) cannot be changed, and a review that
1831/// gave a verdict cannot be deleted, only edited. `edit_comment` returns
1832/// `Outcome<Comment>`; `delete_comment` returns `Outcome<()>`.
1833#[derive(Debug, Serialize, Deserialize)]
1834#[serde(rename_all = "camelCase")]
1835pub struct CommentActionArgs {
1836 pub actor: User,
1837 pub repo: RepoPath,
1838 pub comment_id: String,
1839 /// For `edit_comment`: the new text, in Markdown.
1840 #[serde(default)]
1841 pub body: String,
1842}
1843
1844/// Why `actor_id` may not edit (or, with `deleting`, delete) a comment by
1845/// `author_id`, or none when they may: its author may, and so may someone
1846/// who `maintains` the repository (the Maintain role or higher). A note of
1847/// something that happened is never changed, and a review's verdict stays,
1848/// so a comment that gave one can be edited but not deleted.
1849pub fn may_change_comment(
1850 kind: CommentKind,
1851 has_verdict: bool,
1852 author_id: &str,
1853 actor_id: &str,
1854 maintains: bool,
1855 deleting: bool,
1856) -> Result<(), &'static str> {
1857 if kind == CommentKind::Event {
1858 return Err("That is a record of something that happened, not a comment; it cannot be changed.");
1859 }
1860 if author_id != actor_id && !maintains {
1861 return Err(if deleting {
1862 "Only its author, or someone with the Maintain role or higher, can delete this comment."
1863 } else {
1864 "Only its author, or someone with the Maintain role or higher, can edit this comment."
1865 });
1866 }
1867 if deleting && has_verdict {
1868 return Err("A review cannot be deleted. Edit its text instead.");
1869 }
1870 Ok(())
1871}
1872
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1873/// `open_pull`. Without `branch`, forks the repo and returns a draft pull
1874/// request to push to. With it, opens a pull request, ready for review,
1875/// for a branch already pushed to the repo. Returns `Outcome<Pull>`.
1876#[derive(Debug, Serialize, Deserialize)]
1877pub struct OpenPullArgs {
1878 pub actor: User,
1879 pub repo: RepoPath,
1880 /// The issue this is for.
1881 #[serde(default)]
1882 pub issue: Option<u32>,
1883 /// Defaults to the issue's title; required without an issue.
1884 #[serde(default)]
1885 pub title: String,
1886 /// What changed and why. Usually set later, when a draft is marked ready.
1887 #[serde(default)]
1888 pub body: String,
1889 /// A branch of the repository that already holds the change.
1890 #[serde(default)]
1891 pub branch: Option<String>,
1892 #[serde(default)]
1893 pub agent: String,
1894 pub runtime: Runtime,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1895 /// The branch to merge into: the default branch when absent.
1896 #[serde(default)]
1897 pub base: Option<String>,
Open a pull request as a draft: it can't merge, and agents' review routines wait, until it is marked ready1898 /// Opened from a branch as a draft, still being worked on: it can't
1899 /// merge, and agents' review routines wait, until it is marked ready.
1900 #[serde(default)]
1901 pub draft: bool,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1902}
1903
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts1904/// `ready_pull`, `close_pull`, `reopen_pull`, `convert_pull_to_draft` and
1905/// `merge_pull`. Each returns `Outcome<Pull>`. `reopen_pull` opens a closed
1906/// pull request again (never a merged one), as the draft it was if it was
1907/// closed as one; `convert_pull_to_draft` turns one that is open back into
1908/// a draft, which leaves the merge queue.
Catching up with main takes seconds when the two sides touched different files1909///
1910/// Also `catch_up_pull`: brings the pull request up to date with the
1911/// default branch without a sandbox where that is safe, as the repos
1912/// service's `update_pull_branch` does, after checking that `actor` may
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights1913/// update it: its owner ([`Pull::owner`]) for a fork, any member for a
1914/// branch.
Catching up with main takes seconds when the two sides touched different files1915/// Returns `Outcome<repos::PullBranchUpdate>`; on `needs_agent` nothing was
1916/// pushed and the runner's `update` is the way on.
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1917#[derive(Debug, Serialize, Deserialize)]
1918#[serde(rename_all = "camelCase")]
1919pub struct PullActionArgs {
1920 pub actor: User,
1921 pub repo: RepoPath,
1922 pub number: u32,
1923 /// For `ready_pull`: what changed and why.
1924 #[serde(default)]
1925 pub summary: String,
1926 /// For `merge_pull`: leave the issue open and the other pull requests
1927 /// for it untouched, because this one is only part of the work.
1928 #[serde(default)]
1929 pub keep_issue_open: bool,
Fast pages, required checks on the branch, self-hosted runners, honest incidents1930 /// For `merge_pull`: merge although required checks have not passed,
1931 /// where the repository lets members bypass them.
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1932 #[serde(default)]
1933 pub ignore_checks: bool,
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge1934 /// For `merge_pull`: merge although rules are not met, where a ruleset
1935 /// lists the actor as one who may bypass it. Recorded as a bypass.
1936 #[serde(default)]
1937 pub bypass_rules: bool,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1938}
1939
1940/// Where a plan stands.
1941#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1942#[serde(rename_all = "lowercase")]
1943pub enum PlanStatus {
1944 /// An agent is reading the repository and writing it.
1945 Planning,
1946 /// Written, and waiting for a person to read and apply it.
1947 Ready,
1948 /// It could not be written.
1949 Failed,
1950 /// Its issues have been opened.
1951 Applied,
1952}
1953
1954/// One issue a plan proposes.
1955#[derive(Clone, Debug, Default, Serialize, Deserialize)]
1956#[serde(rename_all = "camelCase", default)]
1957pub struct PlannedIssue {
1958 pub title: String,
1959 /// Markdown: what to change, where, and why.
1960 pub body: String,
1961 pub labels: Vec<String>,
Fast pages, required checks on the branch, self-hosted runners, honest incidents1962 /// What is true once it is done, in plain words. Added to the issue's
1963 /// body under "Definition of done". Plans written before this was
1964 /// called `done` named it `checks`.
1965 #[serde(alias = "checks")]
1966 pub done: Vec<String>,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request1967 /// The files it will most likely change.
1968 pub files: Vec<String>,
1969 /// The positions, counting from 1, of earlier issues in the plan that
1970 /// have to be merged first. An agent writes this as `depends_on`.
1971 #[serde(alias = "depends_on")]
1972 pub depends_on: Vec<u32>,
1973 /// Its number, once the plan has been applied and it was kept.
1974 pub number: Option<u32>,
1975}
1976
1977/// An outcome someone wrote, and the issues an agent proposes to get there.
1978#[derive(Clone, Debug, Serialize, Deserialize)]
1979#[serde(rename_all = "camelCase")]
1980pub struct Plan {
1981 pub id: String,
1982 pub repo_id: String,
1983 /// The outcome wanted, as written.
1984 pub brief: String,
1985 pub status: PlanStatus,
1986 /// The agent's account of how it split the work.
1987 pub summary: String,
1988 pub issues: Vec<PlannedIssue>,
1989 /// Why it could not be written, when `status` is `failed`.
1990 pub error: Option<String>,
1991 pub author: User,
1992 /// RFC 3339.
1993 pub created_at: String,
1994 /// RFC 3339.
1995 pub finished_at: Option<String>,
1996 /// Once applied: where each issue it opened stands now, in plan order.
1997 /// Filled in by `get_plan` only.
1998 #[serde(default)]
1999 pub progress: Vec<IssueProgress>,
Agents ask each other, hand each other work, and answer2000 /// Questions and handoffs between the agents on its pull requests,
2001 /// newest first. Filled in by `get_plan` only.
2002 #[serde(default)]
2003 pub exchanges: Vec<AgentMessage>,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request2004}
2005
2006/// Where one issue of an applied plan stands.
2007#[derive(Clone, Debug, Serialize, Deserialize)]
2008#[serde(rename_all = "camelCase")]
2009pub struct IssueProgress {
2010 pub number: u32,
2011 pub title: String,
2012 /// `blocked` (waiting on issues it depends on), `waiting` (for an
2013 /// agent), `open` (nobody on it), one of the lifecycle stages
2014 /// (`working`, `checking`, `reviewing`, `revising`, `catching_up`,
Agents asked while not at work are woken to answer2015 /// `answering`, `queued`, `ready`, `needs_you`), `landed` or `closed`.
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request2016 pub state: String,
2017 /// One sentence about where it stands.
2018 pub detail: String,
2019 /// The issues it is waiting on that are still open.
2020 pub blocked_by: Vec<u32>,
2021 /// The pull request carrying it, the newest if several.
2022 pub pull: Option<u32>,
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent2023 /// Who or what is working on it, e.g. `g1t`.
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request2024 pub agent: Option<String>,
2025}
2026
2027/// `start_plan`: records an outcome to plan for. Members of the
2028/// repository's workspace only. Called by the runner service, which starts
2029/// the sandbox. Returns `Outcome<PlanJob>`.
2030#[derive(Debug, Serialize, Deserialize)]
2031pub struct StartPlanArgs {
2032 pub actor: User,
2033 pub repo: RepoPath,
2034 pub brief: String,
2035}
2036
2037/// What a sandbox needs to write a plan.
2038#[derive(Debug, Serialize, Deserialize)]
2039#[serde(rename_all = "camelCase")]
2040pub struct PlanJob {
2041 pub plan_id: String,
2042 /// Lets the sandbox, and nothing else, report this plan.
2043 pub token: String,
2044 pub brief: String,
2045 pub repo: RepoPath,
2046}
2047
2048/// `report_plan`: the plan a sandbox's agent wrote, or why it could not
2049/// write one. Returns `Outcome<bool>`.
2050#[derive(Debug, Serialize, Deserialize)]
2051#[serde(rename_all = "camelCase")]
2052pub struct ReportPlanArgs {
2053 pub plan_id: String,
2054 pub token: String,
2055 #[serde(default)]
2056 pub summary: String,
2057 #[serde(default)]
2058 pub issues: Vec<PlannedIssue>,
2059 #[serde(default)]
2060 pub error: Option<String>,
2061}
2062
2063/// `get_plan`. Members only. Returns `Outcome<Plan>`. `list_plans` takes
2064/// `ViewArgs` and returns `Outcome<Vec<Plan>>`, newest first.
2065#[derive(Debug, Serialize, Deserialize)]
2066pub struct PlanArgs {
2067 pub repo: RepoPath,
2068 pub viewer: Viewer,
2069 pub id: String,
2070}
2071
2072/// `apply_plan`: opens a plan's issues, each blocked by the ones it depends
2073/// on. Members only, and once. Returns `Outcome<Plan>`, its issues now
2074/// carrying their numbers.
2075#[derive(Debug, Serialize, Deserialize)]
2076pub struct ApplyPlanArgs {
2077 pub actor: User,
2078 pub repo: RepoPath,
2079 pub id: String,
2080 /// Queue every issue for a g1t agent.
2081 #[serde(default)]
2082 pub assign: bool,
2083 /// The positions, counting from 1, of the issues to open. All of them
2084 /// when absent.
2085 #[serde(default)]
2086 pub keep: Option<Vec<u32>>,
2087}
2088
2089/// `queue_issue`: asks for a g1t agent to take an issue as soon as it can,
2090/// or withdraws that. The author or a member may. Returns `Outcome<bool>`.
2091#[derive(Debug, Serialize, Deserialize)]
2092pub struct QueueIssueArgs {
2093 pub actor: User,
2094 pub repo: RepoPath,
2095 pub number: u32,
2096 pub queued: bool,
2097}
2098
2099/// `ready_issues`: issues waiting for a g1t agent that can be given one
2100/// now, in one repository or in all. Called by the runner service. Returns
2101/// `Vec<ReadyIssue>`.
2102#[derive(Debug, Default, Serialize, Deserialize)]
2103#[serde(rename_all = "camelCase")]
2104pub struct ReadyIssuesArgs {
2105 #[serde(default)]
2106 pub repo_id: Option<String>,
2107}
2108
2109#[derive(Debug, Serialize, Deserialize)]
2110pub struct ReadyIssue {
2111 pub repo: RepoPath,
2112 pub number: u32,
2113 /// Who queued it, on whose say-so the agent works.
2114 pub actor: User,
2115}
2116
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar2117/// `update_pull`: changes who a pull request is assigned to, whose review
2118/// is asked for, its labels, its milestone and the branch it merges into.
2119/// Each list given replaces the whole set. Whoever opened it, or someone
2120/// with the Triage role, may; changing the base needs the Write role.
2121/// Returns `Outcome<Pull>`.
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request2122#[derive(Debug, Serialize, Deserialize)]
2123pub struct UpdatePullArgs {
2124 pub actor: User,
2125 pub repo: RepoPath,
2126 pub number: u32,
2127 #[serde(default)]
2128 pub assignees: Option<Vec<String>>,
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent2129 /// May include `g1t`. Asking for its review does not by itself
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar2130 /// start one; the runner's `review` does. A team is named
2131 /// `workspace/team` (or `@workspace/team`): the whole list, people and
2132 /// teams, replaces who is asked.
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request2133 #[serde(default)]
2134 pub reviewers: Option<Vec<String>>,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar2135 /// Its labels; replaces the whole set, as `set_labels` does.
2136 #[serde(default)]
2137 pub labels: Option<Vec<String>>,
2138 /// The number of the milestone to put it in; 0 takes it out. Needs the
2139 /// Triage role.
2140 #[serde(default)]
2141 pub milestone: Option<u32>,
2142 /// The branch it merges into: an existing branch other than its own.
2143 /// Needs the Write role.
2144 #[serde(default)]
2145 pub base: Option<String>,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request2146}
2147
Fast pages, required checks on the branch, self-hosted runners, honest incidents2148/// `start_checks`: always refused now; a pull request's checks are the
2149/// workflows run on it. Kept so that a runner from before is answered.
2150/// Returns `Outcome<CheckJob>`.
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request2151#[derive(Debug, Serialize, Deserialize)]
2152#[serde(rename_all = "camelCase")]
2153pub struct StartChecksArgs {
2154 pub pull_id: String,
2155}
2156
2157/// What a sandbox needs to carry out a check run.
2158#[derive(Debug, Serialize, Deserialize)]
2159#[serde(rename_all = "camelCase")]
2160pub struct CheckJob {
2161 pub run_id: String,
2162 /// Lets the sandbox, and nothing else, report this run's results.
2163 pub token: String,
2164 pub commands: Vec<String>,
2165 /// The repository holding the commit: the fork, or the repository itself.
2166 pub source: RepoPath,
2167 pub commit: String,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights2168 /// Who the pull request is for ([`Pull::owner`]: whoever asked g1t for
2169 /// it, or its author), and so can read its source.
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request2170 pub author: User,
2171 /// Username of whoever wrote the checks: the issue's author.
2172 pub requested_by: String,
2173 pub repo: RepoPath,
2174 pub number: u32,
2175}
2176
2177/// `report_checks`: what a sandbox says about its run. With no results and
2178/// no error it has started. `skip` forgets the run, for one that will not
2179/// be carried out. Returns `Outcome<CheckRun>`.
2180#[derive(Debug, Serialize, Deserialize)]
2181#[serde(rename_all = "camelCase")]
2182pub struct ReportChecksArgs {
2183 pub run_id: String,
2184 pub token: String,
2185 #[serde(default)]
2186 pub results: Vec<CheckResult>,
2187 #[serde(default)]
2188 pub error: Option<String>,
2189 #[serde(default)]
2190 pub skip: bool,
2191}
2192
2193/// A pull request in progress, with where it lives.
2194#[derive(Debug, Serialize, Deserialize)]
2195pub struct ActivePull {
2196 pub pull: Pull,
2197 pub issue: Option<Issue>,
2198 /// Where it stands, for one g1t is seeing through.
2199 #[serde(default)]
2200 pub lifecycle: Option<Lifecycle>,
2201}
2202
2203/// `list_active_pulls`: drafts and open pull requests the viewer started,
2204/// most recently active first. Returns `Vec<ActivePull>`. Also
2205/// `list_assigned_issues`: open issues assigned to the viewer, most
2206/// recently changed first. Returns `Vec<Issue>`.
2207#[derive(Debug, Serialize, Deserialize)]
2208pub struct ViewerArgs {
2209 pub viewer: Viewer,
2210}
2211
2212/// `append_session`. Returns `Outcome<Appended>`.
2213#[derive(Debug, Serialize, Deserialize)]
2214pub struct AppendSessionArgs {
2215 pub actor: User,
2216 pub repo: RepoPath,
2217 pub number: u32,
2218 pub entries: Vec<NewSessionEntry>,
2219}
2220
2221#[derive(Debug, Serialize, Deserialize)]
2222pub struct Appended {
2223 pub count: u32,
2224}
2225
2226/// `start_review`: begins a review of a pull request by a g1t agent. Called
2227/// by the runner service, which starts the sandbox. Returns
2228/// `Outcome<ReviewJob>`.
2229#[derive(Debug, Serialize, Deserialize)]
2230#[serde(rename_all = "camelCase")]
2231pub struct StartReviewArgs {
2232 pub pull_id: String,
2233}
2234
2235/// What a sandbox needs to review a pull request.
2236#[derive(Debug, Serialize, Deserialize)]
2237#[serde(rename_all = "camelCase")]
2238pub struct ReviewJob {
2239 pub run_id: String,
2240 /// Lets the sandbox, and nothing else, report this review.
2241 pub token: String,
2242 /// The repository holding the commit: the fork, or the repository itself.
2243 pub source: RepoPath,
2244 pub commit: String,
2245 pub repo: RepoPath,
2246 pub default_branch: String,
2247 pub number: u32,
2248 pub title: String,
2249 pub description: String,
2250 /// The issue the pull request is for, which says what it should achieve.
2251 pub issue: Option<Issue>,
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights2252 /// Who the pull request is for ([`Pull::owner`]: whoever asked g1t for
2253 /// it, or its author), and so can read its source.
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request2254 pub author: User,
Auto model routing: the cheapest tier that can do each piece of work, a retry goes up a tier, and each run records its tier2255 /// The files it changes, as of its latest push: how large the change
2256 /// is, which decides the model that reviews it.
2257 #[serde(default)]
2258 pub files: Vec<ChangedFile>,
2259 /// What among them runs, configures or guards things (CI workflows,
2260 /// secrets, infrastructure), once each. Any sends the review to the
2261 /// larger model.
2262 #[serde(default)]
2263 pub sensitive: Vec<String>,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request2264}
2265
2266/// A comment on one line, as a reviewing agent reports it.
2267#[derive(Debug, Serialize, Deserialize)]
2268pub struct ReviewComment {
2269 pub path: String,
2270 #[serde(default)]
2271 pub line: u32,
2272 pub body: String,
2273}
2274
2275/// `report_review`: the review a sandbox's agent wrote, or why it could not
2276/// write one. Returns `Outcome<bool>`.
2277#[derive(Debug, Serialize, Deserialize)]
2278#[serde(rename_all = "camelCase")]
2279pub struct ReportReviewArgs {
2280 pub run_id: String,
2281 pub token: String,
2282 #[serde(default)]
2283 pub verdict: Option<Verdict>,
2284 #[serde(default)]
2285 pub body: String,
2286 #[serde(default)]
2287 pub comments: Vec<ReviewComment>,
2288 /// The model that wrote it, by its public name.
2289 #[serde(default)]
2290 pub model: Option<String>,
2291 #[serde(default)]
2292 pub error: Option<String>,
2293}
2294
2295/// Lowercases, trims and de-duplicates labels, dropping empty ones.
2296/// Returns `None` if there are too many or one is too long.
2297pub fn normalize_labels(labels: &[String]) -> Option<Vec<String>> {
2298 let mut normalized: Vec<String> = Vec::new();
2299 for label in labels {
2300 let label = label
2301 .split_whitespace()
2302 .collect::<Vec<_>>()
2303 .join(" ")
2304 .to_lowercase();
2305 if label.is_empty() || normalized.contains(&label) {
2306 continue;
2307 }
2308 if label.chars().count() > MAX_LABEL_CHARS {
2309 return None;
2310 }
2311 normalized.push(label);
2312 }
2313 (normalized.len() <= MAX_LABELS).then_some(normalized)
2314}
2315
2316#[cfg(test)]
2317mod tests {
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar2318 use super::*;
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request2319
2320 fn labels(names: &[&str]) -> Vec<String> {
2321 names.iter().map(|name| (*name).to_owned()).collect()
2322 }
2323
2324 #[test]
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar2325 fn colors_and_due_dates_are_tidied() {
2326 assert_eq!(tidy_color("#A1B2C3").as_deref(), Some("a1b2c3"));
2327 assert_eq!(tidy_color("fc0").as_deref(), Some("ffcc00"));
2328 assert_eq!(tidy_color("red"), None);
2329 assert_eq!(tidy_color("12345"), None);
2330 assert_eq!(label_color_for("bug"), "d73a4a");
2331 assert_eq!(label_color_for("area: web"), label_color_for("area: web"));
2332 assert_eq!(tidy_due_on("2026-10-14").as_deref(), Some("2026-10-14"));
2333 assert_eq!(tidy_due_on("2026-10-14T00:00:00Z").as_deref(), Some("2026-10-14"));
2334 assert_eq!(tidy_due_on("2026-13-01"), None);
2335 assert_eq!(tidy_due_on("soon"), None);
2336 }
2337
2338 #[test]
2339 fn a_milestone_is_as_far_along_as_its_closed_items() {
2340 let mut milestone: Milestone = serde_json::from_value(serde_json::json!({
2341 "number": 1, "title": "Launch", "state": "open", "createdAt": "", "updatedAt": ""
2342 }))
2343 .unwrap();
2344 assert_eq!(milestone.percent(), 0);
2345 milestone.open_items = 3;
2346 milestone.closed_items = 1;
2347 assert_eq!(milestone.percent(), 25);
2348 }
2349
2350 #[test]
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request2351 fn labels_are_lowercased_trimmed_and_unique() {
2352 assert_eq!(
2353 normalize_labels(&labels(&[" Bug ", "bug", "", "Good First Issue"])),
2354 Some(labels(&["bug", "good first issue"]))
2355 );
2356 }
2357
2358 #[test]
2359 fn too_long_or_too_many_labels_are_refused() {
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar2360 assert_eq!(normalize_labels(&["x".repeat(51)]), None);
2361 assert!(normalize_labels(&["x".repeat(50)]).is_some());
2362 let many: Vec<String> = (0..21).map(|i| format!("label-{i}")).collect();
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request2363 assert_eq!(normalize_labels(&many), None);
2364 }
2365}
2366
2367
2368// --- Merge queue ----------------------------------------------------------
2369
2370/// Where a pull request in a merge queue stands.
2371#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
2372#[serde(rename_all = "snake_case")]
2373pub enum QueueState {
2374 /// Waiting for its turn to be tested.
2375 Waiting,
2376 /// Its combined state is being built and checked.
2377 Testing,
2378 /// Its combined state passed; it lands once everything ahead has.
2379 Passed,
2380 /// Its combined state failed, or would not merge. It left the queue.
2381 Failed,
2382 /// On the default branch.
2383 Landed,
2384 /// Taken out of the queue by a person, or closed.
2385 Removed,
2386}
2387
2388impl QueueState {
2389 pub fn as_str(self) -> &'static str {
2390 match self {
2391 QueueState::Waiting => "waiting",
2392 QueueState::Testing => "testing",
2393 QueueState::Passed => "passed",
2394 QueueState::Failed => "failed",
2395 QueueState::Landed => "landed",
2396 QueueState::Removed => "removed",
2397 }
2398 }
2399
2400 /// Still in the queue.
2401 pub fn is_active(self) -> bool {
2402 matches!(
2403 self,
2404 QueueState::Waiting | QueueState::Testing | QueueState::Passed
2405 )
2406 }
2407}
2408
2409/// One pull request's place in a merge queue.
2410#[derive(Clone, Debug, Serialize, Deserialize)]
2411#[serde(rename_all = "camelCase")]
2412pub struct QueueEntry {
2413 pub id: String,
2414 pub number: u32,
2415 pub title: String,
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent2416 /// Who or what made the pull request, e.g. `g1t`.
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request2417 pub agent: String,
2418 pub state: QueueState,
2419 /// The pull requests merged ahead of it in the state being tested, in
2420 /// queue order. Empty when it was tested on the default branch alone.
2421 pub ahead: Vec<u32>,
2422 /// The default branch's commit the tested state was built on.
2423 pub base_commit: Option<String>,
2424 /// The tested state: the default branch with everything ahead and this.
2425 pub combined_commit: Option<String>,
2426 /// Why it failed: a merge conflict or what could not be run.
2427 pub error: Option<String>,
Fast pages, required checks on the branch, self-hosted runners, honest incidents2428 /// Commands run against the tested state by queues from before its
2429 /// checks were workflows. Empty since.
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request2430 pub results: Vec<CheckResult>,
2431 /// Username of whoever merged it into the queue: a person, or `g1t`.
2432 pub enqueued_by: String,
2433 /// RFC 3339.
2434 pub created_at: String,
2435 /// RFC 3339. When it landed or left.
2436 pub finished_at: Option<String>,
2437}
2438
2439/// A repository's merge queue: what is in it, in order, and what recently
2440/// left it.
2441#[derive(Clone, Debug, Serialize, Deserialize)]
2442#[serde(rename_all = "camelCase")]
2443pub struct QueueView {
2444 /// Whether the repository merges through the queue.
2445 pub enabled: bool,
2446 pub active: Vec<QueueEntry>,
2447 /// Newest first.
2448 pub recent: Vec<QueueEntry>,
2449}
2450
2451/// `queue`: a repository's merge queue. Returns `Outcome<QueueView>`.
2452#[derive(Debug, Serialize, Deserialize)]
2453pub struct QueueArgs {
2454 pub repo: RepoPath,
2455 pub viewer: Viewer,
2456}
2457
2458/// `queue_build`: the next batch to test for a repository, if nothing is
2459/// being tested now. Returns `Vec<QueueJob>`, one per entry, each testing
2460/// the default branch with that entry and everything ahead of it.
2461#[derive(Debug, Serialize, Deserialize)]
2462#[serde(rename_all = "camelCase")]
2463pub struct QueueBuildArgs {
2464 pub repo_id: String,
2465}
2466
2467/// One pull request in a state being tested: where its change is.
2468#[derive(Clone, Debug, Serialize, Deserialize)]
2469#[serde(rename_all = "camelCase")]
2470pub struct QueueStackItem {
2471 pub number: u32,
2472 pub title: String,
2473 /// The repository holding the change: its fork, or the repository.
2474 pub source: RepoPath,
2475 /// The branch of `source` holding it.
2476 pub branch: String,
2477 pub commit: String,
2478}
2479
2480/// What a sandbox needs to build and check one combined state.
2481#[derive(Clone, Debug, Serialize, Deserialize)]
2482#[serde(rename_all = "camelCase")]
2483pub struct QueueJob {
2484 pub entry_id: String,
2485 /// Lets the sandbox, and nothing else, report this state's result.
2486 pub token: String,
2487 pub repo: RepoPath,
2488 pub default_branch: String,
2489 /// The default branch's commit to build on.
2490 pub base_commit: String,
2491 /// Where to push the tested state, in the repository itself.
2492 pub branch: String,
2493 /// The pull requests to merge in, in order; the last is the entry.
2494 pub stack: Vec<QueueStackItem>,
Fast pages, required checks on the branch, self-hosted runners, honest incidents2495 /// Commands to run on the built state. Always empty: the state is
2496 /// checked by the `merge_group` workflows run on it, and the default
2497 /// branch's required checks must pass there.
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request2498 pub checks: Vec<String>,
Fast pages, required checks on the branch, self-hosted runners, honest incidents2499 /// Always empty, as `checks`.
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request2500 #[serde(default)]
2501 pub contract_checks: Vec<String>,
2502 /// Who the sandbox acts as: a member who can push the tested state.
2503 pub actor: User,
2504}
2505
2506/// `report_queue`: a sandbox's result for one combined state. Returns
2507/// `Outcome<QueueState>`.
2508#[derive(Debug, Serialize, Deserialize)]
2509#[serde(rename_all = "camelCase")]
2510pub struct ReportQueueArgs {
2511 pub entry_id: String,
2512 pub token: String,
2513 #[serde(default)]
2514 pub combined_commit: Option<String>,
2515 #[serde(default)]
2516 pub results: Vec<CheckResult>,
2517 /// Set when the state could not be built or checked.
2518 #[serde(default)]
2519 pub error: Option<String>,
2520 /// For a merge conflict: the pull request whose change it collided with.
2521 #[serde(default)]
2522 pub conflict_with: Option<u32>,
Agents and memory, checks and conflicts, profiles, slug renames, custom domains2523 /// For a merge conflict: the files that conflicted.
2524 #[serde(default)]
2525 pub conflicts: Vec<String>,
Usage, like a hosting provider's: what agents cost, per day, task, repository and pull request2526}
Record your own agent's sessions automatically2527
2528
2529/// `locate_pull`: where a pull request lives, by its id, for a tool that
2530/// knows only the fork it is working in (`g1t.sh/pulls/<id>`). Returns
2531/// `Outcome<LocatedPull>`; not found for anyone who cannot see it.
2532#[derive(Debug, Serialize, Deserialize)]
2533pub struct LocatePullArgs {
2534 pub id: String,
2535 pub viewer: Viewer,
2536}
2537
2538#[derive(Clone, Debug, Serialize, Deserialize)]
2539#[serde(rename_all = "camelCase")]
2540pub struct LocatedPull {
2541 pub repo: RepoPath,
2542 pub number: u32,
2543 pub title: String,
2544 pub status: PullStatus,
2545}
Agents and memory, checks and conflicts, profiles, slug renames, custom domains2546
2547// --- A person's work -------------------------------------------------------
2548
2549/// Issues or pull requests, on a person's profile.
2550#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
2551#[serde(rename_all = "lowercase")]
2552pub enum AuthoredKind {
2553 Issue,
2554 Pull,
2555}
2556
2557/// The state filter on a person's work. `Closed` takes in merged pull
2558/// requests too; `Merged` is only those.
2559#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
2560#[serde(rename_all = "lowercase")]
2561pub enum AuthoredState {
2562 Open,
2563 Closed,
2564 Merged,
2565}
2566
2567/// How a person's work is ordered.
2568#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
2569#[serde(rename_all = "lowercase")]
2570pub enum AuthoredSort {
2571 /// Newest first.
2572 #[default]
2573 Created,
2574 /// Most recently changed first.
2575 Updated,
2576 /// Oldest first.
2577 Oldest,
2578}
2579
2580/// The most items one `by_author` page holds.
2581pub const AUTHORED_PAGE: u32 = 25;
2582
2583/// `by_author`: the issues and pull requests a person opened, only on
2584/// repositories `viewer` may read, so a private title never reaches anyone
2585/// who could not open it. Returns `Outcome<Authored>`; not found for an
2586/// account that does not exist.
2587#[derive(Debug, Serialize, Deserialize)]
2588pub struct ByAuthorArgs {
2589 pub username: String,
2590 pub viewer: Viewer,
2591 #[serde(default)]
2592 pub kind: Option<AuthoredKind>,
2593 #[serde(default)]
2594 pub state: Option<AuthoredState>,
2595 /// Only work on this repository: `namespace/name`.
2596 #[serde(default)]
2597 pub repo: Option<String>,
2598 #[serde(default)]
2599 pub sort: AuthoredSort,
2600 /// The `next` of the page before, to read on from there.
2601 #[serde(default)]
2602 pub before: Option<String>,
2603 /// At most [`AUTHORED_PAGE`]; that when absent.
2604 #[serde(default)]
2605 pub limit: Option<u32>,
2606}
2607
2608/// One issue or pull request a person opened.
2609#[derive(Clone, Debug, Serialize, Deserialize)]
2610#[serde(rename_all = "camelCase")]
2611pub struct AuthoredItem {
2612 pub kind: AuthoredKind,
2613 pub repo: RepoPath,
2614 pub number: u32,
2615 pub title: String,
2616 /// Open or closed; a merged pull request is closed.
2617 pub state: State,
2618 /// A pull request's own status.
2619 pub status: Option<PullStatus>,
2620 /// Why an issue was closed.
2621 pub reason: Option<IssueReason>,
2622 pub draft: bool,
2623 pub merged: bool,
2624 /// RFC 3339.
2625 pub created_at: String,
2626 /// RFC 3339.
2627 pub updated_at: String,
2628 /// When a pull request was merged. RFC 3339.
2629 pub merged_at: Option<String>,
2630}
2631
2632/// What a person has done, as far as the viewer may see.
2633#[derive(Clone, Debug, Default, Serialize, Deserialize)]
2634#[serde(rename_all = "camelCase")]
2635pub struct AuthoredCounts {
2636 pub pulls_merged: u32,
2637 pub pulls_open: u32,
2638 pub pulls: u32,
2639 pub issues: u32,
2640 pub issues_open: u32,
2641}
2642
2643/// A repository a person has opened work on, with how much.
2644#[derive(Clone, Debug, Serialize, Deserialize)]
2645pub struct AuthoredRepo {
2646 pub repo: RepoPath,
2647 pub count: u32,
2648}
2649
2650/// A page of a person's work.
2651#[derive(Clone, Debug, Default, Serialize, Deserialize)]
2652#[serde(rename_all = "camelCase")]
2653pub struct Authored {
2654 pub items: Vec<AuthoredItem>,
2655 /// Pass as `before` for the next page; null on the last.
2656 pub next: Option<String>,
2657 /// Over every repository the viewer may read, whatever the filters.
2658 pub counts: AuthoredCounts,
2659 /// Those repositories, most work first.
2660 pub repos: Vec<AuthoredRepo>,
2661}
Fast pages, required checks on the branch, self-hosted runners, honest incidents2662
Chat controls, public profiles, shadcn selects, and no Docs tab in a project2663/// How many days `contributions` covers: today and the 364 before it.
2664pub const CONTRIBUTION_DAYS: u64 = 365;
2665
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar2666/// `contributions`: what a person did each day of the last year (commits
2667/// they pushed to a default branch, issues and pull requests opened,
2668/// reviews given), only on repositories `viewer` may read, for the calendar on their profile. Returns
Chat controls, public profiles, shadcn selects, and no Docs tab in a project2669/// `Outcome<Contributions>`; not found for an account that does not exist.
2670#[derive(Debug, Serialize, Deserialize)]
2671pub struct ContributionsArgs {
2672 pub username: String,
2673 pub viewer: Viewer,
2674}
2675
2676/// One day with something on it. Days with nothing are left out.
2677#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
2678pub struct ContributionDay {
2679 /// `YYYY-MM-DD`, UTC.
2680 pub date: String,
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar2681 /// Everything that day, commits included.
Chat controls, public profiles, shadcn selects, and no Docs tab in a project2682 pub count: u32,
Cards you act on in chat; agents comment and review as themselves; names shown cleanly; commits on the calendar2683 /// How many of `count` are commits they pushed (repos' `commit_days`).
2684 #[serde(default)]
2685 pub commits: u32,
Chat controls, public profiles, shadcn selects, and no Docs tab in a project2686}
2687
2688/// A person's year, as far as the viewer may see.
2689#[derive(Clone, Debug, Default, Serialize, Deserialize)]
2690pub struct Contributions {
2691 /// Oldest first.
2692 pub days: Vec<ContributionDay>,
2693 pub total: u32,
2694 /// The first day counted, `YYYY-MM-DD`; the last is today.
2695 pub from: String,
2696}
2697
Fast pages, required checks on the branch, self-hosted runners, honest incidents2698#[cfg(test)]
2699mod required_tests {
2700 use super::*;
2701
2702 fn status(context: &str, state: &str) -> CommitStatus {
2703 CommitStatus {
2704 context: context.into(),
2705 state: state.into(),
2706 description: Some(format!("{context} {state}")),
2707 target_url: None,
2708 updated_at: String::new(),
Merge rulesets: branch and tag rules, agent-first, enforced on push and merge2709 source: None,
Merge checks: statuses and check runs on every commit2710 check_run_id: None,
Fast pages, required checks on the branch, self-hosted runners, honest incidents2711 }
2712 }
2713
2714 #[test]
2715 fn a_context_names_its_check_and_event() {
2716 assert_eq!(check_name("CI / pull_request"), ("CI", Some("pull_request")));
2717 assert_eq!(check_name("Build and test / merge_group"), ("Build and test", Some("merge_group")));
2718 assert_eq!(check_name("g1t / deploy"), ("g1t / deploy", None));
2719 assert_eq!(check_name("g1t / deploy (docs)"), ("g1t / deploy (docs)", None));
2720 assert_eq!(check_name("lint"), ("lint", None));
2721 }
2722
2723 #[test]
2724 fn required_checks_are_missing_pending_failed_or_passed() {
2725 let required = vec!["CI".to_owned(), "Lint".to_owned(), "g1t / deploy".to_owned(), "Docs".to_owned()];
2726 let statuses = [
2727 status("CI / pull_request", "success"),
2728 status("Lint / pull_request", "pending"),
2729 status("g1t / deploy", "failure"),
2730 ];
2731 let states: Vec<RequiredState> = required_checks(&required, &statuses).into_iter().map(|c| c.state).collect();
2732 assert_eq!(
2733 states,
2734 [RequiredState::Success, RequiredState::Pending, RequiredState::Failure, RequiredState::Expected]
2735 );
2736 }
2737
2738 #[test]
2739 fn any_event_reports_a_check_and_a_failure_wins() {
2740 let required = vec!["ci".to_owned()];
2741 let both = [status("CI / push", "failure"), status("CI / pull_request", "success")];
2742 let check = &required_checks(&required, &both)[0];
2743 assert_eq!(check.state, RequiredState::Failure);
2744 assert_eq!(check.description.as_deref(), Some("CI / push failure"));
2745 let queue = [status("CI / merge_group", "success")];
2746 assert_eq!(required_checks(&required, &queue)[0].state, RequiredState::Success);
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar2747 }
2748
2749 #[test]
2750 fn a_pull_request_merges_into_its_base_or_the_default_branch() {
2751 let mut pull: Pull = serde_json::from_value(serde_json::json!({
2752 "id": "pr_1", "repoId": "rep_1", "number": 14, "issue": null, "title": "Fix it", "body": null,
2753 "agent": "agent", "runtime": "external", "status": "open", "fork": null, "forkRepoId": null,
2754 "branch": "fix", "headCommit": null, "mergeBase": null, "mergedBy": null, "mergedAt": null,
2755 "supersededBy": null, "checkStatus": null, "author": { "id": "x", "username": "x" },
2756 "createdAt": "", "updatedAt": ""
2757 }))
2758 .unwrap();
2759 assert_eq!(pull.base_branch("main"), "main");
2760 assert!(pull.targets_default("main"));
2761 assert_eq!(pull.comparison(&None).base_branch, None);
2762 pull.base = Some("release".into());
2763 assert_eq!(pull.base_branch("main"), "release");
2764 assert!(!pull.targets_default("main"));
2765 assert_eq!(pull.comparison(&None).base_branch.as_deref(), Some("release"));
2766 }
2767
2768 #[test]
Fast pages, required checks on the branch, self-hosted runners, honest incidents2769 fn required_names_are_tidied() {
2770 let names = vec![" CI ".to_owned(), "ci".to_owned(), String::new(), "Lint".to_owned()];
2771 assert_eq!(tidy_required(&names), ["CI", "Lint"]);
2772 }
2773
2774 #[test]
2775 fn a_definition_of_done_is_added_once() {
2776 let items = commands_pass(&["cargo test".to_owned(), " ".to_owned()]);
2777 assert_eq!(items, ["`cargo test` passes."]);
2778 let body = with_definition_of_done("Fix the greeting.", &items);
2779 assert_eq!(body, "Fix the greeting.\n\n## Definition of done\n\n- `cargo test` passes.");
2780 assert_eq!(with_definition_of_done(&body, &items), body);
2781 assert_eq!(with_definition_of_done("", &items), "## Definition of done\n\n- `cargo test` passes.");
2782 assert_eq!(with_definition_of_done(" Text ", &[]), "Text");
2783 }
2784}
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights2785
2786#[cfg(test)]
2787mod authorship_tests {
2788 use super::*;
2789 use crate::PrincipalKind;
2790 use crate::credentials::{Acting, Principal};
2791 use crate::identity::{AGENT_ID, AgentScope};
2792
2793 fn person() -> User {
2794 User {
2795 id: "usr_1".into(),
2796 username: "syntaqx".into(),
2797 verified: true,
2798 ..User::default()
2799 }
2800 }
2801
2802 fn agent_for(id: &str, username: &str) -> User {
2803 User {
2804 id: AGENT_ID.into(),
2805 username: "g1t".into(),
2806 kind: PrincipalKind::Agent,
2807 acting: Some(Box::new(Acting {
2808 credential_id: "tok_1".into(),
2809 agent: "g1t".into(),
2810 on_behalf_of: Principal { id: id.into(), username: username.into() },
2811 scope: AgentScope {
2812 repo: RepoPath { namespace: "acme".into(), name: "web".into() },
2813 operations: Vec::new(),
2814 run: None,
2815 },
2816 })),
2817 ..User::default()
2818 }
2819 }
2820
2821 fn pull(author: User, requested_by: Option<User>) -> Pull {
2822 let mut pull: Pull = serde_json::from_value(serde_json::json!({
2823 "id": "pr_1", "repoId": "rep_1", "number": 14, "issue": 12, "title": "Fix it", "body": null,
2824 "agent": "g1t", "runtime": "hosted", "status": "open", "fork": null, "forkRepoId": null,
2825 "branch": null, "headCommit": null, "mergeBase": null, "mergedBy": null, "mergedAt": null,
2826 "supersededBy": null, "checkStatus": null,
2827 "author": { "id": "x", "username": "x" },
2828 "createdAt": "", "updatedAt": ""
2829 }))
2830 .unwrap();
2831 pull.author = author;
2832 pull.requested_by = requested_by;
2833 pull
2834 }
2835
2836 #[test]
2837 fn a_change_a_person_has_g1t_make_is_g1t_s_requested_by_them() {
2838 let (author, asked) = authorship(&person(), true);
2839 assert_eq!((author.id.as_str(), author.username.as_str(), author.kind), (AGENT_ID, "g1t", PrincipalKind::Agent));
2840 let asked = asked.expect("the person asked for it");
2841 assert_eq!((asked.id.as_str(), asked.username.as_str(), asked.kind), ("usr_1", "syntaqx", PrincipalKind::User));
2842 }
2843
2844 #[test]
2845 fn what_g1t_s_agent_files_at_work_is_g1t_s_requested_by_whoever_it_works_for() {
2846 let (author, asked) = authorship(&agent_for("usr_1", "syntaqx"), false);
2847 assert_eq!(author.id, AGENT_ID);
2848 assert_eq!(asked.map(|user| user.username), Some("syntaqx".into()));
2849 }
2850
2851 #[test]
2852 fn nobody_asked_for_g1t_s_own_work() {
2853 // A run g1t started itself acts for g1t, not for a person.
2854 let (author, asked) = authorship(&agent_for(crate::system::ID, "g1t"), false);
2855 assert_eq!(author.id, AGENT_ID);
2856 assert!(asked.is_none());
2857 // A security update g1t opens is g1t's own, as before.
2858 let (author, asked) = authorship(&User::system("acme"), true);
2859 assert_eq!((author.id.as_str(), author.kind), (crate::system::ID, PrincipalKind::System));
2860 assert!(asked.is_none());
2861 }
2862
2863 #[test]
2864 fn anyone_else_opens_their_own() {
2865 let (author, asked) = authorship(&person(), false);
2866 assert_eq!((author.id.as_str(), author.kind), ("usr_1", PrincipalKind::User));
2867 assert!(asked.is_none());
2868 // Nothing but who they are is kept.
2869 assert!(author.workspaces.is_empty() && author.acting.is_none());
2870 }
2871
2872 #[test]
2873 fn the_requester_owns_g1t_s_pull_request_and_an_author_their_own() {
2874 let made = pull(g1t_author(), Some(person()));
2875 assert_eq!(made.owner().id, "usr_1");
2876 assert!(made.is_owned_by("usr_1"));
2877 assert!(!made.is_owned_by(AGENT_ID), "g1t's agent does not answer for its own change");
2878 let own = pull(person(), None);
2879 assert_eq!(own.owner().id, "usr_1");
2880 assert!(own.is_owned_by("usr_1"));
2881 }
2882
2883 #[test]
2884 fn requested_by_is_null_when_nobody_asked_and_read_as_absent_from_older_senders() {
2885 let own = pull(person(), None);
2886 let sent = serde_json::to_value(&own).unwrap();
2887 assert!(sent["requestedBy"].is_null());
2888 let mut older = sent.clone();
2889 older.as_object_mut().unwrap().remove("requestedBy");
2890 let read: Pull = serde_json::from_value(older).unwrap();
2891 assert!(read.requested_by.is_none());
2892 }
Merge Actions: cross-repo workflows and actions, release and deployment triggers, step timeouts2893
2894 #[test]
2895 fn a_comment_is_changed_by_its_author_or_a_maintainer() {
2896 use CommentKind::{Comment, Event};
2897 // Its author edits and deletes it; someone else needs Maintain.
2898 assert!(may_change_comment(Comment, false, "usr_1", "usr_1", false, false).is_ok());
2899 assert!(may_change_comment(Comment, false, "usr_1", "usr_1", false, true).is_ok());
2900 assert!(may_change_comment(Comment, false, "usr_1", "usr_2", false, false).is_err());
2901 assert!(may_change_comment(Comment, false, "usr_1", "usr_2", false, true).is_err());
2902 assert!(may_change_comment(Comment, false, "usr_1", "usr_2", true, false).is_ok());
2903 assert!(may_change_comment(Comment, false, "usr_1", "usr_2", true, true).is_ok());
2904 // A review's text can be edited, but its verdict stays.
2905 assert!(may_change_comment(Comment, true, "usr_1", "usr_1", false, false).is_ok());
2906 assert!(may_change_comment(Comment, true, "usr_1", "usr_1", true, true).is_err());
2907 // A note of what happened is nobody's to change.
2908 assert!(may_change_comment(Event, false, "usr_1", "usr_1", true, false).is_err());
2909 assert!(may_change_comment(Event, false, "usr_1", "usr_1", true, true).is_err());
2910 }
g1t is the stored author of what it opens; the person who asked is requested_by and keeps the author's rights2911}

This file's history is long; its oldest lines are credited to the oldest commit read.